Software
Türkçe okuFrom the ePassport Chip to the Digital Travel ID: NFC, Passive Authentication, and Data Normalization
Using the working Digital Travel prototype, we are examining ePassport chip access, the normalization of reader responses, the Passive Authentication chain of trust, and the correct architectural boundaries for generating Passport Credentials from verified data.
Digital Travel Architecture — Part 1
In most presentations, the contactless travel experience begins with the traveler’s digital ID on their phone or a facial recognition gate at the airport. However, the first critical point in the chain of trust lies further back: the accurate reading of the data within the physical ePassport, its cryptographic verification, and its transfer to the rest of the application as a controlled data model.
In this article, using the passport chip reading workflow in a working FastAPI prototype as a reference, we seek to answer the following question: How can the raw output from an ePassport reader be transformed into reliable input for a Passport Credential that will later be added to a digital wallet?
1. Defining the Architectural Boundary Correctly
In the demo application, the React interface sends the passport number, date of birth, and expiration date fields to the backend. The FastAPI layer converts these values into the format expected by the chip reader service, calls a local ePassport reading service, and normalizes the returned result.
React istemcisi
│ belge no + doğum tarihi + son geçerlilik tarihi
▼
FastAPI /issuer/passport-chip-read
│ alan dönüşümü ve servis çağrısı
▼
ePassport okuma servisi / NFC okuyucu
│ LDS veri grupları + EF.SOD + doğrulama sonucu
▼
Normalize edilmiş pasaport modeli
│
├── kullanıcıya gösterilecek biyografik alanlar
├── biyometrik kayıt için portre
└── credential issuance için güvenlik sonucu
This distinction is important. The FastAPI layer does not reimplement NFC protocols; it acts as an anti-corruption layer between the document reader integration and the digital travel app. It translates reader-specific field names, date formats, and error objects into a consistent contract that the rest of the application can understand.
2. Chip Access: Why Are Three Fields Necessary?
In the prototype, the user is prompted for the document number, date of birth, and expiration date. These three values are the primary inputs used to generate an MRZ-based access key. The code converts ISO dates into the six-digit YYMMDD format:
def to_chip_date(date_text: str) -> str:
for fmt in ("%d.%m.%Y", "%Y-%m-%d"):
try:
parsed = datetime.strptime(date_text, fmt)
return parsed.strftime("%y%m%d")
except ValueError:
continue
raise ValueError("Unsupported date format")
The demo code passes the unparseable value to the reader as-is. In a production system, this approach should be replaced by rejecting the request at the outer boundary. Otherwise, a format error appears as an access protocol error and compromises observability.
These fields do not validate the data on the chip. They merely provide the input necessary to establish access mechanisms such as BAC or PACE. Access control, establishing the communication channel, and Passive Authentication are distinct security controls.
3. Normalizing the Raw Reader Response
Reader services often return field names specific to the hardware manufacturer, library, or country. In the reference implementation Ad, Soyad, Uyruk, Belge_Numarasi, Dogum_Tarihi, IMAGE and passiveAuth the domains are mapped to a single Pydantic model.
class PassportChipNormalized(BaseModel):
given_name: str
family_name: str
nationality: str
passport_number: str
date_of_birth: str
expiry_date: str
place_of_birth: str | None = None
portrait_b64: str | None = None
passive_auth_ok: bool
passive_auth_error: str | None = None
The normalization layer offers three benefits:
- The frontend is not dependent on the reader manufacturer’s field names.
- The credential, check-in, and biometric services use the same canonical data model.
- When the reader changes, the transformation is updated only in the integration layer.
However, the raw returning the response and the Base64-encoded portrait to the client is not suitable for production. The raw response should be limited to the debug environment; the portrait should not be included in logs, browser state, or the general API response until its purpose, retention period, and access policy have been defined.
4. What does Passive Authentication actually prove?
According to ICAO, Passive Authentication verifies the integrity of the data on the ePassport chip and confirms that it originates from the issuing authority via a digital signature. The simplified chain is as follows:
Güvenilen CSCA sertifikası
│ doğrular
▼
Document Signer Certificate (DSC)
│ EF.SOD üzerindeki imzayı doğrular
▼
Document Security Object
│ beklenen hash değerlerini taşır
▼
DG1, DG2 ve okunan diğer Data Group'lar
The verification system first validates the signature within the EF.SOD using the Document Signer certificate. It then checks that the DS certificate is linked to the trusted national root certificate, the CSCA. Finally, the hash values of the read data groups are compared with the values in the EF.SOD. Current CRL information must also be part of the certificate status evaluation.
If this check is successful, the following two conclusions are supported:
- The read data is consistent with the document data signed by the authorized issuer.
- The relevant data group has not been altered since signing.
However, Passive Authentication alone does not prove that the chip is not a clone. ICAO explicitly states that detecting a counterfeit chip may require additional mechanisms, such as Active Authentication, Chip Authentication, or PACE Chip Authentication Mapping, depending on the supported document. Similarly, successful Passive Authentication does not prove that the person carrying the document is the same person as the one in the portrait; this requires a live face, a DG2 portrait, and an appropriate biometric comparison policy.
5. Why is a Boolean result insufficient?
If the reader lacks a reference code failureReason field is missing passive_auth_ok=True . While this approach is understandable for a demo, it can produce false positives in production. “Error field not received” is not the same as “all cryptographic checks were successfully completed.”
A more robust result model evaluates each step separately:
class PassiveAuthResult(BaseModel):
sod_signature_valid: bool
ds_certificate_valid: bool
csca_trust_anchor_found: bool
revocation_status: str
data_group_hashes: dict[str, bool]
overall_status: Literal["valid", "invalid", "indeterminate"]
failure_codes: list[str]
indeterminate which is particularly important. For example, failing to find a CSCA trust point is not the same operational outcome as the data set being tampered with. The former could be a trust store or certificate distribution issue; the latter could directly indicate an integrity breach. This distinction determines not only the automated decision but also the appropriate manual review queue.
6. When can a Passport Credential be generated?
In the prototype, the chip-reading endpoint and the endpoint that generates the Passport Credential are independent of each other. Since credential fields are submitted via the user form, a credential can technically be generated even before the chip is read or if Passive Authentication fails. This is the most significant distinction between the demo and a reliable production architecture.
In production, the issuance process must be tied to a short-lived read session verified on the server side, rather than to biographical fields resubmitted by the client:
chip_session = chip_sessions.get(request.chip_session_id)
if chip_session.passive_auth.overall_status != "valid":
raise IssuanceDenied("Chip authenticity is not established")
credential = issue_passport_credential(
subject=authenticated_wallet_holder,
claims=chip_session.verified_claims,
evidence={
"method": "eMRTD-passive-authentication",
"verified_at": chip_session.verified_at,
"reader_id": chip_session.reader_id,
},
)
In this model, the browser is not the authority that re-identifies the first and last name or document number. The client merely points to the verified session and the wallet to which the credential will be bound. The issuer retrieves the claims from its own trusted server state.
7. Security Checklist for Production
- The chip reader service must be protected not only by relying on the local address but also by mutual TLS and service identity.
- MRZ/CAN inputs must be masked in logs; portrait and raw LDS data must not be logged by default.
- CSCA, DSC, Master List, and CRL update processes must be traceable and versioned.
- Passive Authentication results must be stored with detailed error codes; failed and ambiguous cases must be distinguished.
- Credentials must be generated only through a verified chip session and after user/wallet authentication.
- The use of portraits must be governed by explicit consent, purpose limitation, and minimum retention and deletion policies.
- All calls between the reader, issuer, and wallet must be auditable using a correlation ID.
Conclusion
Reading the ePassport chip is a necessary but not sufficient step for generating a digital travel identity. A reliable architecture treats the access protocol, data normalization, the result of Passive Authentication, and the credential issuance decision as separate layers. The most critical design principle is this: The claims within the credential must come from a cryptographically verified document session, not from a browser form.
In the next part of this series, we will examine how this verified data can be converted into a Passport Credential; the Issuer–Holder–Verifier roles; a JWT-based demo approach; and the security controls required for transitioning to a true Verifiable Credential architecture.
Official Sources
How would you rate this article?
Your feedback helps improve future articles.