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.
4
stars
5
commits
Python
primary language
Aug 26, 2026
updated
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
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.
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.
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.
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.
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.
Neither of them will hand you the certificates out of a .ml file, which is
what this repo is for.
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.
MIT
5 commits
Python
100.0%
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.
4
stars
5
commits
Python
primary language
Aug 26, 2026
updated
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
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.
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.
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.
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.
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.
Neither of them will hand you the certificates out of a .ml file, which is
what this repo is for.
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.
MIT
5 commits
Python
100.0%