diff --git a/CSCA/CNlist b/CSCA/CNlist index 1464a87..10e1ecd 100644 --- a/CSCA/CNlist +++ b/CSCA/CNlist @@ -1,9 +1,9 @@ dn: cn=CN\=United Nations CSCA\,OU\=Certification Authorities\,O\=United Nat CMS extraction: 536150 bytes in - 532482 bytes out CMS Verification failure -802BE9F64F7F0000:error:02000068:rsa routines:ossl_rsa_verify:bad signature:../crypto/rsa/rsa_sign.c:430: -802BE9F64F7F0000:error:1C880004:Provider routines:rsa_verify:RSA lib:../providers/implementations/signature/rsa_sig.c:774: -802BE9F64F7F0000:error:1700009E:CMS routines:CMS_SignerInfo_verify:verification failure:../crypto/cms/cms_sd.c:899: +80DB01C6DF7F0000:error:02000068:rsa routines:ossl_rsa_verify:bad signature:../crypto/rsa/rsa_sign.c:430: +80DB01C6DF7F0000:error:1C880004:Provider routines:rsa_verify:RSA lib:../providers/implementations/signature/rsa_sig.c:774: +80DB01C6DF7F0000:error:1700009E:CMS routines:CMS_SignerInfo_verify:verification failure:../crypto/cms/cms_sd.c:899: 355 certificates are on the list... 10077 diff --git a/README.md b/README.md index 02c5420..c76b1c9 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,138 @@ The meaning of the following line options can be deduced from respective man pag # verifying the signature of the body with the public key : openssl dgst -sha256 -verify cs.pkey -signature ds.sig ds.body Verified OK -#### TODO -- Read NFC to extend the trust chain onto document's data e.g., DG1 (hashed MRZ) +#### Understanding MRTD Secure Object (SOD) Structure +At a time of the writing [D-Logic Reader](https://www.d-logic.com/nfc-rfid-reader-sdk/software/epassport-reading-machine-readable-travel-documents-mrtd/) can read SOD from LDS v1.7 (see [Appendix D here](https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf)) but not from contemporary LDS v1.8 ([para 4.6.2](https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf)). Hence, an example in `sod-example` folder concerns the older LDS v1.7. -Proud users of [D-Logic Readers](https://www.d-logic.com/nfc-rfid-reader-sdk/software/epassport-reading-machine-readable-travel-documents-mrtd/) or equivalent are sought - your help would be appreciated! +NFC read SOD object is present as binary file `sod`. We first strip SOD header with tag byte 0x77 to get more convenient PKCS7/CMS format: + + # strip SOD header : + openssl asn1parse -inform der -in sod -strparse 4 -noout -out pkcs7 +This format represents cryptographic message syntax (CMS), where the message.. + + openssl cms -inform der -noverify -verify -in pkcs7 -out message + openssl asn1parse -inform der -in message # datagroup hashes : +is a signed list of sha1 hashes for all data groups (DG) the document contains. The first command extracting the `message` binary file emits `CMS Verification successful`, confirming that the message is signed by the document signer (DS) certificate. We extract DS certificate and its parametrized public key like so: + + # extract document signer (ds), assuming no country signer (cs) in SOD : + openssl pkcs7 -inform der -print_certs -in pkcs7 |grep -A99 CERT >ds.pem + openssl x509 -in ds.pem -pubkey -noout >ds.pkey +Note, that a digest being signed is calculated over a structure called `SignedAttrs` from [CMS RFC](https://www.rfc-editor.org/rfc/rfc5652#section-5.4). Then, a signature of that structure is appended to the very end of `pkcs7` file, provided that `unsignedAttrs` optional field is missing as per Table 37 of [ICAO framework](https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf). Let's examine layout in question: + + openssl cms -cmsout -print -inform der -in pkcs7 |tail -28 +-- out : + + digestAlgorithm: + algorithm: sha1 (1.3.14.3.2.26) + parameter: + signedAttrs: + object: contentType (1.2.840.113549.1.9.3) + set: + OBJECT:undefined (2.23.136.1.1.1) + + object: signingTime (1.2.840.113549.1.9.5) + set: + UTCTIME:May 21 03:08:11 2015 GMT + + object: messageDigest (1.2.840.113549.1.9.4) + set: + OCTET STRING: + 0000 - fa f7 c7 ec 04 f8 f8 44-7b 5b 82 a5 ab .......D{[... + 000d - de 6c c7 92 91 1b a9 .l..... + signatureAlgorithm: + algorithm: ecdsa-with-SHA1 (1.2.840.10045.4.1) + parameter: + signature: + 0000 - 30 44 02 20 4d b0 0b 91-68 57 93 51 0f 96 f6 0D. M...hW.Q... + 000f - a5 62 07 b7 00 c1 bc 30-27 d6 88 05 76 18 1c .b.....0'...v.. + 001e - e7 5d 6f 28 14 92 02 20-10 e4 1f 3d 02 e8 ed .]o(... ...=... + 002d - 41 60 be 5a 57 6f 9f da-de bd 2e 93 5f 2d fe A`.ZWo......_-. + 003c - 8a e6 9b af a2 02 10 58-7b 14 .......X{. + unsignedAttrs: + +Signed bytes here: OID `2.23.136.1.1.1`, which is undefined in CMS but defined as [LdsSecurityObject](https://oidref.com/2.23.136.1.1.1) from ICAO; time stamp when signing took place - here it predates passport issuance; message digest, algorithm of which is given right at the beginning of the snippet (sha1). And since we already have the "message" extracted, let's verify: + + sha1sum message + faf7c7ec04f8f8447b5b82a5abde6cc792911ba9 message + +Magic. Note, that `signatureAlgorithm` employs sha1 as well. Generally, hashing `message` and hashing `signedAttrs` could use different algorithms. Now, let's extract `signedAttrs` and `signature`. We do so by means of ASN1 parser. Although it is terser than the CMS parsing above, it provides byte offsets we need for extraction: + + openssl asn1parse -i -inform der -in pkcs7 |tail -18 + + 1137:d=5 hl=2 l= 7 cons: SEQUENCE + 1139:d=6 hl=2 l= 5 prim: OBJECT :sha1 + 1146:d=5 hl=2 l= 90 cons: cont [ 0 ] + 1148:d=6 hl=2 l= 21 cons: SEQUENCE + 1150:d=7 hl=2 l= 9 prim: OBJECT :contentType + 1161:d=7 hl=2 l= 8 cons: SET + 1163:d=8 hl=2 l= 6 prim: OBJECT :2.23.136.1.1.1 + 1171:d=6 hl=2 l= 28 cons: SEQUENCE + 1173:d=7 hl=2 l= 9 prim: OBJECT :signingTime + 1184:d=7 hl=2 l= 15 cons: SET + 1186:d=8 hl=2 l= 13 prim: UTCTIME :150521030811Z + 1201:d=6 hl=2 l= 35 cons: SEQUENCE + 1203:d=7 hl=2 l= 9 prim: OBJECT :messageDigest + 1214:d=7 hl=2 l= 22 cons: SET + 1216:d=8 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:FAF7C7EC04F8F8447B5B82A5ABDE6CC792911BA9 + 1238:d=5 hl=2 l= 9 cons: SEQUENCE + 1240:d=6 hl=2 l= 7 prim: OBJECT :ecdsa-with-SHA1 + 1249:d=5 hl=2 l= 70 prim: OCTET STRING [HEX DUMP]:304402204DB00B91685793510F96F6A56207B700C1BC3027D6880576181CE75D6F281492022010E41F3D02E8ED4160BE5A576F9FDADEBD2E935F2DFE8AE69BAFA20210587B14 + +Note a block at offset 1146 and 90 bytes in length (`l= 90`). Parameter `d=` for depth shows level of indentation in pkcs7. Knowing these couple of things, we can be sure that the block is actually `signedAttrs`. Similarly, the last one is the sought signature. Extracting both: + + openssl asn1parse -inform der -in pkcs7 -strparse 1146 -out attrs + openssl asn1parse -inform der -in pkcs7 -strparse$(openssl asn1parse -inform der -in pkcs7 |awk -F: '{x=$1}END{print x}') -out sod.sig + +Changing the first byte of `attrs` extracted from 0xA0 to 0x31. Some [details on the trick](https://stackoverflow.com/a/24581628/2550808). + + # what we had : + xxd attrs |head -1 + 00000000: a05a 3015 0609 2a86 4886 f70d 0109 0331 .Z0...*.H......1 + + xxd attrs >temp + vi temp # first byte a0 -> 31 + cat temp |xxd -r >attrsExplicit + + # what we use : + xxd attrsExplicit |head -1 + 00000000: 315a 3015 0609 2a86 4886 f70d 0109 0331 1Z0...*.H......1 + +We can now verify the signature like so.. + + openssl dgst -sha1 -verify ds.pkey -signature sod.sig attrsExplicit + Verified OK +However, we'd better compress it all down to a hash being signed, that is + + sha1sum attrsExplicit |awk '{print$1}' |xxd -r -ps >sod.hash +Time to recap. Passport's DG1 (ordinary passport data) and DG2 (face-photo file) are both hashed, and the hashes are injected in a `message` (hash algorithm is there too). The `message` is hashed, and the hash is injected into a `signedAttrs` (the algo is in pkcs7). The `signedAttrs` is hashed (the algo is in pkcs7) and we have it in `sod.hash` file (20 bytes' token in case of sha1). + +The token is uniquely derived from some personal data. As such, it can serve as a personal identifier, albeit with multiple levels of indirection. The token is non-fudgeable (aka NFT) in a sense that it is signed by a State (as opposed to recording on a decentralized blockchain), so it cannot be altered if accompanied by the signature (70 bytes in `sod.sig` file) and the final ingredient - reference to the public key - another 20 bytes of `Subject Key Identifier` from the signing certificate: + + openssl x509 -in ds.pem -noout -text |grep -A1 X509 + + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature + X509v3 Subject Key Identifier: + 07:10:06:8A:48:58:FA:04:58:08:8C:47:67:99:BA:1D:5F:EB:2C:3F + X509v3 Authority Key Identifier: + 56:59:99:89:A1:CC:1C:13:D3:9F:BC:B0:C8:77:00:38:50:33:A5:33 + +As all certificates are supposed to be publicly available at ICAO PKI, anyone could download this certificate, extract public key, and verify the "NFT" like so: + + openssl pkeyutl -verify -sigfile sod.sig -pubin -inkey ds.pkey -in sod.hash + Signature Verified Successfully + +The problem is that this particular document signer isn't on ICAO PKI: + + python3 icao-dsprobe.py icaopkd-001-dsccrl-005973.ldif 07:10:06:8A:48:58:FA:04:58:08:8C:47:67:99:BA:1D:5F:EB:2C:3F + + 17763 inspected, 0 certificate(s) dumped in DSCA/0710068A4858FA0458088C476799BA1D5FEB2C3F folder + +Authority certificate, on the other hand, is on ICAO Master list. So, our "NFT" would have to comprise 20 bytes token + 70 bytes signature + 844 byes document signer (whole certificate): + + openssl x509 -in ds.pem -outform der -out ds.der + +Not very elegant. +#### Future? +It is interesting to notice that conventional identification relies on subjective (latterly AI-based) matching between a state issued document and a personal look (generally - biometric scans). That would be comparable if not inferior to a cryptographic ["limited knowledge" proof](https://en.wikipedia.org/wiki/Zero-knowledge_proof) of possession of a signed message (e.g., State-signed identifier). The latter would not require biometric scans, however, which makes it suitable for paperless remote identification. An abstract problem statement - [here](https://crypto.stackexchange.com/q/102705/104362). diff --git a/sod-example/attrs b/sod-example/attrs new file mode 100644 index 0000000..996e1a3 --- /dev/null +++ b/sod-example/attrs @@ -0,0 +1 @@ +Z0 *H  1g0 *H  1 150521030811Z0# *H  1D{[lǒ \ No newline at end of file diff --git a/sod-example/attrsExplicit b/sod-example/attrsExplicit new file mode 100644 index 0000000..0bdca4a --- /dev/null +++ b/sod-example/attrsExplicit @@ -0,0 +1 @@ +1Z0 *H  1g0 *H  1 150521030811Z0# *H  1D{[lǒ \ No newline at end of file diff --git a/sod-example/cs.pem b/sod-example/cs.pem new file mode 100644 index 0000000..d1d45c8 --- /dev/null +++ b/sod-example/cs.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEKDCCA8+gAwIBAgIJANwdrlnS2BZbMAkGByqGSM49BAEwgZQxCzAJBgNVBAYT +AlJVMRswGQYDVQQIExJSdXNzaWFuIEZlZGVyYXRpb24xDzANBgNVBAcTBk1vc2Nv +dzESMBAGA1UEChMJU1RDIEF0bGFzMQwwCgYDVQQLEwNTWkQxFDASBgNVBAMTC0NT +Q0EtUnVzc2lhMR8wHQYJKoZIhvcNAQkBFhBjYW1haWxAc3RjbmV0LnJ1MB4XDTEw +MDIwNTA4MzE0NVoXDTMyMDEzMTA4MzE0NVowgZQxCzAJBgNVBAYTAlJVMRswGQYD +VQQIExJSdXNzaWFuIEZlZGVyYXRpb24xDzANBgNVBAcTBk1vc2NvdzESMBAGA1UE +ChMJU1RDIEF0bGFzMQwwCgYDVQQLEwNTWkQxFDASBgNVBAMTC0NTQ0EtUnVzc2lh +MR8wHQYJKoZIhvcNAQkBFhBjYW1haWxAc3RjbmV0LnJ1MIIBSzCCAQMGByqGSM49 +AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////// +////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXY +qjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVBMSdNgiG5wSTamZ44ROdJreB +n36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n +60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxee +hPO5ysL8YyVRAgEBA0IABFgXbfjKUQhG14XIHLlpseIqYugSweVrgQWnbbfvlGX6 +pcBrLM/JoDcAOi4WlkILJGGU49ybVJ+83B/vr4TSAnejggETMIIBDzAdBgNVHQ4E +FgQUVlmZiaHMHBPTn7ywyHcAOFAzpTMwgckGA1UdIwSBwTCBvoAUVlmZiaHMHBPT +n7ywyHcAOFAzpTOhgZqkgZcwgZQxCzAJBgNVBAYTAlJVMRswGQYDVQQIExJSdXNz +aWFuIEZlZGVyYXRpb24xDzANBgNVBAcTBk1vc2NvdzESMBAGA1UEChMJU1RDIEF0 +bGFzMQwwCgYDVQQLEwNTWkQxFDASBgNVBAMTC0NTQ0EtUnVzc2lhMR8wHQYJKoZI +hvcNAQkBFhBjYW1haWxAc3RjbmV0LnJ1ggkA3B2uWdLYFlswEgYDVR0TAQH/BAgw +BgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwCQYHKoZIzj0EAQNIADBFAiAEo0jligNV +t8laftPk8x/toeyQyaGBfJHBxXOFNl/UEAIhAPsznr12itghRhgyEE8AROl+WsIT +eOG4EX0HFk0NZXfT +-----END CERTIFICATE----- diff --git a/sod-example/ds.der b/sod-example/ds.der new file mode 100644 index 0000000..2153323 Binary files /dev/null and b/sod-example/ds.der differ diff --git a/sod-example/ds.pem b/sod-example/ds.pem new file mode 100644 index 0000000..7e5e080 --- /dev/null +++ b/sod-example/ds.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDSDCCAvCgAwIBAgIBTTAJBgcqhkjOPQQBMIGUMQswCQYDVQQGEwJSVTEbMBkG +A1UECBMSUnVzc2lhbiBGZWRlcmF0aW9uMQ8wDQYDVQQHEwZNb3Njb3cxEjAQBgNV +BAoTCVNUQyBBdGxhczEMMAoGA1UECxMDU1pEMRQwEgYDVQQDEwtDU0NBLVJ1c3Np +YTEfMB0GCSqGSIb3DQEJARYQY2FtYWlsQHN0Y25ldC5ydTAeFw0xNTAyMDMxMTA2 +MTJaFw0yNzAxMzExMTA2MTJaMIGAMQswCQYDVQQGEwJSVTEPMA0GA1UEBwwGTW9z +Y293MRIwEAYDVQQKDAlTVEMgQXRsYXMxDTALBgNVBAsMBFVaSVMxHDAaBgNVBAMM +E0RvY3VtZW50IFNpZ25lciAzLjIxHzAdBgkqhkiG9w0BCQEWEGNhbWFpbEBzdGNu +ZXQucnUwggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAA +AAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAA +AAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBL +AxUExJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3r +M6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA//// +/wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAERB1rAeLm0VFpXKjU +5kDqddxpHnFLt6tbh3xJrIT7nE9N+ygZy3gQkp1uPSSvE3t55i5xfx8t3nP/aXf9 +bpnky6NSMFAwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBQHEAaKSFj6BFgIjEdn +mbodX+ssPzAfBgNVHSMEGDAWgBRWWZmJocwcE9OfvLDIdwA4UDOlMzAJBgcqhkjO +PQQBA0cAMEQCIHUaHEmkfqCrtaoVVnfLl81M4M6Dc/+ArIwbZxrn48EWAiAUp7NM +lGLzPN4E1YFZ5MEFFX6bkCr/fVl/uUvs3FU9yA== +-----END CERTIFICATE----- + diff --git a/sod-example/ds.pkey b/sod-example/ds.pkey new file mode 100644 index 0000000..a8ce7b7 --- /dev/null +++ b/sod-example/ds.pkey @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA +AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// +///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd +NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 +RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA +//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABEQdawHi5tFRaVyo1OZA6nXc +aR5xS7erW4d8SayE+5xPTfsoGct4EJKdbj0krxN7eeYucX8fLd5z/2l3/W6Z5Ms= +-----END PUBLIC KEY----- diff --git a/sod-example/message b/sod-example/message new file mode 100644 index 0000000..ced3340 Binary files /dev/null and b/sod-example/message differ diff --git a/sod-example/pkcs7 b/sod-example/pkcs7 new file mode 100644 index 0000000..d2e83f8 Binary files /dev/null and b/sod-example/pkcs7 differ diff --git a/sod-example/sod b/sod-example/sod new file mode 100755 index 0000000..63a5ec3 Binary files /dev/null and b/sod-example/sod differ diff --git a/sod-example/sod.hash b/sod-example/sod.hash new file mode 100644 index 0000000..bfcc43a --- /dev/null +++ b/sod-example/sod.hash @@ -0,0 +1 @@ +638[=_-kc \ No newline at end of file diff --git a/sod-example/sod.sig b/sod-example/sod.sig new file mode 100644 index 0000000..3f8505d Binary files /dev/null and b/sod-example/sod.sig differ diff --git a/sod-example/temp b/sod-example/temp new file mode 100644 index 0000000..c62afbd --- /dev/null +++ b/sod-example/temp @@ -0,0 +1,6 @@ +00000000: 315a 3015 0609 2a86 4886 f70d 0109 0331 .Z0...*.H......1 +00000010: 0806 0667 8108 0101 0130 1c06 092a 8648 ...g.....0...*.H +00000020: 86f7 0d01 0905 310f 170d 3135 3035 3231 ......1...150521 +00000030: 3033 3038 3131 5a30 2306 092a 8648 86f7 030811Z0#..*.H.. +00000040: 0d01 0904 3116 0414 faf7 c7ec 04f8 f844 ....1..........D +00000050: 7b5b 82a5 abde 6cc7 9291 1ba9 {[....l.....