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
LICENSE = LICENSE
LIL_VSTT_Plugins.vsmdi = LIL_VSTT_Plugins.vsmdi
Local.testsettings = Local.testsettings
Notes.md = Notes.md
README.md = README.md
EndProjectSection
EndProject
......
......@@ -55,6 +55,12 @@
<ItemGroup>
<None Include="Registry\Schannel_high_withclient.reg" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\crypto\crypto.Net45.csproj">
<Project>{45473847-8af8-4baf-b768-442c6875b8cf}</Project>
<Name>crypto.Net45</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
......
......@@ -549,12 +549,12 @@ namespace LIL_VSTT_Plugins
public class ClientCertificatePlugin : WebTestPlugin
{
[DisplayName("Certificate Path")]
[Description("Sökvägen till certifikatfilen (P12/PFX med privat nyckel)")]
[Description("Sökvägen till certifikatfilen (.P12/.PFX/.PEM med privat nyckel)")]
[DefaultValue("")]
public string pCertificatePath { get; set; }
[DisplayName("Certificate Path Parameter")]
[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")]
[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")]
[DefaultValue("")]
public string pCertificatePathParameter { get; set; }
......@@ -585,6 +585,7 @@ namespace LIL_VSTT_Plugins
private bool haveCert = false;
private X509Certificate myClientCert;
private X509Certificate2 myClientCertAndKey;
public override void PreWebTest(object sender, PreWebTestEventArgs e)
{
......@@ -616,35 +617,114 @@ namespace LIL_VSTT_Plugins
return;
}
// Check what type of container we have. All files are treated as PEM unless the extension is .pfx or .p12
// Read certificate and private key depending on type
if (certPath.ToLower().EndsWith(".pfx") || certPath.ToLower().EndsWith(".p12"))
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate file is treated as PFX/PKCS12");
try
{
myClientCertAndKey = new X509Certificate2(certPath, certPass, X509KeyStorageFlags.PersistKeySet);
myClientCert = new X509Certificate(myClientCertAndKey);
}
catch (Exception ex)
{
e.WebTest.AddCommentToResult("Error during loading of certificate: " + certPath + " Message: " + ex.Message);
return;
}
} else
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate file is treated as PEM/PKCS8");
// Use Bouncy Castle to read the certificate and key, then convert to .NET X509Certificate2 and X509Certificate
String text;
try {
myClientCert = new X509Certificate(certPath, certPass);
text = File.ReadAllText(certPath);
} catch (Exception ex)
{
e.WebTest.AddCommentToResult("Error during loading of certificate: " + certPath + " Message: " + ex.Message);
e.WebTest.AddCommentToResult("Error opening PEM file: " + certPath + " Message: " + ex.Message);
return;
}
// Find the key and certificate in file and load them
int keyTextBeginPos = text.IndexOf("-----BEGIN");
int keyTextEndPos = text.IndexOf("-----END");
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair bcKey = null;
Org.BouncyCastle.X509.X509Certificate bcCert = null;
while (keyTextBeginPos != -1)
{
text = text.Substring(keyTextBeginPos);
object obj;
try
{
Org.BouncyCastle.OpenSsl.PemReader bc = new Org.BouncyCastle.OpenSsl.PemReader(new StringReader(text), new PasswordHelper(certPass));
obj = bc.ReadObject();
}
catch (Org.BouncyCastle.Crypto.InvalidCipherTextException ex)
{
e.WebTest.AddCommentToResult("Error during reading of PEM file: " + certPath + " Wrong password? Message: " + ex.Message);
return;
}
catch (Exception ex)
{
e.WebTest.AddCommentToResult("Error during reading of PEM file: " + certPath + " Message: " + ex.GetType().FullName + ":" + ex.Message);
return;
}
if (obj != null)
{
if (pDebug) e.WebTest.AddCommentToResult("Found PEM Object of type " + obj.GetType().FullName);
if(obj is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)
{
// We have a keypair
bcKey = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)obj;
}
if(obj is Org.BouncyCastle.X509.X509Certificate)
{
// We have a certificate
bcCert = (Org.BouncyCastle.X509.X509Certificate)obj;
}
}
keyTextBeginPos = text.IndexOf("-----BEGIN", keyTextEndPos);
}
if (bcKey == null || bcCert == null)
{
e.WebTest.AddCommentToResult("Error: PEM file has to contain both certificate and private key");
return;
}
try {
myClientCert = Org.BouncyCastle.Security.DotNetUtilities.ToX509Certificate(bcCert);
myClientCertAndKey = new X509Certificate2(myClientCert);
myClientCertAndKey.PrivateKey = Org.BouncyCastle.Security.DotNetUtilities.ToRSA(bcKey.Private as Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters);
} catch (Exception ex)
{
e.WebTest.AddCommentToResult("Error during loading of PEM file: " + certPath + " Message: " + ex.Message);
return;
}
}
if(myClientCert == null) {
// Check that we have a certificate
if (myClientCert == null)
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " could not be loaded.");
return;
} else
}
else
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath);
if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " loaded successfully.");
}
// Check that it seems okey
if (string.IsNullOrWhiteSpace(myClientCert.GetCertHashString()))
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate File " + certPath + " contains no SHA1 hash. Not using it.");
return;
}
if (pDebug) e.WebTest.AddCommentToResult("Loaded client certificate for Subject: [" + myClientCert.Subject + "] Issued by: [" + myClientCert.Issuer + "] Expires: [" + myClientCert.GetExpirationDateString() + "]");
// Check if the certificate is trusted (i.e. chain can be validated)
bool myCertTrusted = false;
X509Certificate2 cer = new X509Certificate2(certPath, certPass, X509KeyStorageFlags.PersistKeySet);
cer.FriendlyName = "VSTT";
if (cer.Verify())
if (myClientCertAndKey.Verify())
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate is TRUSTED");
myCertTrusted = true;
......@@ -654,19 +734,20 @@ namespace LIL_VSTT_Plugins
myCertTrusted = false;
}
if(cer.NotAfter < DateTime.Now)
// Check if it is expired or about to expire
if(myClientCertAndKey.NotAfter < DateTime.Now)
{
e.WebTest.AddCommentToResult("Warning: Client Certificate has expired. Might not be trusted on server. Expired " + cer.NotAfter.ToString());
} else if (cer.NotBefore > DateTime.Now)
e.WebTest.AddCommentToResult("Warning: Client Certificate has expired. Might not be trusted on server. Expired " + myClientCertAndKey.NotAfter.ToString());
} else if (myClientCertAndKey.NotBefore > DateTime.Now)
{
e.WebTest.AddCommentToResult("Warning: Client Certificate is not valid yet. Might not be trusted on server. Valid " + cer.NotBefore.ToString());
} else if (cer.NotAfter < DateTime.Now.AddDays(14))
e.WebTest.AddCommentToResult("Warning: Client Certificate is not valid yet. Might not be trusted on server. Valid " + myClientCertAndKey.NotBefore.ToString());
} else if (myClientCertAndKey.NotAfter < DateTime.Now.AddDays(14))
{
e.WebTest.AddCommentToResult("Warning: Client Certificate will expire in less than 14 days. Better renew it soon. Expires " + cer.NotAfter.ToString());
e.WebTest.AddCommentToResult("Warning: Client Certificate will expire in less than 14 days. Better renew it soon. Expires " + myClientCertAndKey.NotAfter.ToString());
}
// Check if we have a private key
if (!cer.HasPrivateKey)
if (!myClientCertAndKey.HasPrivateKey)
{
// Cant use it without private key
e.WebTest.AddCommentToResult("Error: Certificate HAS NO PRIVATE KEY, cannot use it without one.");
......@@ -679,8 +760,8 @@ namespace LIL_VSTT_Plugins
// Check that the certificate exists in the cert store
X509Store cuStore = new X509Store();
cuStore.Open(OpenFlags.ReadWrite);
if(cuStore.Certificates.Contains(cer)) {
if (pDebug) e.WebTest.AddCommentToResult("Certificate is INSTALLED");
if(cuStore.Certificates.Contains(myClientCertAndKey)) {
if (pDebug) e.WebTest.AddCommentToResult("Certificate already INSTALLED in Current User Windows Certificate Store");
} else
{
if (pDebug) e.WebTest.AddCommentToResult("Certificate is NOT INSTALLED");
......@@ -691,11 +772,12 @@ namespace LIL_VSTT_Plugins
{
// Install in user store
try {
cuStore.Add(cer);
if (pDebug) e.WebTest.AddCommentToResult("Certificate HAS BEEN INSTALLED in the Windows Certificate Store");
myClientCertAndKey.FriendlyName = "VSTT";
cuStore.Add(myClientCertAndKey);
if (pDebug) e.WebTest.AddCommentToResult("Certificate HAS BEEN INSTALLED in the Current User Windows Certificate Store with Friendly Name: VSTT");
} catch (Exception ex)
{
e.WebTest.AddCommentToResult("Error: COULD NOT INSTALL in the Windows Certificate Store, Message: " + ex.Message);
e.WebTest.AddCommentToResult("Error: COULD NOT INSTALL in the Current User Windows Certificate Store, Message: " + ex.Message);
return;
}
}
......@@ -724,6 +806,19 @@ namespace LIL_VSTT_Plugins
}
}
public class PasswordHelper : Org.BouncyCastle.OpenSsl.IPasswordFinder
{
private string pwd = "";
public PasswordHelper(String password)
{
this.pwd = password;
}
public char[] GetPassword()
{
return pwd.ToArray();
}
}
/*
/// <summary>
/// WebTest Plugin Template
......
# Notes on Bouncy Castle integration
Once we have a certificate, it can be converted to .NET:
DotNetUtilities.ToX509Certificate((Org.BouncyCastle.X509.X509Certificate)newCert)
http://stackoverflow.com/questions/6128541/bouncycastle-privatekey-to-x509certificate2-privatekey
......@@ -43,7 +43,10 @@
</Request>
</Items>
<ContextParameters>
<ContextParameter Name="CertFile" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\P12\Auth - FN10007 EN10007.p12" />
<ContextParameter Name="PEM-Key" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\PEM\Auth_FN10007_EN10007-key.pem" />
<ContextParameter Name="PEM-Cert" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\PEM\Auth_FN10007_EN10007-cert.pem" />
<ContextParameter Name="PEM" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\PEM\Auth_FN10007_EN10007.pem" />
<ContextParameter Name="PFX" Value="U:\projekt\MjukaCertifikat\Interna certifikat_2016\P12\Auth - FN10007 EN10007.p12" />
</ContextParameters>
<ValidationRules>
<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 @@
<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.">
<RuleParameters>
<RuleParameter Name="pCertificatePath" Value="" />
<RuleParameter Name="pCertificatePathParameter" Value="CertFile" />
<RuleParameter Name="pCertificatePathParameter" Value="PEM" />
<RuleParameter Name="pCertificatePassword" Value="abcd1234" />
<RuleParameter Name="pCertificatePasswordParameter" Value="" />
<RuleParameter Name="pDebug" Value="False" />
<RuleParameter Name="pDebug" Value="True" />
<RuleParameter Name="pInstallTrusted" Value="True" />
<RuleParameter Name="pInstallUntrusted" Value="True" />
</RuleParameters>
......
#if !(NETCF_1_0 || SILVERLIGHT || PORTABLE)
using System;
using System.Security.Cryptography;
using SystemX509 = System.Security.Cryptography.X509Certificates;
......@@ -242,4 +240,3 @@ namespace Org.BouncyCastle.Security
}
}
#endif
......