GitHub - Eginn-33/csca-masterlist-tools: Parse an ICAO/BSI CSCA Master List (.ml) and export every Country Signing CA certificate — PEM bundle, TSV manifest, with an OpenSSL fallback for the certs strict ASN.1 parsers reject.

GitHub

4 min read Original article ↗

Parse an ICAO / BSI CSCA Master List (.ml) and export every Country Signing CA certificate inside it — as a PEM bundle, as a TSV manifest, or just as a count.

One file, one dependency, no framework.

python extract_masterlist.py DE_ML_2026-01-08.ml -o csca_bundle.pem -m manifest.tsv
master list : DE_ML_2026-01-08.ml (891,227 bytes)
entries     : 581
parsed      : 581   (openssl fallback rescued 6)
unparsable  : 0
unique      : 581
countries   : 112

top issuers : CN=26, HU=21, BE=19, LU=12, TR=12, LV=11, AU=10, GR=10, MT=10, NL=10

PEM bundle  -> csca_bundle.pem
manifest    -> manifest.tsv

Why this exists

A Master List is the trust anchor set for electronic passports: to validate the SOD on any eMRTD you need the issuing country's CSCA, and Master Lists are how states distribute them in bulk. The format is specified in ICAO Doc 9303 Part 12 and BSI TR-03129:

ContentInfo
 └─ SignedData
     └─ encapContentInfo.eContent  (OCTET STRING)
         └─ CscaMasterList ::= SEQUENCE {
                version   INTEGER,
                certList  SET OF Certificate
            }

That is not hard — but there is remarkably little public tooling that just opens the file and gives you the certificates. Most eMRTD code buries the parse inside a larger verification stack. This repo is the parse on its own.

The interesting part: six certificates Python won't read

Run against a real Master List with --strict and six entries drop on the floor:

entries     : 581
parsed      : 575
unparsable  : 6

All six fail inside cryptography's Rust ASN.1 parser with:

ParseError { kind: ExtraData, location: ["Certificate::tbs_cert",
                                         "TbsCertificate::signature_alg"] }

They are not junk. OpenSSL reads every one of them:

# Subject
48, 49 C=AT, O=GV, OU=BMI, CN=CSCA-AUSTRIA
61 C=AE, O=MOI, OU=EPASS, CN=UAE CSCA 02
84, 85, 90 C=JP, O=Japanese Government, OU=The Ministry of Foreign Affairs, CN=e-passportCSCA

These are live, government-issued CSCAs carrying trailing bytes in the signature AlgorithmIdentifier that a strict DER parser rejects. Austria, the UAE and Japan are not edge cases you get to skip — silently dropping them means passports from those countries fail validation with a confusing "unknown issuer" instead of a real error.

So the default path falls back to openssl x509 for anything cryptography refuses, and the manifest records which parser produced each row:

country  subject                      ...  parser
AT       CN=CSCA-AUSTRIA,OU=BMI,...   ...  openssl
AD       CN=CSCA-AND,OU=MJI,...       ...  cryptography

Use --strict if you want the lenient path off and the failures visible.

Install

pip install -r requirements.txt   # cryptography

openssl on PATH is optional; without it, --strict behaviour is the only behaviour and the six certificates above are reported as unparsable.

Usage

extract_masterlist.py MASTERLIST [-o BUNDLE.pem] [-m MANIFEST.tsv]
                                 [--keep-duplicates] [--strict]
flag effect
-o, --out write the concatenated PEM trust bundle
-m, --manifest write a TSV row per certificate (country, subject, serial, validity, sig alg, SHA-256, size, parser)
--keep-duplicates keep every entry; default de-duplicates by SHA-256
--strict no OpenSSL fallback

With no output flags it prints statistics and exits — useful for diffing two Master List releases.

See examples/manifest_sample.tsv for the manifest shape.

Where to get a Master List

  • ICAO PKDhttps://pkddownloadsg.icao.int/ (the authoritative source)
  • German BSI — publishes a national Master List that aggregates a large share of participating states

No Master List is bundled here, and .gitignore refuses to commit one. Fetch your own and keep it fresh: CSCAs roll over, and a stale trust store is how you end up rejecting valid documents.

Related projects

  • JMRTD — the long-running open-source eMRTD implementation (Java). Also publishes per-country CSCA certificates, useful when you want a handful of anchors rather than a whole Master List.
  • Kinegram eMRTD Connector — open-source client for a commercial chip-verification service.

Neither of them will hand you the certificates out of a .ml file, which is what this repo is for.

Scope

This tool reads public trust anchors — the certificates states publish specifically so that anyone can validate the passports they issue. It handles no private keys, no chip communication, and no passport data. It is the boring, public half of eMRTD work, which is exactly why it can be open.

License

MIT