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.....