Wow. I had an adventure with certificates with an On Premise Service Bus installation. At first, I was using the “Auto Generate” option. However, I ran into an issue where I uninstalled Service Bus (but I left the certificates in the store), and tried to reinstall Service Bus (this time using the certificates that were already in the store. (I was basically pseudo testing disaster recovery).
(Side note : Disaster recovery is kinda talked about here : http://sharepoint-community.net/profiles/blogs/workflow-manager-disaster-recovery )
I have posted something about the error (reinstalling by reusing the same certificates) here: https://social.msdn.microsoft.com/Forums/azure/en-US/6ae07eb9-121a-4187-8181-e198490a530d/using-the-auto-generated-certificates-causes-bad-key-issue-if-the-certificates-are-used-again-on?forum=servbus
Basically, I was getting a “Bad Key” error.
So let’s review a little bit. The “Auto-Generate” option created certificates like this:
Certificate : IssuedTo: *Machine1.fullyqualified.domainname.com* Issued By: AppServerGeneratedSBCA Intended Purposes : Server Authentication
——–
Certificate : IssuedTo: AppServerGeneratedSBCA Issued By: AppServerGeneratedSBCA Intended Purposes : <All>
The most interesting part of the Auto-Generate option is the name of the “Service Authentication” certificate. *Machine1.fullyqualified.domainname.com* which reflects the first machine on which Service Bus was installed.
Then I documented where Microsoft (via the Service Bus installer) put these certficates:
Auto Created Certificates
Certificate : IssuedTo: *Machine1.fullyqualified.domainname.com* Issued By: AppServerGeneratedSBCA Intended Purposes : Server Authentication
On the “Server” aka “Machine1” (where you first installed Service Bus)
Cert Stores : (Personal) (This has the private key)
On “MachineN” (The ServiceBus “Add Farm” will auto-voodoo put the certs on MachineN)
Cert Stores : (Personal) (This has the private key)
On a “ClientMachine” (You must manually put the certs on this machine)
Cert Stores : (Personal) (No private key, aka public key)
——–
Certificate : IssuedTo: AppServerGeneratedSBCA Issued By: AppServerGeneratedSBCA Intended Purposes : <All>
On the “Server” aka “Machine1” (where you first installed Service Bus)
Cert Stores : (Personal, Trusted Root Certificate Authorities, Intermediate Certificate Authorities)….they are the same thumbprint) (This has the private key)
On “MachineN” (The ServiceBus “Add Farm” will auto-voodoo put the certs on MachineN)
Cert Stores : (Trusted Root Certificate Authorities) (This has the private key)
On a “ClientMachine” (You must manually put the certs on this machine)
Cert Stores : (Trusted Root Certificate Authorities) (No private key, aka public key)
So I started down the road of generating the certificates myself.
Using help I got from this article ( http://www.22bugs.co/post/sb-farm-errors-and-their-solutions/ ), I came up with this “.bat” file code to create the necessary certificates. (If you are quick-reading this blog entry, then the code below will NOT work for multiple-computing-nodes in the farm. Aka, don’t use this below code)
REM https://blogs.technet.microsoft.com/jhoward/2005/02/02/how-to-use-makecert-for-trusted-root-certification-authority-and-ssl-certificate-issuance/#comment-34025
set __rootDirectory=C:\LetsMakeSomeCerts\MakeCert\SelfSignedWithTrustedRootAuth\Output\
set __makecertExe=C:\LetsMakeSomeCerts\MakeCert\makecert.exe
set __pvk2pfxExe=C:\LetsMakeSomeCerts\MakeCert\PVK2PFX.exe
set __trustRootAuthorityName=MeAndMyselfTrustedRootAuthority
MD “%__rootDirectory%”
@ECHO OFF
ECHO Need to run As-Administrator to install certs in cert-store
FOR /f “tokens=2,* delims= ” %%a in (‘IPCONFIG ^/ALL ^| FINDSTR “Primary Dns”‘) do set tempsuffix=%%b
FOR /f “tokens=1,2 delims=:” %%a in (‘echo %tempsuffix%’) do set dnssuffix=%%b
SET __FQDN=%COMPUTERNAME%.%DNSSUFFIX:~1%
ECHO Server FQDN: %__FQDN%
%__makecertExe% -pe -r -n “CN=%__trustRootAuthorityName%” -ss my -sr LocalMachine -a sha1 -sky signature -b 10/01/2016 -e 12/31/9998 -sv “%__rootDirectory%%__trustRootAuthorityName%PrivateKeyfile.pvk” “%__rootDirectory%%__trustRootAuthorityName%.cer”
%__makecertExe% -pe -n “CN=%__FQDN%” -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in “%__trustRootAuthorityName%” -is MY -ir LocalMachine -sp “Microsoft RSA SChannel Cryptographic Provider” -sy 12 -b 10/01/2016 -e 12/31/9998 -sv “%__rootDirectory%%__FQDN%.pvk” “%__rootDirectory%%__FQDN%.cer
set __rootDirectory=
set __makecertExe=
set __pvk2pfxExe=
SET __FQDN=
set __trustRootAuthorityName=
pause
And that was working well, until I tried to add more computing-nodes to the farm. One reason you add more computing-nodes to the farm is so that if one node fails, the others will pick it up. Aka, High Availability.
So the setup looked like this:
First Machine with Service Bus : Machine1.fullyqualified.domainname.com
Second Machine with Service Bus : Machine2.fullyqualified.domainname.com
Well, then I “took down” the first machine, the “client” should be able to continue by communicating with Machine2.fullyqualified.domainname.com. Mine was failing. 😦
So after looking at the certificates that Auto-Generate created, I discovered that the “Subject Alternate Name” was set to
DNS Name=*.fullyqualified.domainname.com
and that was the secret that allowed the client to talk to the second (or the third, or the fourth or the fifth) machine in the farm.
But “makecert.exe” doesn’t support setting the “Subject Alternate Name”.
Side note, you can read about the “Subject Alternate Name” here :
https://www.digicert.com/subject-alternative-name.htm
Gaaaaaaaaaaaaaaaaaaaaaaaa! (That’s my version of a Homer Simpson “D’oh”)
So I couldn’t use Auto-Generate and “makecert.exe” couldn’t set (all of) the properties correctly.
So I tried to find some ways to create the certificates that supported “Subject Alternate Name”.
At first, I tried Mono.Security. And that looked promising. But I hit an issue that I logged here: ( https://stackoverflow.com/questions/40287336/mono-security-wont-set-multiple-keyusages )
So then I went to Bouncy Castle. And I was able to create some “rough code” to get the certificates that I needed.
So I am posting that code here, in the hopes it may help someone.
The code will mimic the certificates : “Machine1.fullyqualified.domainname.com” and “AppServerGeneratedSBCA”. Here the “AppServerGeneratedSBCA” is replaced by “BouncyCastleTrustedRootCertAuthority”. (You can call it whatever name you choose).
The Machine1.fullyqualified.domainname.com certficate will be of the “1.3.6.1.5.5.7.3.1” variety. I’ve also tried to mimic all of the key-usages that were in the original Microsoft “auto generated” certificates.
Which were:
(for “Machine1.fullyqualified.domainname.com”) : X509Extension.X509KeyUsageExtension.KeyUsages=’KeyEncipherment, DigitalSignature‘
and
(for “AppServerGeneratedSBCA”) (which will be “BouncyCastleTrustedRootCertAuthority”) :
X509Extension.X509KeyUsageExtension.KeyUsages=’CrlSign, KeyCertSign‘
And finally, here is the C# code.
using System;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
namespace ServiceBusCertificateMaker.BAL
{
/*
<?xml version=”1.0″ encoding=”utf-8″?>
<packages>
<package id=”BouncyCastle” version=”1.8.1″ targetFramework=”net45″ />
</packages>
*/
/* Notes, because this code actually places certificates IN YOUR CERTIFICATE STORE, it needs to be run “As Administrator” */
public class BouncyCastleMaker
{
public const string DefaultIssuer = “BouncyCastleTrustedRootCertAuthority”;
public void MakeItSo(string certificateName, List<string> subjectAlternateNames, string certificateFileName, string privateKeyFilePassword, string rootsigningCertFileName)
{
string issuerCnName = string.Format(“CN={0}”, DefaultIssuer);
AsymmetricKeyParameter caPrivKey = GenerateCACertificate(issuerCnName, privateKeyFilePassword, rootsigningCertFileName);
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = GenerateSelfSignedCertificate(certificateName, subjectAlternateNames, certificateFileName, privateKeyFilePassword, issuerCnName, caPrivKey);
AddCertToStore(cert, System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine);
ServiceBusSample.Shared.Showers.SecurityShower.ShowCertAndChain(cert);
}
public System.Security.Cryptography.X509Certificates.X509Certificate2 GenerateSelfSignedCertificate(string certificateName, List<string> subjectAlternateNames, string certificateFileName, string privateKeyFilePassword, string issuerName, AsymmetricKeyParameter issuerPrivKey)
{
return GenerateSelfSignedCertificate(certificateName, subjectAlternateNames, certificateFileName, privateKeyFilePassword, issuerName, issuerPrivKey, 2048);
}
public System.Security.Cryptography.X509Certificates.X509Certificate2 GenerateSelfSignedCertificate(string certificateName, List<string> subjectAlternateNames, string certificateFileName, string privateKeyFilePassword, string issuerName, AsymmetricKeyParameter issuerPrivKey, int keyStrength)
{
string subjectName = string.Format(“CN={0}”, certificateName);
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
// The Certificate Generator
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Signature Algorithm
const string SignatureAlgorithm = “SHA256WithRSA”;
certificateGenerator.SetSignatureAlgorithm(SignatureAlgorithm);
// Issuer and Subject Name
var subjectDN = new X509Name(subjectName);
// original code var issuerDN = issuerName;
var issuerDN = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(2);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
KeyUsage keyUsage = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment);
certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
// Add the “Extended Key Usage” attribute, specifying “server authentication”.
var usages = new[] { KeyPurposeID.IdKPServerAuth };
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(usages));
/* DNS Name=*.fullyqualified.domainname.com */
if (subjectAlternateNames.Count <= 1)
{
/* the <=1 is for the simple reason of showing an alternate syntax .. */
foreach (string subjectAlternateName in subjectAlternateNames)
{
GeneralName altName = new GeneralName(GeneralName.DnsName, subjectAlternateName);
GeneralNames subjectAltName = new GeneralNames(altName);
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
}
}
else
{
//Asn1Encodable[] ansiEncodeSubjectAlternativeNames = new Asn1Encodable[]
// {
// //new GeneralName(GeneralName.DnsName, “*.fullyqualified.domainname.com”),
// new GeneralName(GeneralName.DnsName, “*.fullyqualified.domainname.com”)
// };
List<Asn1Encodable> asn1EncodableList = new List<Asn1Encodable>();
foreach (string subjectAlternateName in subjectAlternateNames)
{
asn1EncodableList.Add(new GeneralName(GeneralName.DnsName, subjectAlternateName));
}
DerSequence subjectAlternativeNamesExtension = new DerSequence(asn1EncodableList.ToArray());
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
// Subject Public Key
AsymmetricCipherKeyPair subjectKeyPair;
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Generating the Certificate
var issuerKeyPair = subjectKeyPair;
// selfsign certificate
var certificate = certificateGenerator.Generate(issuerPrivKey, random);
// correcponding private key
PrivateKeyInfo pinfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
// merge into X509Certificate2
var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate.GetEncoded());
var seq = (Asn1Sequence)Asn1Object.FromByteArray(pinfo.PrivateKey.GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException(“malformed sequence in RSA private key”);
}
var rsa = new RsaPrivateKeyStructure(seq);
RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
File.WriteAllBytes(certificateFileName.Replace(“.pfx”, “.cer”), x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert));
// Export Certificate with private key
File.WriteAllBytes(certificateFileName, x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, privateKeyFilePassword));
return x509;
}
public AsymmetricKeyParameter GenerateCACertificate(string subjectName, string privateKeyFilePassword, string rootsigningCertFileName, int keyStrength = 2048)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Signature Algorithm
const string SignatureAlgorithm = “SHA256WithRSA”;
certificateGenerator.SetSignatureAlgorithm(SignatureAlgorithm);
// Issuer and Subject Name
var subjectDN = new X509Name(subjectName);
var issuerDN = subjectDN;
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(2);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
KeyUsage keyUsage = new KeyUsage(KeyUsage.KeyCertSign | KeyUsage.CrlSign);
certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
// Subject Public Key
AsymmetricCipherKeyPair subjectKeyPair;
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Generating the Certificate
var issuerKeyPair = subjectKeyPair;
// selfsign certificate
Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(issuerKeyPair.Private, random);
System.Security.Cryptography.X509Certificates.X509Certificate2 x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate.GetEncoded());
#region Private Key
// correcponding private key
PrivateKeyInfo pinfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence)Asn1Object.FromByteArray(pinfo.PrivateKey.GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException(“malformed sequence in RSA private key”);
}
var rsa = new RsaPrivateKeyStructure(seq);
RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
#endregion
// Add CA certificate to Root store
AddCertToStore(x509, System.Security.Cryptography.X509Certificates.StoreName.Root, System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine);
File.WriteAllBytes(rootsigningCertFileName.Replace(“.pfx”, “.cer”), x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert));
// Export Certificate with private key
File.WriteAllBytes(rootsigningCertFileName, x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, privateKeyFilePassword));
return issuerKeyPair.Private;
}
public bool AddCertToStore(System.Security.Cryptography.X509Certificates.X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
{
bool bRet = false;
try
{
System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl);
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite);
store.Add(cert);
store.Close();
}
catch
{
throw;
}
return bRet;
}
}
}
And then the code to call it, with specific parameter names.
private static void RunBouncyCastleMakerStuff()
{
string fullyQualifiedName = “StarDot.fullyqualified.domainname.com”;
List<string> subjectAlternateNames = new List<string>();
/* you can either add by wildcard */
//subjectAlternateNames.Add(“*.fullyqualified.domainname.com”); /* see https://www.digicert.com/subject-alternative-name.htm */
/* or you can add by machine names on the farm */
subjectAlternateNames.Add(“Machine1.fullyqualified.domainname.com”);
subjectAlternateNames.Add(“Machine2.fullyqualified.domainname.com”);
subjectAlternateNames.Add(“Machine3.fullyqualified.domainname.com”);
string privateKeyFilePassword = “MyPrivateKeyPa$$word”;
string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(“N”));
string certFileName = Path.Combine(rootFolder, fullyQualifiedName + “.pfx”);
string rootsigningCertFileName = Path.Combine(rootFolder, BouncyCastleMaker.DefaultIssuer + “.pfx”);
if(!Directory.Exists(rootFolder))
{
Directory.CreateDirectory(rootFolder);
}
if (System.IO.File.Exists(certFileName))
{
System.IO.File.Delete(certFileName);
}
if (System.IO.File.Exists(rootsigningCertFileName))
{
System.IO.File.Delete(rootsigningCertFileName);
}
new BouncyCastleMaker().MakeItSo(fullyQualifiedName, subjectAlternateNames, certFileName, privateKeyFilePassword, rootsigningCertFileName);
Process.Start(“explorer.exe”, rootFolder);
}
This will create the certificates:
Certificate : IssuedTo: *StarDot.fullyqualified.domainname.com* Issued By: BouncyCastleTrustedRootCertAuthority Intended Purposes : Server Authentication
Then you need to put the certificates in the stores of Machine1, Machine2-N, and “The Client”.
NOT Auto Created Certificates (Aka Custom Certificates)
Same as above “Auto Created Certificates” except
First: Manually Add all certificates listed above in “Auto Generate”.
ADDITIONALLY :
——–
Certificate : IssuedTo: BouncyCastleTrustedRootCertAuthority Issued By: BouncyCastleTrustedRootCertAuthority Intended Purposes : <All>
On “MachineN”
Cert Stores : Manually add (private-key-version) to Personal.
And there ya go.
I have tested installations, re-installs (pseudo testing disaster recovery), and that my client will keep processing, even if Machine1.fullyqualified.domainname.com “goes down”. (You can test this by stopping the Windows-Service “Service Bus Gateway” on Machine1.fullyqualified.domainname.com (or Machine2.fullyqualified.domainname.com or MachineN.fullyqualified.domainname.com, but stopping the service on “Machine1.fullyqualified.domainname.com” is the test that proves the certificate still work for the other computing-nodes)
While I haven’t shown the “client” code, the client-code is main-stream code using this package:
<?xml version=”1.0″ encoding=”utf-8″?>
<packages>
<package id=”Microsoft.WindowsAzure.ConfigurationManager” version=”2.0.1.0″ targetFramework=”net45″ />
<package id=”WindowsAzure.ServiceBus” version=”2.1.4.0″ targetFramework=”net45″ />
</packages>
with objects such as MessagingFactory, QueueClient.
Oh yeah, here is my “Certificate-Show-er” code. (Not shower, like bathing… 8) )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace ServiceBusSample.Shared.Showers
{
public static class SecurityShower
{
public static void ShowHttpWebRequest(System.Net.HttpWebRequest hwr)
{
StringBuilder sb = new StringBuilder();
if (null != hwr)
{
sb.Append(“———————————————–HttpWebRequest” + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.Address.AbsolutePath='{0}'”, hwr.Address.AbsolutePath) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.Address.AbsoluteUri='{0}'”, hwr.Address.AbsoluteUri) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.Address='{0}'”, hwr.Address) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.RequestUri.AbsolutePath='{0}'”, hwr.RequestUri.AbsolutePath) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.RequestUri.AbsoluteUri='{0}'”, hwr.RequestUri.AbsoluteUri) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.RequestUri='{0}'”, hwr.RequestUri) + System.Environment.NewLine);
foreach (X509Certificate cert in hwr.ClientCertificates)
{
ShowX509Certificate(sb, cert);
}
}
string result = sb.ToString();
Console.WriteLine(result);
}
public static void ShowCertAndChain(X509Certificate2 cert)
{
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
chain.Build(cert);
ShowCertAndChain(cert, chain);
}
public static void ShowCertAndChain(X509Certificate cert, X509Chain chain)
{
StringBuilder sb = new StringBuilder();
if (null != cert)
{
ShowX509Certificate(sb, cert);
}
if (null != chain)
{
sb.Append(“-X509Chain(Start)-” + System.Environment.NewLine);
////sb.Append(string.Format(“Cert.ChainStatus='{0}'”, string.Join(“,”, chain.ChainStatus.ToList())) + System.Environment.NewLine);
foreach (X509ChainStatus cstat in chain.ChainStatus)
{
sb.Append(string.Format(“X509ChainStatus::'{0}’-‘{1}'”, cstat.Status.ToString(), cstat.StatusInformation) + System.Environment.NewLine);
}
X509ChainElementCollection ces = chain.ChainElements;
ShowX509ChainElementCollection(sb, ces);
sb.Append(“-X509Chain(End)-” + System.Environment.NewLine);
}
string result = sb.ToString();
Console.WriteLine(result);
}
private static void ShowX509Extension(StringBuilder sb, int x509ExtensionCount, X509Extension ext)
{
sb.Append(string.Empty + System.Environment.NewLine);
sb.Append(string.Format(“——–X509ExtensionNumber(Start):{0}”, x509ExtensionCount) + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.Critical='{0}'”, ext.Critical) + System.Environment.NewLine);
AsnEncodedData asndata = new AsnEncodedData(ext.Oid, ext.RawData);
sb.Append(string.Format(“Extension type: {0}”, ext.Oid.FriendlyName) + System.Environment.NewLine);
sb.Append(string.Format(“Oid value: {0}”, asndata.Oid.Value) + System.Environment.NewLine);
sb.Append(string.Format(“Raw data length: {0} {1}”, asndata.RawData.Length, Environment.NewLine) + System.Environment.NewLine);
sb.Append(asndata.Format(true) + System.Environment.NewLine);
X509BasicConstraintsExtension basicEx = ext as X509BasicConstraintsExtension;
if (null != basicEx)
{
sb.Append(“-X509BasicConstraintsExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509BasicConstraintsExtension.CertificateAuthority='{0}'”, basicEx.CertificateAuthority) + System.Environment.NewLine);
}
X509EnhancedKeyUsageExtension keyEx = ext as X509EnhancedKeyUsageExtension;
if (null != keyEx)
{
sb.Append(“-X509EnhancedKeyUsageExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509EnhancedKeyUsageExtension.EnhancedKeyUsages='{0}'”, keyEx.EnhancedKeyUsages) + System.Environment.NewLine);
foreach (Oid oi in keyEx.EnhancedKeyUsages)
{
sb.Append(string.Format(“————EnhancedKeyUsages.Oid.FriendlyName='{0}'”, oi.FriendlyName) + System.Environment.NewLine);
sb.Append(string.Format(“————EnhancedKeyUsages.Oid.Value='{0}'”, oi.Value) + System.Environment.NewLine);
}
}
X509KeyUsageExtension usageEx = ext as X509KeyUsageExtension;
if (null != usageEx)
{
sb.Append(“-X509KeyUsageExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509KeyUsageExtension.KeyUsages='{0}'”, usageEx.KeyUsages) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.CrlSign='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.CrlSign) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.DataEncipherment='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.DataEncipherment) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.DecipherOnly='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.DecipherOnly) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.DigitalSignature='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.DigitalSignature) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.EncipherOnly='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.EncipherOnly) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.KeyAgreement='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.KeyAgreement) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.KeyCertSign='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.KeyCertSign) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.KeyEncipherment='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.KeyEncipherment) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.None='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.None) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.NonRepudiation='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.NonRepudiation) != 0) + System.Environment.NewLine);
}
X509SubjectKeyIdentifierExtension skIdEx = ext as X509SubjectKeyIdentifierExtension;
if (null != skIdEx)
{
sb.Append(“-X509SubjectKeyIdentifierExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509SubjectKeyIdentifierExtension.Oid='{0}'”, skIdEx.Oid) + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509SubjectKeyIdentifierExtension.SubjectKeyIdentifier='{0}'”, skIdEx.SubjectKeyIdentifier) + System.Environment.NewLine);
}
sb.Append(string.Format(“——–X509ExtensionNumber(End):{0}”, x509ExtensionCount) + System.Environment.NewLine);
}
private static void ShowX509Extensions(StringBuilder sb, string cert2SubjectName, X509ExtensionCollection extColl)
{
int x509ExtensionCount = 0;
sb.Append(string.Format(“——–ShowX509Extensions(Start):for:{0}”, cert2SubjectName) + System.Environment.NewLine);
foreach (X509Extension ext in extColl)
{
ShowX509Extension(sb, ++x509ExtensionCount, ext);
}
sb.Append(string.Format(“——–ShowX509Extensions(End):for:{0}”, cert2SubjectName) + System.Environment.NewLine);
}
private static void ShowX509Certificate2(StringBuilder sb, X509Certificate2 cert2)
{
if (null != cert2)
{
sb.Append(string.Format(“X509Certificate2.SubjectName.Name='{0}'”, cert2.SubjectName.Name) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.Subject='{0}'”, cert2.Subject) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.Thumbprint='{0}'”, cert2.Thumbprint) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.HasPrivateKey='{0}'”, cert2.HasPrivateKey) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.Version='{0}'”, cert2.Version) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.NotBefore='{0}'”, cert2.NotBefore) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.NotAfter='{0}'”, cert2.NotAfter) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.PublicKey.Key.KeySize='{0}'”, cert2.PublicKey.Key.KeySize) + System.Environment.NewLine);
////List<X509KeyUsageExtension> keyUsageExtensions = cert2.Extensions.OfType<X509KeyUsageExtension>().ToList();
////List<X509Extension> extensions = cert2.Extensions.OfType<X509Extension>().ToList();
ShowX509Extensions(sb, cert2.Subject, cert2.Extensions);
}
}
private static void ShowX509ChainElementCollection(StringBuilder sb, X509ChainElementCollection ces)
{
int x509ChainElementCount = 0;
foreach (X509ChainElement ce in ces)
{
sb.Append(string.Empty + System.Environment.NewLine);
sb.Append(string.Format(“—-X509ChainElementNumber:{0}”, ++x509ChainElementCount) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.SubjectName.Name='{0}'”, ce.Certificate.SubjectName.Name) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.Issuer='{0}'”, ce.Certificate.Issuer) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.Thumbprint='{0}'”, ce.Certificate.Thumbprint) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.HasPrivateKey='{0}'”, ce.Certificate.HasPrivateKey) + System.Environment.NewLine);
X509Certificate2 cert2 = ce.Certificate as X509Certificate2;
ShowX509Certificate2(sb, cert2);
ShowX509Extensions(sb, cert2.Subject, ce.Certificate.Extensions);
}
}
private static void ShowX509Certificate(StringBuilder sb, X509Certificate cert)
{
sb.Append(“———————————————–” + System.Environment.NewLine);
sb.Append(string.Format(“Cert.Subject='{0}'”, cert.Subject) + System.Environment.NewLine);
sb.Append(string.Format(“Cert.Issuer='{0}'”, cert.Issuer) + System.Environment.NewLine);
sb.Append(string.Format(“Cert.GetPublicKey().Length='{0}'”, cert.GetPublicKey().Length) + System.Environment.NewLine);
X509Certificate2 cert2 = cert as X509Certificate2;
ShowX509Certificate2(sb, cert2);
}
}
}
Last note: If you’re having certificate issues on “the client”, you can hook into this event below to see what is happening. This is how I originally figured out all the settings on the Auto-Generated certificates.
/* Use the below to debug failed verification. Remember that the Certificate ALTERNATE subject name comes into play in a High Availability scenario */
ServicePointManager.ServerCertificateValidationCallback =
new System.Net.Security.RemoteCertificateValidationCallback((
sender,
cert,
chain,
ssl) =>
{
Console.WriteLine(“ServerCertificateValidationCallback for Cert.Subject : ‘{0}'”, cert.Subject);
System.Net.HttpWebRequest hwr = sender as System.Net.HttpWebRequest;
if (null != hwr)
{
SecurityShower.ShowHttpWebRequest(hwr);
}
SecurityShower.ShowCertAndChain(cert, chain);
return true; /* return true here is ONLY FOR DEBUGGING */
});