I wondered how to calculate the public key pin of an X509 certificate?
For example. I've got a certificate via a web request
var cert = (httpRequest as HttpWebRequest).ServicePoint.Certificate;
X509Certificate2 cert2 = new X509Certificate2(cert);
I'm not sure what to do after this as I need the Subject Public Key Info (to hash it) but I can't find it on the X509Certificate2 class or sure how to construct it. I can get the exponent and modulus via the GetKeyInfo() method which seems to be the guts of the SPKI.
I'm sure there is an easy way to do this but any help would be great!
Thanks
Helper method:
String publicKeyPinningHash = certificate.GetPublicKeyPinningHash();
String s = String.Format("Public-Key-Pins: pin-sha256=\"{0}\";
max-age=31536000", publicKeyHash);
Which for Facebook.com spits out:
Public-Key-Pins: pin-sha256="hUIG87ch71EZQYhZBEkq2VKBLjhussUw7nR8wyuY7rY="; max-age=31536000
https://dotnetfiddle.net/F9t6IQ
As you've discovered, the .NET Framework has no capability to manipulate an X509Certificate.
X.509 certificates are encoded using the DER flavor of ASN.1. The .NET Framework has no capability to manipulate AsnEcodedData.
This means we're left to roll our own code to calculate the PublicKeyPinning hash of an X.509 certificate:
static String GetPublicKeyPinningHash(X509Certificate2 x509Cert)
{
//Public Domain: No attribution required
//Get the SubjectPublicKeyInfo member of the certificate
Byte[] subjectPublicKeyInfo = GetSubjectPublicKeyInfoRaw(x509Cert);
//Take the SHA2-256 hash of the DER ASN.1 encoded value
Byte[] digest;
using (var sha2 = new SHA256Managed())
{
digest = sha2.ComputeHash(subjectPublicKeyInfo);
}
//Convert hash to base64
String hash = Convert.ToBase64String(digest);
return hash;
}
This code is built on the lower function, that .NET also doesn't provide, to get the raw SubjectPublickeyInfo bytes:
static Byte[] GetSubjectPublicKeyInfoRaw(X509Certificate2 x509Cert)
{
//Public Domain: No attribution required
Byte[] rawCert = x509Cert.GetRawCertData();
/*
Certificate is, by definition:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3
extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version MUST be v3
}
So we walk to ASN.1 DER tree in order to drill down to the SubjectPublicKeyInfo item
*/
Byte[] list = AsnNext(ref rawCert, true); //unwrap certificate sequence
Byte[] tbsCertificate = AsnNext(ref list, false); //get next item; which is tbsCertificate
list = AsnNext(ref tbsCertificate, true); //unwap tbsCertificate sequence
Byte[] version = AsnNext(ref list, false); //tbsCertificate.Version
Byte[] serialNumber = AsnNext(ref list, false); //tbsCertificate.SerialNumber
Byte[] signature = AsnNext(ref list, false); //tbsCertificate.Signature
Byte[] issuer = AsnNext(ref list, false); //tbsCertificate.Issuer
Byte[] validity = AsnNext(ref list, false); //tbsCertificate.Validity
Byte[] subject = AsnNext(ref list, false); //tbsCertificate.Subject
Byte[] subjectPublicKeyInfo = AsnNext(ref list, false); //tbsCertificate.SubjectPublicKeyInfo
return subjectPublicKeyInfo;
}
Which is built on the lower level function to parse ASN.1 encoded data that was encoded using the DER flavor of ASN.1:
static Byte[] AsnNext(ref Byte[] buffer, Boolean unwrap)
{
//Public Domain: No attribution required
Byte[] result;
if (buffer.Length < 2)
{
result = buffer;
buffer = new Byte[0];
return result;
}
int index = 0;
Byte entityType = buffer[index];
index += 1;
int length = buffer[index];
index += 1;
int lengthBytes = 1;
if (length >= 0x80)
{
lengthBytes = length & 0x0F; //low nibble is number of length bytes to follow
length = 0;
for (int i = 0; i < lengthBytes; i++)
{
length = (length << 8) + (int)buffer[2 + i];
index += 1;
}
lengthBytes++;
}
int copyStart;
int copyLength;
if (unwrap)
{
copyStart = 1 + lengthBytes;
copyLength = length;
}
else
{
copyStart = 0;
copyLength = 1 + lengthBytes + length;
}
result = new Byte[copyLength];
Array.Copy(buffer, copyStart, result, 0, copyLength);
Byte[] remaining = new Byte[buffer.Length - (copyStart+copyLength)];
if (remaining.Length > 0)
Array.Copy(buffer, copyStart + copyLength, remaining, 0, remaining.Length);
buffer = remaining;
return result;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With