En 2007, j'avais fait quelques expériences de lecture de
cartes d'identité belges avec un lecteur de cartes utilisant un ACR38 :
myACR38.blogspot.com... J'avais écrit un
petit programme en 'C' d'un peu plus de 300 lignes n'utilisant que la librairie usb pour extraire les données d'identité et la photo sous Linux. Aujourd'hui, je ne l'ai plus mais je peux utiliser le
Digipass 870 de
Vasco fourni par la banque Belfius pour effectuer des opérations bancaires. Les opérations bancaires
ne nécessitent pas que le lecteur soit connecté à un ordinateur mais le lecteur est équipé d'un port USB qui permet cette connexion (contrairement au lecteur de carte
Digipass 810 fourni par
Bpostbanque).
Curieusement, il n'est pas possible de se connecter à une administration belge sans que le lecteur de carte ne soit connecté à un ordinateur équipé d'un logiciel qui ne fonctionne qu'avec certains logiciels particuliers sur une sélection particulières de systèmes d'exploitation (
compatibilité Linux)... Par exemple, il n'est pas possible d'utiliser un vieil Ubuntu, un Ubuntu
trop récent ou un Raspberry Pi. On vous invite alors à partir des sources,
eid-mw-5.1.4-v5.1.4.tar.gz qui, bien sûr, nécessite un tas de dépendances qui en rendent la compilation fort hasardeuse.
Était-ce bien nécessaire? Je ne pense pas. Je ne suis pas un spécialiste en cryptographie mais c'est l'occasion de regarder d'un peu plus près ce qui se passe exactement avec cette carte d'identité électronique. On trouve assez bien d'informations. Par exemple, sur un
wiki de Philippe Teuwen qui réference une
présentation de Danny De Cock (pdf) contenant des informations techniques. On trouve également des choses sur
github.com/Fedict. Par exemple, le contenu de la carte :
Belgian Electronic Identity Card content v5_4.pdf. On voit que la carte respecte la norme de cartes électroniques
ISO-7816 et le standard de cryptographie
PKCS#15.
Sur Linux, on peut utiliser le package opensc pour lire le contenu de la carte (
sources sur github). Il y a deux 'applications'. L'une reprenant des données d'identité sous 'DF01' et l'autre, 'BELPIC' ('DF00'), la partie cryptographique. Dans chacune, il y a plusieurs fichiers. Parfois respectant un format 'BER' (
Basic Encoding Rules d'
ASN.1), parfois transparent comme, par exemple, pour la photo qui est un fichier
JPEG d'environ 4 kilo-octets (3?) d'une
définition de 140x200 pixels avec 16
niveaux de gris.
$ sudo apt-get install opensc
$ opensc-explorer <<END
cd df01
get 4031 4031-id_rn.bin
get 4032 4032-sgn_rn.bin
get 4033 4033-id_address.bin
get 4034 4034-sgn_rn.bin
get 4035 4035-id_photo.jpg
get 4038 4038-puk_7_ca.bin
get 4039 4039-pref.bin
cd ..
cd df00
get 5031 5031-odf.bin
get 5032 5032-token_info.bin
get 5034 5034-aodf.bin
get 5035 5035-pr_kdf.bin
get 5037 5037-cdf.bin
get 5038 5038-cert_2_auth.der
get 5039 5039-cert_3_sign.der
get 503a 503a-cert_4_ca.der
get 503b 503b-cert_6_root.der
get 503c 503c-cert_8_rn.der
END
Les certificats, en format DER, peuvent être affichés ou transformés en base64 avec
openssl(1) :
$ # display beid certificate
$ openssl x509 -in 5038-cert_2_auth.der -inform DER -text
$ # convert
$ openssl x509 -inform der -in cert.der -outform pem -out cert.pem
Avec opensc-explorer, on peut aussi effectuer l'opération MVP:VERIFY. C'est le clavier du lecteur de carte qui est utilisé:
$ opensc-explorer
OpenSC [3F00]> verify CHV1
Please enter PIN on the reader's pin pad.
Code correct.
Bizarrement, le code source de opensc-explorer:do_verify est complexe. Cela passe par sc_pin_cmd() et part10_build_verify_pin_block() pour utiliser l'API du lecteur de carte (...qui doit correspondre à un standard documenté quelque part) qui ne semble pas être piqué des vers. Et même pour un lecteur de carte sans clavier, ce n'est pas évident.
OpenSC/src/libopensc/sec.c nous apprend, à la ligne 269, fonction sc_build_pin(), que les 8 octets de data de l'apdu sont 24:nn:nn:ff:ff:ff:ff:ff:ff (!) pour un PIN de 4 chiffres. Par exemple, pour '1234', on aura 'apdu 00:20:00:01:08:24:12:34:ff:ff:ff:ff:ff'.
Avec un lecteur muni d'un clavier, je ne peux pas utiliser l'apdu MVP:VERIFY dans opensc-explorer apdu (ni en 'C' avec libpcsclite), le code de retour est 6985 et non 9000. Je peux néanmoins utiliser 'apdu' pour tester d'autres commandes après avoir utilisé la commande 'verify' de opensc-explorer.
OpenSC [3F00]> apdu 00:20:00:01:00 # MVP:VERIFY with 'lc' = 0
Sending: 00 20 00 01 00
Received (SW1=0x69, SW2=0x85)
Failure: Not allowed
OpenSC [3F00]> apdu 80:E6:00:00 # LOG OFF
Sending: 80 E6 00 00
Received (SW1=0x90, SW2=0x00)
Success!
OpenSC [3F00]> apdu 80:E4:00:00:1C # GET CARD DATA
Sending: 80 E4 00 00 1C
Received (SW1=0x90, SW2=0x00):
53 4C 47 90 61 28 00 00 2E CD 12 BF 12 92 61 54 SLG.a(........aT
F3 36 01 25 01 17 00 03 00 21 01 0F .6.%.....!..
Success!
Le fin du GET CARD DATA donne 17 (Applet Version 1.7), 0003 (Belpic V-1.7), 00 (interface version), 21 (PKCS#1 version 2.1), 0F (Personalized state). SW1=0x90 et SW2=0x00 signifient que la commande s'est terminée normalement... (On se demande bien ce que les gars qui développent ce genre de standard ont dans la tête...)
Préparons un petit copion pour effectuer des opérations avec opensc-explorer... D'abord, sélectionner la paire de clé et l'algorithme de signature. Il n'y a pas 36 possibilités : on peut exécuter 6 algorithme avec deux clés privées (la clé d'authentification et la clé de non-répudiation (signature)). Ensuite, appliquer l'algorithme sur un 'hash' dont la longueur dépend de l'algorithme (16, 20, 32 octets pour MD5, SHA1, SHA256). Ces 'hashes' peuvent être obtenus au moyen des commandes Linux md5sum(1), sha1sum(1) et sha256sum(1).
# MSE:SET Manage Security Environment : Set
# le dernier :82 ou :83 sélectionne la clé (Auth ou Sign)
# l'avant-pénultième octet sélectionne l'algorithme (en fait, la longueur des données à signer)
apdu 00:22:41:B6:05:04:80:01:84:82 # MSE:SET Auth - RSASSA-PKCS1
apdu 00:22:41:B6:05:04:80:02:84:82 # MSE:SET Auth - RSASSA-PKCS1-v1.5 SHA1
apdu 00:22:41:B6:05:04:80:04:84:82 # MSE:SET Auth - RSASSA-PKCS1-v1.5 MD5
apdu 00:22:41:B6:05:04:80:08:84:82 # MSE:SET Auth - RSASSA-PKCS1-v1.5 SHA256
apdu 00:22:41:B6:05:04:80:10:84:82 # MSE:SET Auth - RSASSA-PSS PKCS1-v2.1 SHA1
apdu 00:22:41:B6:05:04:80:20:84:82 # MSE:SET Auth - RSASSA-PSS PKCS1-v2.1 SHA256
apdu 00:22:41:B6:05:04:80:01:84:83 # MSE:SET Sign - RSASSA-PKCS1
apdu 00:22:41:B6:05:04:80:02:84:83 # MSE:SET Sign - RSASSA-PKCS1-v1.5 SHA1
apdu 00:22:41:B6:05:04:80:04:84:83 # MSE:SET Sign - RSASSA-PKCS1-v1.5 MD5
apdu 00:22:41:B6:05:04:80:08:84:83 # MSE:SET Sign - RSASSA-PKCS1-v1.5 SHA256
apdu 00:22:41:B6:05:04:80:10:84:83 # MSE:SET Sign - RSASSA-PSS PKCS1-v2.1 SHA1
apdu 00:22:41:B6:05:04:80:20:84:83 # MSE:SET Sign - RSASSA-PSS PKCS1-v2.1 SHA256
# PSO:CDS Perform Security Operation : Compute Digital Signature
apdu 00:2A:9E:9A:LL::00 # LL(MD5)=16; LL(SHA1)=20; LL(SHA256)=32
# test
apdu 00:22:41:B6:05:04:80:04:84:82 # MSE:SET Auth - RSASSA-PKCS1-v1.5 MD5
verify CHV1
apdu 00:2A:9E:9A:10:ed:9e:7e:69:f8:da:f8:8d:44:ef:78:30:41:42:1d:b6:00 # cal 2023 |md5sum|sed 's/../&:/g' -> ed:9e:7e...
# verify PIN=1234
apdu 00:20:00:01:08:24:12:34:ff:ff:ff:ff:ff
Utilisons maintenant ce copion avec la carte insérée dans le lecteur : choix de l'algorithme, introduction du PIN, demande de calcul de la
signature :
$ opensc-explorer
OpenSC Explorer version 0.17.0
Using reader with a card: VASCO DIGIPASS 870 [CCID] 00 00
OpenSC [3F00]> apdu 00:22:41:B6:05:04:80:04:84:82
Sending: 00 22 41 B6 05 04 80 04 84 82
Received (SW1=0x90, SW2=0x00)
Success!
OpenSC [3F00]> verify CHV1
Please enter PIN on the reader's pin pad.
Code correct.
OpenSC [3F00]> apdu 00:2A:9E:9A:10:ed:9e:7e:69:f8:da:f8:8d:44:ef:78:30:41:42:1d:b6:00
Sending: 00 2A 9E 9A 10 ED 9E 7E 69 F8 DA F8 8D 44 EF 78 30 41 42 1D B6 00
Received (SW1=0x90, SW2=0x00):
18 CA 07 F8 78 3C 12 37 DB 15 CB D9 30 2F 0E A4 ....x<.7....0/..
BB 59 AA 7B 1E 42 DA A3 B2 91 7F CE 2D 3F 3F 41 .Y.{.B......-??A
7D 13 2F 24 D9 58 43 E0 E1 30 99 44 D6 AD 4D C5 }./$.XC..0.D..M.
08 B9 66 BA 31 75 29 8E 9B 75 7A 4A CA 36 0A BB ..f.1u)..uzJ.6..
E3 C9 12 31 78 12 7E F9 4D BA 34 27 82 8E 12 E3 ...1x.~.M.4'....
D8 BD 21 1A F6 3D 38 D2 F0 35 C0 C6 B6 7C AD B2 ..!..=8..5...|..
DE 6E 87 1A DB 78 E7 85 84 BF D1 9F 65 31 7E 68 .n...x......e1~h
1B F9 34 A1 7F 2E 9F A3 9E 9D B1 A2 F1 31 8E E3 ..4..........1..
45 D6 4E 71 7F E2 26 9A 7B F7 C4 2A 04 5E 7E EB E.Nq..&.{..*.^~.
E7 EA 50 76 C1 C0 AF C9 52 AC A2 9F AD 9F D4 5D ..Pv....R......]
C5 0D 52 20 77 BD 2B 5D 22 52 57 91 73 D0 8A 96 ..R w.+]"RW.s...
F1 38 A3 B7 6D 49 7B A0 91 91 A9 8C 20 43 83 69 .8..mI{..... C.i
18 B4 3B 8A AA 85 33 D4 50 09 6A 1F 3D 16 6B 33 ..;...3.P.j.=.k3
61 9A F0 A3 98 4F C7 FC 73 91 6C 66 08 1A B6 FF a....O..s.lf....
5C E2 F1 06 5B FE 32 F9 61 DF F9 1B 3E 85 53 7C \...[.2.a...>.S|
08 89 CF 65 58 75 BD C5 09 7A 6E 26 48 51 D7 36 ...eXu...zn&HQ.6
Success!
OpenSC [3F00]>
Bingo! Cela fonctionne comme prévu. Donc, concrètement, rien de bien compliqué. On peut se demander pourquoi il faut triturer la documentation dans tous les sens pour obtenir ce résultat. Cela ressemble fort à de l'
offuscation...
Reste à vérifier que l'on peut confirmer avec le certificat contenant la clé publique correspondante que le 'hash' a bien été signé par la clé privée...
D'abord, transformer le certificat de non-repudiation .der en .pem
$ openssl x509 -inform der -in 5039-cert_3_sign.der \
-outform pem -out 5039-cert_3_sign.pem
En extraire la clé publique
$ openssl x509 -noout -pubkey -in 5039-cert_3_sign.pem \
-out cert_3_sign-pubkey.pem
Enfin, vérifier la signature
$ openssl pkeyutl -verify \
-in cal2023.sha256 \
-inkey cert_3_sign-pubkey.pem \
-sigfile cal2023-sha256.sig \
-pubin \
-pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha256
Signature Verified Successfully
cal2023.sha256 est un fichier binaire contenant le résultat de 'cal 2023 | sha256sum'.
cal2023-sha256.sig est la signature avec l'algorithme 0x20 du hash sha256 sous forme de fichier binaire de 256 octets.
cert_3_sign-pubkey.pem est la clé publique extraite du certificat.
Les fichiers binaires ont été obtenu par un petit programme en 'C' interagissant avec la carte en utilisant la libpcsclite (voir
https://github.com/xofc/my_beid).
À suivre...