Commit 3ca6e994 3ca6e994c9235320670887b15d9228cc5db26f32 by Christian Gerdes

Fungerande PEM inläsning, behöver dock testas så att privata nyckeln i cert stor…

…e faktiskt går att använda efter installation.
1 parent 9e479a05
...@@ -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
620 // Check what type of container we have. All files are treated as PEM unless the extension is .pfx or .p12
621 // Read certificate and private key depending on type
622 if (certPath.ToLower().EndsWith(".pfx") || certPath.ToLower().EndsWith(".p12"))
623 {
624 if (pDebug) e.WebTest.AddCommentToResult("Certificate file is treated as PFX/PKCS12");
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;
619 try { 641 try {
620 myClientCert = new X509Certificate(certPath, certPass); 642 text = File.ReadAllText(certPath);
621 } catch (Exception ex) 643 } catch (Exception ex)
622 { 644 {
623 e.WebTest.AddCommentToResult("Error during loading of certificate: " + certPath + " Message: " + ex.Message); 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");
624 return; 693 return;
625 } 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 }
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
......
1 # Notes on Bouncy Castle integration
2
3 Once we have a certificate, it can be converted to .NET:
4
5 DotNetUtilities.ToX509Certificate((Org.BouncyCastle.X509.X509Certificate)newCert)
6
7 http://stackoverflow.com/questions/6128541/bouncycastle-privatekey-to-x509certificate2-privatekey
8
...@@ -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&#xD;&#xA;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&#xD;&#xA;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
......