Fungerande PEM inläsning, behöver dock testas så att privata nyckeln i cert stor…
…e faktiskt går att använda efter installation.
Showing
6 changed files
with
142 additions
and
32 deletions
| ... | @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | ... | @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution |
| 8 | LICENSE = LICENSE | 8 | LICENSE = LICENSE |
| 9 | LIL_VSTT_Plugins.vsmdi = LIL_VSTT_Plugins.vsmdi | 9 | LIL_VSTT_Plugins.vsmdi = LIL_VSTT_Plugins.vsmdi |
| 10 | Local.testsettings = Local.testsettings | 10 | Local.testsettings = Local.testsettings |
| 11 | Notes.md = Notes.md | ||
| 11 | README.md = README.md | 12 | README.md = README.md |
| 12 | EndProjectSection | 13 | EndProjectSection |
| 13 | EndProject | 14 | EndProject | ... | ... |
| ... | @@ -55,6 +55,12 @@ | ... | @@ -55,6 +55,12 @@ |
| 55 | <ItemGroup> | 55 | <ItemGroup> |
| 56 | <None Include="Registry\Schannel_high_withclient.reg" /> | 56 | <None Include="Registry\Schannel_high_withclient.reg" /> |
| 57 | </ItemGroup> | 57 | </ItemGroup> |
| 58 | <ItemGroup> | ||
| 59 | <ProjectReference Include="..\crypto\crypto.Net45.csproj"> | ||
| 60 | <Project>{45473847-8af8-4baf-b768-442c6875b8cf}</Project> | ||
| 61 | <Name>crypto.Net45</Name> | ||
| 62 | </ProjectReference> | ||
| 63 | </ItemGroup> | ||
| 58 | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | 64 | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| 59 | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | 65 | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. |
| 60 | Other similar extension points exist, see Microsoft.Common.targets. | 66 | Other similar extension points exist, see Microsoft.Common.targets. | ... | ... |
| ... | @@ -549,12 +549,12 @@ namespace LIL_VSTT_Plugins | ... | @@ -549,12 +549,12 @@ namespace LIL_VSTT_Plugins |
| 549 | public class ClientCertificatePlugin : WebTestPlugin | 549 | public class ClientCertificatePlugin : WebTestPlugin |
| 550 | { | 550 | { |
| 551 | [DisplayName("Certificate Path")] | 551 | [DisplayName("Certificate Path")] |
| 552 | [Description("Sökvägen till certifikatfilen (P12/PFX med privat nyckel)")] | 552 | [Description("Sökvägen till certifikatfilen (.P12/.PFX/.PEM med privat nyckel)")] |
| 553 | [DefaultValue("")] | 553 | [DefaultValue("")] |
| 554 | public string pCertificatePath { get; set; } | 554 | public string pCertificatePath { get; set; } |
| 555 | 555 | ||
| 556 | [DisplayName("Certificate Path Parameter")] | 556 | [DisplayName("Certificate Path Parameter")] |
| 557 | [Description("Ange namn på parameter som ska användas för sökvägen till certifikatfilen (P12/PFX). Om parametern saknas eller är tom används Certificate Path")] | 557 | [Description("Ange namn på parameter som ska användas för sökvägen till certifikatfilen (.P12/.PFX/.PEM). Om parametern saknas eller är tom används Certificate Path")] |
| 558 | [DefaultValue("")] | 558 | [DefaultValue("")] |
| 559 | public string pCertificatePathParameter { get; set; } | 559 | public string pCertificatePathParameter { get; set; } |
| 560 | 560 | ||
| ... | @@ -585,6 +585,7 @@ namespace LIL_VSTT_Plugins | ... | @@ -585,6 +585,7 @@ namespace LIL_VSTT_Plugins |
| 585 | 585 | ||
| 586 | private bool haveCert = false; | 586 | private bool haveCert = false; |
| 587 | private X509Certificate myClientCert; | 587 | private X509Certificate myClientCert; |
| 588 | private X509Certificate2 myClientCertAndKey; | ||
| 588 | 589 | ||
| 589 | public override void PreWebTest(object sender, PreWebTestEventArgs e) | 590 | public override void PreWebTest(object sender, PreWebTestEventArgs e) |
| 590 | { | 591 | { |
| ... | @@ -616,35 +617,114 @@ namespace LIL_VSTT_Plugins | ... | @@ -616,35 +617,114 @@ namespace LIL_VSTT_Plugins |
| 616 | return; | 617 | return; |
| 617 | } | 618 | } |
| 618 | 619 | ||
| 619 | try { | 620 | // Check what type of container we have. All files are treated as PEM unless the extension is .pfx or .p12 |
| 620 | myClientCert = new X509Certificate(certPath, certPass); | 621 | // Read certificate and private key depending on type |
| 621 | } catch (Exception ex) | 622 | if (certPath.ToLower().EndsWith(".pfx") || certPath.ToLower().EndsWith(".p12")) |
| 622 | { | 623 | { |
| 623 | e.WebTest.AddCommentToResult("Error during loading of certificate: " + certPath + " Message: " + ex.Message); | 624 | if (pDebug) e.WebTest.AddCommentToResult("Certificate file is treated as PFX/PKCS12"); |
| 624 | return; | 625 | try |
| 626 | { | ||
| 627 | myClientCertAndKey = new X509Certificate2(certPath, certPass, X509KeyStorageFlags.PersistKeySet); | ||
| 628 | myClientCert = new X509Certificate(myClientCertAndKey); | ||
| 629 | } | ||
| 630 | catch (Exception ex) | ||
| 631 | { | ||
| 632 | e.WebTest.AddCommentToResult("Error during loading of certificate: " + certPath + " Message: " + ex.Message); | ||
| 633 | return; | ||
| 634 | } | ||
| 635 | } else | ||
| 636 | { | ||
| 637 | if (pDebug) e.WebTest.AddCommentToResult("Certificate file is treated as PEM/PKCS8"); | ||
| 638 | |||
| 639 | // Use Bouncy Castle to read the certificate and key, then convert to .NET X509Certificate2 and X509Certificate | ||
| 640 | String text; | ||
| 641 | try { | ||
| 642 | text = File.ReadAllText(certPath); | ||
| 643 | } catch (Exception ex) | ||
| 644 | { | ||
| 645 | e.WebTest.AddCommentToResult("Error opening PEM file: " + certPath + " Message: " + ex.Message); | ||
| 646 | return; | ||
| 647 | } | ||
| 648 | |||
| 649 | // Find the key and certificate in file and load them | ||
| 650 | int keyTextBeginPos = text.IndexOf("-----BEGIN"); | ||
| 651 | int keyTextEndPos = text.IndexOf("-----END"); | ||
| 652 | Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair bcKey = null; | ||
| 653 | Org.BouncyCastle.X509.X509Certificate bcCert = null; | ||
| 654 | |||
| 655 | while (keyTextBeginPos != -1) | ||
| 656 | { | ||
| 657 | text = text.Substring(keyTextBeginPos); | ||
| 658 | object obj; | ||
| 659 | try | ||
| 660 | { | ||
| 661 | Org.BouncyCastle.OpenSsl.PemReader bc = new Org.BouncyCastle.OpenSsl.PemReader(new StringReader(text), new PasswordHelper(certPass)); | ||
| 662 | obj = bc.ReadObject(); | ||
| 663 | } | ||
| 664 | catch (Org.BouncyCastle.Crypto.InvalidCipherTextException ex) | ||
| 665 | { | ||
| 666 | e.WebTest.AddCommentToResult("Error during reading of PEM file: " + certPath + " Wrong password? Message: " + ex.Message); | ||
| 667 | return; | ||
| 668 | } | ||
| 669 | catch (Exception ex) | ||
| 670 | { | ||
| 671 | e.WebTest.AddCommentToResult("Error during reading of PEM file: " + certPath + " Message: " + ex.GetType().FullName + ":" + ex.Message); | ||
| 672 | return; | ||
| 673 | } | ||
| 674 | if (obj != null) | ||
| 675 | { | ||
| 676 | if (pDebug) e.WebTest.AddCommentToResult("Found PEM Object of type " + obj.GetType().FullName); | ||
| 677 | if(obj is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair) | ||
| 678 | { | ||
| 679 | // We have a keypair | ||
| 680 | bcKey = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)obj; | ||
| 681 | } | ||
| 682 | if(obj is Org.BouncyCastle.X509.X509Certificate) | ||
| 683 | { | ||
| 684 | // We have a certificate | ||
| 685 | bcCert = (Org.BouncyCastle.X509.X509Certificate)obj; | ||
| 686 | } | ||
| 687 | } | ||
| 688 | keyTextBeginPos = text.IndexOf("-----BEGIN", keyTextEndPos); | ||
| 689 | } | ||
| 690 | if (bcKey == null || bcCert == null) | ||
| 691 | { | ||
| 692 | e.WebTest.AddCommentToResult("Error: PEM file has to contain both certificate and private key"); | ||
| 693 | return; | ||
| 694 | } | ||
| 695 | try { | ||
| 696 | myClientCert = Org.BouncyCastle.Security.DotNetUtilities.ToX509Certificate(bcCert); | ||
| 697 | myClientCertAndKey = new X509Certificate2(myClientCert); | ||
| 698 | myClientCertAndKey.PrivateKey = Org.BouncyCastle.Security.DotNetUtilities.ToRSA(bcKey.Private as Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters); | ||
| 699 | } catch (Exception ex) | ||
| 700 | { | ||
| 701 | e.WebTest.AddCommentToResult("Error during loading of PEM file: " + certPath + " Message: " + ex.Message); | ||
| 702 | return; | ||
| 703 | } | ||
| 625 | } | 704 | } |
| 626 | 705 | ||
| 627 | if(myClientCert == null) { | 706 | // Check that we have a certificate |
| 707 | if (myClientCert == null) | ||
| 708 | { | ||
| 628 | if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " could not be loaded."); | 709 | if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " could not be loaded."); |
| 629 | return; | 710 | return; |
| 630 | } else | 711 | } |
| 712 | else | ||
| 631 | { | 713 | { |
| 632 | if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath); | 714 | if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " loaded successfully."); |
| 633 | } | 715 | } |
| 634 | 716 | ||
| 717 | // Check that it seems okey | ||
| 635 | if (string.IsNullOrWhiteSpace(myClientCert.GetCertHashString())) | 718 | if (string.IsNullOrWhiteSpace(myClientCert.GetCertHashString())) |
| 636 | { | 719 | { |
| 637 | if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " contains no SHA1 hash. Not using it."); | 720 | if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " contains no SHA1 hash. Not using it."); |
| 638 | return; | 721 | return; |
| 639 | } | 722 | } |
| 640 | |||
| 641 | if (pDebug) e.WebTest.AddCommentToResult("Loaded client certificate for Subject: [" + myClientCert.Subject + "] Issued by: [" + myClientCert.Issuer + "] Expires: [" + myClientCert.GetExpirationDateString() + "]"); | 723 | if (pDebug) e.WebTest.AddCommentToResult("Loaded client certificate for Subject: [" + myClientCert.Subject + "] Issued by: [" + myClientCert.Issuer + "] Expires: [" + myClientCert.GetExpirationDateString() + "]"); |
| 642 | 724 | ||
| 643 | // Check if the certificate is trusted (i.e. chain can be validated) | 725 | // Check if the certificate is trusted (i.e. chain can be validated) |
| 644 | bool myCertTrusted = false; | 726 | bool myCertTrusted = false; |
| 645 | X509Certificate2 cer = new X509Certificate2(certPath, certPass, X509KeyStorageFlags.PersistKeySet); | 727 | if (myClientCertAndKey.Verify()) |
| 646 | cer.FriendlyName = "VSTT"; | ||
| 647 | if (cer.Verify()) | ||
| 648 | { | 728 | { |
| 649 | if (pDebug) e.WebTest.AddCommentToResult("Certificate is TRUSTED"); | 729 | if (pDebug) e.WebTest.AddCommentToResult("Certificate is TRUSTED"); |
| 650 | myCertTrusted = true; | 730 | myCertTrusted = true; |
| ... | @@ -654,19 +734,20 @@ namespace LIL_VSTT_Plugins | ... | @@ -654,19 +734,20 @@ namespace LIL_VSTT_Plugins |
| 654 | myCertTrusted = false; | 734 | myCertTrusted = false; |
| 655 | } | 735 | } |
| 656 | 736 | ||
| 657 | if(cer.NotAfter < DateTime.Now) | 737 | // Check if it is expired or about to expire |
| 738 | if(myClientCertAndKey.NotAfter < DateTime.Now) | ||
| 658 | { | 739 | { |
| 659 | e.WebTest.AddCommentToResult("Warning: Client Certificate has expired. Might not be trusted on server. Expired " + cer.NotAfter.ToString()); | 740 | e.WebTest.AddCommentToResult("Warning: Client Certificate has expired. Might not be trusted on server. Expired " + myClientCertAndKey.NotAfter.ToString()); |
| 660 | } else if (cer.NotBefore > DateTime.Now) | 741 | } else if (myClientCertAndKey.NotBefore > DateTime.Now) |
| 661 | { | 742 | { |
| 662 | e.WebTest.AddCommentToResult("Warning: Client Certificate is not valid yet. Might not be trusted on server. Valid " + cer.NotBefore.ToString()); | 743 | e.WebTest.AddCommentToResult("Warning: Client Certificate is not valid yet. Might not be trusted on server. Valid " + myClientCertAndKey.NotBefore.ToString()); |
| 663 | } else if (cer.NotAfter < DateTime.Now.AddDays(14)) | 744 | } else if (myClientCertAndKey.NotAfter < DateTime.Now.AddDays(14)) |
| 664 | { | 745 | { |
| 665 | e.WebTest.AddCommentToResult("Warning: Client Certificate will expire in less than 14 days. Better renew it soon. Expires " + cer.NotAfter.ToString()); | 746 | e.WebTest.AddCommentToResult("Warning: Client Certificate will expire in less than 14 days. Better renew it soon. Expires " + myClientCertAndKey.NotAfter.ToString()); |
| 666 | } | 747 | } |
| 667 | 748 | ||
| 668 | // Check if we have a private key | 749 | // Check if we have a private key |
| 669 | if (!cer.HasPrivateKey) | 750 | if (!myClientCertAndKey.HasPrivateKey) |
| 670 | { | 751 | { |
| 671 | // Cant use it without private key | 752 | // Cant use it without private key |
| 672 | e.WebTest.AddCommentToResult("Error: Certificate HAS NO PRIVATE KEY, cannot use it without one."); | 753 | e.WebTest.AddCommentToResult("Error: Certificate HAS NO PRIVATE KEY, cannot use it without one."); |
| ... | @@ -679,8 +760,8 @@ namespace LIL_VSTT_Plugins | ... | @@ -679,8 +760,8 @@ namespace LIL_VSTT_Plugins |
| 679 | // Check that the certificate exists in the cert store | 760 | // Check that the certificate exists in the cert store |
| 680 | X509Store cuStore = new X509Store(); | 761 | X509Store cuStore = new X509Store(); |
| 681 | cuStore.Open(OpenFlags.ReadWrite); | 762 | cuStore.Open(OpenFlags.ReadWrite); |
| 682 | if(cuStore.Certificates.Contains(cer)) { | 763 | if(cuStore.Certificates.Contains(myClientCertAndKey)) { |
| 683 | if (pDebug) e.WebTest.AddCommentToResult("Certificate is INSTALLED"); | 764 | if (pDebug) e.WebTest.AddCommentToResult("Certificate already INSTALLED in Current User Windows Certificate Store"); |
| 684 | } else | 765 | } else |
| 685 | { | 766 | { |
| 686 | if (pDebug) e.WebTest.AddCommentToResult("Certificate is NOT INSTALLED"); | 767 | if (pDebug) e.WebTest.AddCommentToResult("Certificate is NOT INSTALLED"); |
| ... | @@ -691,11 +772,12 @@ namespace LIL_VSTT_Plugins | ... | @@ -691,11 +772,12 @@ namespace LIL_VSTT_Plugins |
| 691 | { | 772 | { |
| 692 | // Install in user store | 773 | // Install in user store |
| 693 | try { | 774 | try { |
| 694 | cuStore.Add(cer); | 775 | myClientCertAndKey.FriendlyName = "VSTT"; |
| 695 | if (pDebug) e.WebTest.AddCommentToResult("Certificate HAS BEEN INSTALLED in the Windows Certificate Store"); | 776 | cuStore.Add(myClientCertAndKey); |
| 777 | if (pDebug) e.WebTest.AddCommentToResult("Certificate HAS BEEN INSTALLED in the Current User Windows Certificate Store with Friendly Name: VSTT"); | ||
| 696 | } catch (Exception ex) | 778 | } catch (Exception ex) |
| 697 | { | 779 | { |
| 698 | e.WebTest.AddCommentToResult("Error: COULD NOT INSTALL in the Windows Certificate Store, Message: " + ex.Message); | 780 | e.WebTest.AddCommentToResult("Error: COULD NOT INSTALL in the Current User Windows Certificate Store, Message: " + ex.Message); |
| 699 | return; | 781 | return; |
| 700 | } | 782 | } |
| 701 | } | 783 | } |
| ... | @@ -724,6 +806,19 @@ namespace LIL_VSTT_Plugins | ... | @@ -724,6 +806,19 @@ namespace LIL_VSTT_Plugins |
| 724 | } | 806 | } |
| 725 | } | 807 | } |
| 726 | 808 | ||
| 809 | public class PasswordHelper : Org.BouncyCastle.OpenSsl.IPasswordFinder | ||
| 810 | { | ||
| 811 | private string pwd = ""; | ||
| 812 | public PasswordHelper(String password) | ||
| 813 | { | ||
| 814 | this.pwd = password; | ||
| 815 | } | ||
| 816 | public char[] GetPassword() | ||
| 817 | { | ||
| 818 | return pwd.ToArray(); | ||
| 819 | } | ||
| 820 | } | ||
| 821 | |||
| 727 | /* | 822 | /* |
| 728 | /// <summary> | 823 | /// <summary> |
| 729 | /// WebTest Plugin Template | 824 | /// WebTest Plugin Template | ... | ... |
Notes.md
0 → 100644
| ... | @@ -43,7 +43,10 @@ | ... | @@ -43,7 +43,10 @@ |
| 43 | </Request> | 43 | </Request> |
| 44 | </Items> | 44 | </Items> |
| 45 | <ContextParameters> | 45 | <ContextParameters> |
| 46 | <ContextParameter Name="CertFile" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\P12\Auth - FN10007 EN10007.p12" /> | 46 | <ContextParameter Name="PEM-Key" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\PEM\Auth_FN10007_EN10007-key.pem" /> |
| 47 | <ContextParameter Name="PEM-Cert" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\PEM\Auth_FN10007_EN10007-cert.pem" /> | ||
| 48 | <ContextParameter Name="PEM" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\PEM\Auth_FN10007_EN10007.pem" /> | ||
| 49 | <ContextParameter Name="PFX" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\P12\Auth - FN10007 EN10007.p12" /> | ||
| 47 | </ContextParameters> | 50 | </ContextParameters> |
| 48 | <ValidationRules> | 51 | <ValidationRules> |
| 49 | <ValidationRule Classname="Microsoft.VisualStudio.TestTools.WebTesting.Rules.ValidateResponseUrl, Microsoft.VisualStudio.QualityTools.WebTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" DisplayName="Response URL" Description="Validates that the response URL after redirects are followed is the same as the recorded response URL. QueryString parameters are ignored." Level="Low" ExectuionOrder="BeforeDependents" /> | 52 | <ValidationRule Classname="Microsoft.VisualStudio.TestTools.WebTesting.Rules.ValidateResponseUrl, Microsoft.VisualStudio.QualityTools.WebTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" DisplayName="Response URL" Description="Validates that the response URL after redirects are followed is the same as the recorded response URL. QueryString parameters are ignored." Level="Low" ExectuionOrder="BeforeDependents" /> |
| ... | @@ -68,10 +71,10 @@ | ... | @@ -68,10 +71,10 @@ |
| 68 | <WebTestPlugin Classname="LIL_VSTT_Plugins.ClientCertificatePlugin, LIL_VSTT_Plugins, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null" DisplayName="Client Certificate" Description="(C) Copyright 2016 LIGHTS IN LINE AB
Sätter webtestet att använda ett specifikt client cert för SSL. Certifikatet behöver inte installeras i certstore först."> | 71 | <WebTestPlugin Classname="LIL_VSTT_Plugins.ClientCertificatePlugin, LIL_VSTT_Plugins, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null" DisplayName="Client Certificate" Description="(C) Copyright 2016 LIGHTS IN LINE AB
Sätter webtestet att använda ett specifikt client cert för SSL. Certifikatet behöver inte installeras i certstore först."> |
| 69 | <RuleParameters> | 72 | <RuleParameters> |
| 70 | <RuleParameter Name="pCertificatePath" Value="" /> | 73 | <RuleParameter Name="pCertificatePath" Value="" /> |
| 71 | <RuleParameter Name="pCertificatePathParameter" Value="CertFile" /> | 74 | <RuleParameter Name="pCertificatePathParameter" Value="PEM" /> |
| 72 | <RuleParameter Name="pCertificatePassword" Value="abcd1234" /> | 75 | <RuleParameter Name="pCertificatePassword" Value="abcd1234" /> |
| 73 | <RuleParameter Name="pCertificatePasswordParameter" Value="" /> | 76 | <RuleParameter Name="pCertificatePasswordParameter" Value="" /> |
| 74 | <RuleParameter Name="pDebug" Value="False" /> | 77 | <RuleParameter Name="pDebug" Value="True" /> |
| 75 | <RuleParameter Name="pInstallTrusted" Value="True" /> | 78 | <RuleParameter Name="pInstallTrusted" Value="True" /> |
| 76 | <RuleParameter Name="pInstallUntrusted" Value="True" /> | 79 | <RuleParameter Name="pInstallUntrusted" Value="True" /> |
| 77 | </RuleParameters> | 80 | </RuleParameters> | ... | ... |
| 1 | #if !(NETCF_1_0 || SILVERLIGHT || PORTABLE) | ||
| 2 | |||
| 3 | using System; | 1 | using System; |
| 4 | using System.Security.Cryptography; | 2 | using System.Security.Cryptography; |
| 5 | using SystemX509 = System.Security.Cryptography.X509Certificates; | 3 | using SystemX509 = System.Security.Cryptography.X509Certificates; |
| ... | @@ -242,4 +240,3 @@ namespace Org.BouncyCastle.Security | ... | @@ -242,4 +240,3 @@ namespace Org.BouncyCastle.Security |
| 242 | } | 240 | } |
| 243 | } | 241 | } |
| 244 | 242 | ||
| 245 | #endif | ... | ... |
-
Please register or sign in to post a comment