Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculate Public Key Pin (.Net)

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

like image 677
Jon Avatar asked Mar 12 '23 03:03

Jon


1 Answers

Short version

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

Functional .NET Fiddle

https://dotnetfiddle.net/F9t6IQ

Background

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;
    }
like image 56
Ian Boyd Avatar answered Mar 19 '23 04:03

Ian Boyd