Certificats : Public Key Thumbprint : dnQualifier

A ne pas le confondre avec Certificate Thumbprint,
Le dnQualifier est l'encodage en Base64 de la Public Key Thumbprint.

Préface

La Public Key Thumbprint est l'empreinte SHA1 du contenu de la Subject Public Key provenant du champ Subject Public Key Info du certificat, donc de la clef public.

dnQualifier est l'abréviation de Distinguished Name Qualifier.

Pour résumer :

SubjectPublicKey = PubKey Header DER ASN.1 + Modulus + Exponent PublicKeyThumbprint = SHA1 ( SubjectPublicKey ) dnQualifier = Base64 ( PublicKeyThumbprint )

Ainsi, lorsque nous allons créer le dnQualifier, on va procéder à un calcul d'empreinte sur la clef publique.

La partie que nous allons signer se trouve ici (Modulus + Exponent) :

openssl x509 -in certificate.pem -noout -text | grep -A19 Modulus
# Modulus:
    00:bc:62:a5:fb:33:4f:73:58:2a:6a:03:37:2b:52:
    62:4e:17:1a:41:6f:6e:f4:c3:98:b5:32:4c:4d:54:
    62:c4:e7:ee:e6:c1:88:39:80:05:7f:a1:66:49:fe:
    55:9d:e1:06:1b:db:5d:fe:23:7a:1a:99:48:d5:ee:
    6e:a5:3b:e4:cb:0d:42:4e:68:ae:02:9d:e9:a9:ca:
    8b:84:cf:2f:c8:f9:ed:ce:5a:08:09:33:71:7b:d0:
    08:8a:e3:a7:25:03:b5:92:12:c6:51:0d:69:80:3e:
    4c:be:97:f2:6b:fe:08:8a:a9:ea:f2:ea:51:f3:83:
    e8:2e:79:ec:10:ab:e8:c5:eb:97:6b:58:8b:ac:2c:
    83:67:29:0f:ed:86:e7:f1:4e:66:bb:37:40:aa:bf:
    58:46:e1:73:17:36:db:ab:c1:5c:af:3d:6e:3e:1c:
    8a:78:ad:22:eb:e6:50:55:d7:d3:f3:1c:b8:06:e3:
    46:41:cb:9c:8a:63:c6:a0:89:ae:fb:6c:9f:35:b1:
    1c:7c:54:1f:7c:30:39:6c:6c:d1:db:f5:c3:25:32:
    c4:6a:a3:70:7b:f5:97:67:21:a8:91:9b:48:47:39:
    85:25:81:9b:e1:9c:be:a5:81:96:20:ae:6e:9a:e8:
    63:34:51:13:b9:f3:48:e3:c9:b2:6c:d6:6d:7a:5a:
    63:23
# Exponent: 
    65537 (0x10001)

Les données "prêtes pour l'empreinte" ne sont pas complètes dans cet exemple, il nous faudra également la structure ASN.1 DER de la clef publique. Cet exemple intègre déjà quasiment 99% des données à utiliser pour l'empreinte et nous permet de mieux comprendre ce qu'on va utiliser comme données.

Récupérons la clef publique stockée dans le certificat :

openssl x509 -in certificate.pem -noout -pubkey
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvGKl+zNPc1gqagM3K1Ji
ThcaQW9u9MOYtTJMTVRixOfu5sGIOYAFf6FmSf5VneEGG9td/iN6GplI1e5upTvk
yw1CTmiuAp3pqcqLhM8vyPntzloICTNxe9AIiuOnJQO1khLGUQ1pgD5Mvpfya/4I
iqnq8upR84PoLnnsEKvoxeuXa1iLrCyDZykP7Ybn8U5muzdAqr9YRuFzFzbbq8Fc
rz1uPhyKeK0i6+ZQVdfT8xy4BuNGQcucimPGoImu+2yfNbEcfFQffDA5bGzR2/XD
JTLEaqNwe/WXZyGokZtIRzmFJYGb4Zy+pYGWIK5umuhjNFETufNI48mybNZtelpj
IwIDAQAB
-----END PUBLIC KEY-----

Cette sortie est au format ASN.1 + PKCS.1 wrappé dans du Base64. Ne prenons que la donnée utile (en violet) et nous allons procéder ainsi pour calculer le dnQualifier (SHA1+Base64 de la clef publique) :

Procédons :

#!/usr/bin/env python3
import hashlib
import base64

# PEM ASN.1
pubkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvGKl+zNPc1gqagM3K1Ji" \
         "ThcaQW9u9MOYtTJMTVRixOfu5sGIOYAFf6FmSf5VneEGG9td/iN6GplI1e5upTvk" \
         "yw1CTmiuAp3pqcqLhM8vyPntzloICTNxe9AIiuOnJQO1khLGUQ1pgD5Mvpfya/4I" \
         "iqnq8upR84PoLnnsEKvoxeuXa1iLrCyDZykP7Ybn8U5muzdAqr9YRuFzFzbbq8Fc" \
         "rz1uPhyKeK0i6+ZQVdfT8xy4BuNGQcucimPGoImu+2yfNbEcfFQffDA5bGzR2/XD" \
         "JTLEaqNwe/WXZyGokZtIRzmFJYGb4Zy+pYGWIK5umuhjNFETufNI48mybNZtelpj" \
         "IwIDAQAB"

# Unwrapping PEM ASN.1 (ascii) to DER ASN.1 (binary)
der = base64.b64decode(pubkey)
print("DER(1) : %s" % der.hex())

# Delete DER RSAEncryption DER ASN.1 Header (24 bytes)
der = der[24:]
print("DER(2) : %s" % der.hex())
# der contains now pubkey DER ASN.1 format without header

# Create SHA1 fingerprint from pubkey DER ASN.1 format
engine = hashlib.sha1()
engine.update(der)
sha1 = engine.digest()
print("SHA1 : %s" % sha1.hex())

# Wrap on base64
hash = base64.b64encode(sha1)

# That's it ! :)
print("dnQualifier = %s" % hash.decode('utf8'))

Et notre sortie est :

DER(1) : 30820122300d06092a864886f70d01010105000382010f003082010a0282010100bc62a5fb334f73582a6a03372b52624e171a416f6ef4c398b5324c4d5462c4e7eee6c1883980057fa16649fe559de1061bdb5dfe237a1a9948d5ee6ea53be4cb0d424e68ae029de9a9ca8b84cf2fc8f9edce5a080933717bd0088ae3a72503b59212c6510d69803e4cbe97f26bfe088aa9eaf2ea51f383e82e79ec10abe8c5eb976b588bac2c8367290fed86e7f14e66bb3740aabf5846e1731736dbabc15caf3d6e3e1c8a78ad22ebe65055d7d3f31cb806e34641cb9c8a63c6a089aefb6c9f35b11c7c541f7c30396c6cd1dbf5c32532c46aa3707bf5976721a8919b4847398525819be19cbea5819620ae6e9ae863345113b9f348e3c9b26cd66d7a5a63230203010001
DER(2) : 3082010a0282010100bc62a5fb334f73582a6a03372b52624e171a416f6ef4c398b5324c4d5462c4e7eee6c1883980057fa16649fe559de1061bdb5dfe237a1a9948d5ee6ea53be4cb0d424e68ae029de9a9ca8b84cf2fc8f9edce5a080933717bd0088ae3a72503b59212c6510d69803e4cbe97f26bfe088aa9eaf2ea51f383e82e79ec10abe8c5eb976b588bac2c8367290fed86e7f14e66bb3740aabf5846e1731736dbabc15caf3d6e3e1c8a78ad22ebe65055d7d3f31cb806e34641cb9c8a63c6a089aefb6c9f35b11c7c541f7c30396c6cd1dbf5c32532c46aa3707bf5976721a8919b4847398525819be19cbea5819620ae6e9ae863345113b9f348e3c9b26cd66d7a5a63230203010001
SHA1   : 490158492a96c2379fa69a9ab0c2667dd992ea52
dnQualifier = SQFYSSqWwjefppqasMJmfdmS6lI=

Maintenant, comparons avec notre dnQualifier présent dans le Subject de notre certificat :

openssl x509 -in certificate.pem -noout -subject
subject =
            O = DC2.SMPTE.DOREMILABS.COM,
           OU = DC.DOREMILABS.COM,
           CN = CS.DMSJP2K-80119.DC.DC2.SMPTE,
  dnQualifier = SQFYSSqWwjefppqasMJmfdmS6lI=

C'est donc le bon calcul :-)

Le SHA1 est notre Public Key Thumbprint, il sera utilisé notamment comme identifiant dans le certificat pour le champ x509v3 Subject Key Identifier.

Revenons sur notre output DER ASN.1 pour comprendre pourquoi nous écartons les 24 premiers octets :

30 82 0122   
^^^^^^^^^^--- DER tag (SEQUENCE) + SizeOfLength + Length

30 0d
^^^^^--- DER tag (SEQUENCE) + Length

06 09 2a864886f70d010101
^^^^^^^^^^^^^^^^^^^^^^^^--- DER tag (OBJECT_ID) + Length + OID rsaEncryption 1.2.840.113549.1.1.1 [notes-1]

05 00
^^^^^---- DER tag (NULL) + Length

03 82 010f 00
^^^^^^^^^^^^^--- DER tag (BIT STRING) + SizeOfLength + Length (+ Data ?)


30 82 010a 
^^^^^^^^^^---- DER tag (SEQUENCE) + SizeOfLength + Length

02 82 0101
^^^^^^^^^^---- DER tag (INTEGER)  [notes-1]

00bc62a5fb334f73582a6a03372b52624e171a416f6ef4c398b5
324c4d5462c4e7eee6c1883980057fa16649fe559de1061bdb5d
fe237a1a9948d5ee6ea53be4cb0d424e68ae029de9a9ca8b84cf
2fc8f9edce5a080933717bd0088ae3a72503b59212c6510d6980
3e4cbe97f26bfe088aa9eaf2ea51f383e82e79ec10abe8c5eb97
6b588bac2c8367290fed86e7f14e66bb3740aabf5846e1731736
dbabc15caf3d6e3e1c8a78ad22ebe65055d7d3f31cb806e34641
cb9c8a63c6a089aefb6c9f35b11c7c541f7c30396c6cd1dbf5c3
2532c46aa3707bf5976721a8919b4847398525819be19cbea581
9620ae6e9ae863345113b9f348e3c9b26cd66d7a5a6323
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---- PubKey: Modulus

02 03 
^^^^^---- DER tag (INTEGER)

010001
^^^^^^------ PubKey: Exponent

Pour créer notre dnQualifier, nous avons donc besoin de :

Rappelez-vous qu'il faut qu'on récupère le Subject Public Key de notre Subject Public Key Info. la définition normée dans la RFC3279 (et remaniée pour être plus lisible) :

SubjectPublicKeyInfo {
    AlgorithmIdentifier {
        algorithm  (rsaEncryption)
        parameters (optionel)             
    },
    subjectPublicKey {
        RSAPublicKey {
            modulus            INTEGER  -- n (2048 bits)
            publicExponent     INTEGER  -- e (24 bits)
        }
}

Récupération du Subject Public Key :

Pour rappel, la Subject Public Key est la clef publique du certificat avec son formatage ASN.1 DER. Ce n'est pas encore notre Public Key Thumbprint.

Mise en équation :

PublicKeyThumbprint = SHA1 ( SubjectPublicKey ) dnQualifier = Base64 ( PublicKeyThumbprint )

Voyons cela avec notre certificat :

$ openssl x509 -in certificate.pem -pubkey -noout \
    | openssl base64 -d \
    | openssl asn1parse -inform DER -offset 24 -noout -out - # récuperation DER PubKey \
    | xxd    # affichage

 offset       données au format hexadécimal           format string
-------------------------------------------------------------------------
00000000: 3082 010a 0282 0101 00bc 62a5 fb33 4f73   | 0.........b..3Os
00000010: 582a 6a03 372b 5262 4e17 1a41 6f6e f4c3   | X*j.7+RbN..Aon..
00000020: 98b5 324c 4d54 62c4 e7ee e6c1 8839 8005   | ..2LMTb......9..
00000030: 7fa1 6649 fe55 9de1 061b db5d fe23 7a1a   | ..fI.U.....].#z.
00000040: 9948 d5ee 6ea5 3be4 cb0d 424e 68ae 029d   | .H..n.;...BNh...
00000050: e9a9 ca8b 84cf 2fc8 f9ed ce5a 0809 3371   | ....../....Z..3q
00000060: 7bd0 088a e3a7 2503 b592 12c6 510d 6980   | {.....%.....Q.i.
00000070: 3e4c be97 f26b fe08 8aa9 eaf2 ea51 f383   | >L...k.......Q..
00000080: e82e 79ec 10ab e8c5 eb97 6b58 8bac 2c83   | ..y.......kX..,.
00000090: 6729 0fed 86e7 f14e 66bb 3740 aabf 5846   | g).....Nf.7@..XF
000000a0: e173 1736 dbab c15c af3d 6e3e 1c8a 78ad   | .s.6...\.=n>..x.
000000b0: 22eb e650 55d7 d3f3 1cb8 06e3 4641 cb9c   | "..PU.......FA..
000000c0: 8a63 c6a0 89ae fb6c 9f35 b11c 7c54 1f7c   | .c.....l.5..|T.|
000000d0: 3039 6c6c d1db f5c3 2532 c46a a370 7bf5   | 09ll....%2.j.p{.
000000e0: 9767 21a8 919b 4847 3985 2581 9be1 9cbe   | .g!...HG9.%.....
000000f0: a581 9620 ae6e 9ae8 6334 5113 b9f3 48e3   | ... .n..c4Q...H.
00000100: c9b2 6cd6 6d7a 5a63 2302 0301 0001        | ..l.mzZc#.....

Ceci est notre structure ASN.1 DER de notre Public Key. Elle commence par 0x3082 qui est l'entête ASN.1 DER (en jaune), suivi de notre clef publique (en vert) et de son exposant (en rouge).

Calculer un dnQualifier avec OpenSSL :

Mise en équation :

PublicKeyThumbprint = SHA1 ( SubjectPublicKey ) dnQualifier = Base64 ( PublicKeyThumbprint )

Avec OpenSSL :

$ openssl x509 -pubkey -noout -in certificate.pem \
    | openssl base64 -d \
    | openssl asn1parse -inform DER -offset 24 -noout -out - \   # skip RSAEncryptionHeader ASN.1
    | openssl sha1 -binary \
    | openssl base64
"SQFYSSqWwjefppqasMJmfdmS6lI="

Si vous utilisez OpenSSL en ligne de commande, ne pas oublier d'ajouter des blackslashs pour les signes + et / (inutile si vous passez par un fichier de configuration ou bien par du code ou librairies) :

     (...)
    | sed -e "s%/%\\\/%g" \
    | sed -e "s%+%\\\+%g"

Calculer un dnQualifier avec OpenSSL (2) :

Sans passer par l'extraction de pubkey et en récupérant directement la partie pubkey avec openssl asn1parse (ne pas utiliser, c'est juste pour le fun :)

openssl asn1parse -in certificate.pem -strparse 361 -noout -out - \
    | openssl sha1 -binary \
    | openssl base64
"SQFYSSqWwjefppqasMJmfdmS6lI="

Calculer un dnQualifier avec OpenSSL depuis la clef privée RSA :

Il faut la keypair (-- BEGIN PRIVATE KEY --) :

openssl rsa -in root.key -outform DER -pubout -out - 2> /dev/null \
    | openssl asn1parse -inform DER -offset 24 -noout -out - \
    | openssl sha1 -binary \
    | openssl base64
"SQFYSSqWwjefppqasMJmfdmS6lI="

Conclusion

Ne déconnez pas avec le calcul du dnQualifier ;-)

Chapitres annexes

Notes


  1. 5.4 Certificate and Public Key Thumbprint (...) the contents of the SubjectPublicKey (..) (excluding the DER tag, length, and number of unused bits count in the DER header for the BIT STRING) -- SMPTE 430-2 - Digital Certificate 2.1.11 - Public Key Thumbprint (...) Skip 24 bytes into the binary form of the public key (...) -- Digital Cinema System Specification: Compliance Test Plan 

    Un encodage DER d'un certificat débutera par les valeurs 0x30 0x82 2

    Notre entête RSA-2048 ASN.1 (RSAEncryptionHeader ASN.1, 24 octets) a comme valeur :

    0x30                                           = SEQUENCE (ASN.1)
    0x82                                           = Size of Length (2 bytes)
    0x01 0x22                                      = Length of Data (290 bytes)
    0x30                                           = SEQUENCE #2
    0x0d                                           = Size SEQ #2
    0x06 0x09                                      = OBJECT ID (tag)
    0x2a 0x86 0x48 0x86 0xf7 0x0d 0x01 0x01 0x01   = rsaEncryption  (http://oid-info.com/get/1.2.840.113549.1.1.1 + https://www.rfc-editor.org/rfc/rfc5698.html#page-23)
    0x05 0x00                                      = NULL (tag)
    0x03 0x82 0x01 0x0f 0x00                       = BIT STRING (tag)
    
  2. Pour être plus précis, ASN.1 respecte le TLV qui est ... comme un KLV ;-) 

    • TLV voulant dire Type (ou Tag suivant les inspirations des auteurs) Length Value,

    • KLV voulant dire Key Length Value.

    La seule différence entre les deux semble être sur la taille du Tag (1-4 bytes pour TLV, 1-16 pour KLV)

    Voici un exemple d'un TLV SEQUENCE d'un x509 :

      +------+
      |  30  | Tag SEQUENCE
      +------+
      |  82  | Code indiquant que les 2 prochains bytes seront la taille des données (voir KLV)
      +......+
      |  LL  |
      |  LL  |--- the length (ex: 047A : 1146 bytes)
      +------+
      |  DD  |
      |  DD  |___ Les données :)
      |  DD  |
      |  ..  |
      +------+
    

    C'est la même chose qu'un KLV, vous pouvez lire la documentation du KLV pour comprendre ainsi la structure binaire interne d'un certificat x509.