Software
Türkçe okueMRTD Passive Authentication: How Is a DS Certificate Paired with a CSCA?
We provide a step-by-step guide to pairing the Document Signer certificate within EF.SOD with a trusted CSCA, verifying the PKIX chain, validating the SOD signature, and calculating the Data Group hash results, using examples in Java and jMRTD.
One of the most common points of confusion in electronic passport validation is how the Document Signer (DS or DSC) certificate should “match” the Country Signing Certification Authority (CSCA) certificate. Simply comparing the name fields or checking for the same country code is not sufficient. The trust relationship is established by cryptographically verifying the DS certificate’s signature using the CSCA’s public key, which has been previously deemed trustworthy.
According to ICAO Doc 9303 Part 12, the CSCA serves as the trust anchor for the receiving system. The CSCA’s private key signs the DS certificates; the DS private key, in turn, signs the EF.SOD object on the passport chip. The Inspection System then compares the hash values within the EF.SOD with the hashes of the read Data Group files. All of these checks constitute the Passive Authentication flow.
How does the trust chain work?
Güvenilen CSCA sertifikası
│ DS sertifikasının imzasını doğrular
▼
Document Signer (DS) sertifikası
│ EF.SOD CMS imzasını doğrular
▼
EF.SOD / LDS Security Object
│ DG hash değerlerini taşır
▼
DG1, DG2, DG11, DG12 ...There are four different outcomes here, and they must be reported separately in the application:
- DS → CSCA chain: Was the DS certificate signed by a trusted CSCA?
- DS validity and revocation status: Is the certificate valid at the time of verification and usable according to the relevant CRL policy?
- SOD signature: Is the CMS signature within EF.SOD verified using the DS public key?
- Data Group integrity: Do the hash values of the read DG files match those in EF.SOD?
DS and CSCA Candidate Matching
A system may contain hundreds of CSCA certificates. To narrow down the candidates before validation, the DS certificate’s issuer field and the CSCA’s subject , as well as the Authority Key Identifier (AKI) and Subject Key Identifier (SKI) extensions, can be used to narrow down the candidates before validation. However, these are only candidate selection indicators. The final match must be made via certificate signature or PKIX path validation.
The following check alone is not secure:
boolean sameName = ds.getIssuerX500Principal()
.equals(csca.getSubjectX500Principal());
// sameName == true olması kriptografik güven kanıtı değildir.There may be multiple keys, rollover certificates, or invalid sources with the same Distinguished Name. Therefore, once a candidate CSCA has been identified, the DS certificate’s signature must be verified.
Simple cryptographic check
If you have a single trusted CSCA candidate, a basic signature check can be performed using the Java X.509 API as follows:
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Date;
public record DsCscaMatch(
boolean signatureValid,
boolean dsTimeValid,
String message
) {}
static DsCscaMatch matchDsToCsca(
X509Certificate ds,
X509Certificate csca,
Instant validationTime) {
try {
// DS sertifikası belirtilen zamanda geçerli mi?
ds.checkValidity(Date.from(validationTime));
// Esas kriptografik eşleşme:
// DS sertifikasının imzasını CSCA public key'i ile doğrula.
ds.verify(csca.getPublicKey());
return new DsCscaMatch(true, true, "DS, CSCA anahtarıyla doğrulandı");
} catch (java.security.cert.CertificateExpiredException
| java.security.cert.CertificateNotYetValidException e) {
return new DsCscaMatch(false, false, "DS zaman geçerliliği başarısız: " + e.getMessage());
} catch (GeneralSecurityException e) {
return new DsCscaMatch(false, true, "DS imzası CSCA ile doğrulanamadı: " + e.getMessage());
}
}ds.verify(csca.getPublicKey()) It verifies that the DS certificate was indeed signed by this CSCA key. However, for a production system, algorithm constraints, certificate profile, critical extensions, validation time, and revocation checks must also be addressed. Therefore, the general solution is PKIX validation.
Correct chain validation with PKIX CertPath
import java.security.cert.*;
import java.util.List;
import java.util.Set;
public record ChainResult(
boolean trusted,
X509Certificate trustAnchor,
String error
) {}
static ChainResult validateDsChain(
X509Certificate ds,
X509Certificate trustedCsca,
java.util.Date validationDate) {
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
CertPath path = factory.generateCertPath(List.of(ds));
TrustAnchor anchor = new TrustAnchor(trustedCsca, null);
PKIXParameters params = new PKIXParameters(Set.of(anchor));
params.setDate(validationDate);
// Örnekte CRL kaynağı eklenmediği için kapalı.
// Üretimde güncel ve doğrulanmış CRL verisiyle etkinleştirilmelidir.
params.setRevocationEnabled(false);
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
PKIXCertPathValidatorResult result =
(PKIXCertPathValidatorResult) validator.validate(path, params);
return new ChainResult(
true,
result.getTrustAnchor().getTrustedCert(),
null
);
} catch (CertPathValidatorException e) {
return new ChainResult(false, null,
"PKIX doğrulama hatası, index=" + e.getIndex()
+ ", reason=" + e.getReason());
} catch (GeneralSecurityException e) {
return new ChainResult(false, null, e.getMessage());
}
}Since the DS certificate is signed directly by the CSCA, the path typically consists of a single certificate; the trust anchor is the CSCA. In CSCA rollover scenarios, self-issued link certificates and local trust policies must also be addressed.
Finding the Correct CSCA Among Multiple CSCA’s
static Optional<X509Certificate> findIssuerCsca(
X509Certificate ds,
Collection<X509Certificate> trustedCscas,
Date validationDate) {
return trustedCscas.stream()
// Ön filtre: performans içindir, güven kararı değildir.
.filter(csca -> ds.getIssuerX500Principal()
.equals(csca.getSubjectX500Principal()))
// Nihai karar: PKIX doğrulaması.
.filter(csca -> validateDsChain(ds, csca, validationDate).trusted())
.findFirst();
}In a production environment, indexing certificates by country code, subject DN, SKI, and AKI speeds up the search. Nevertheless, the candidate must come from a trusted source. The DS certificate stored on the chip is not the trust anchor; the trust anchor is the verified CSCA repository.
Retrieving the DS certificate from EF.SOD using jMRTD
In jMRTD SODFilerepresents the EF.SOD content. Once chip access is granted following BAC or PACE, the EF.SOD can be read:
import java.io.InputStream;
import java.security.cert.X509Certificate;
import org.jmrtd.PassportService;
import org.jmrtd.lds.SODFile;
SODFile readSod(PassportService service) throws Exception {
try (InputStream in = service.getInputStream(PassportService.EF_SOD)) {
return new SODFile(in);
}
}
SODFile sod = readSod(passportService);
X509Certificate ds = sod.getDocSigningCertificate();
if (ds == null) {
throw new IllegalStateException(
"EF.SOD içinde DS sertifikası yok; güvenilir DS deposundan aranmalı"
);
}In the ICAO workflow, the DS certificate may be found within EF.SOD, but the application should not rely solely on this. When necessary, the DS certificate must be obtained from a trusted PKD/national distribution source, along with the issuer and serial information.
Verifying the SOD signature
After the DS chain is validated, the same DS certificate is used to verify the EF.SOD signature:
public record PassiveAuthResult(
boolean dsChainValid,
boolean sodSignatureValid,
Map<Integer, Boolean> dataGroupHashes,
String error
) {}
boolean sodSignatureValid;
try {
sodSignatureValid = sod.checkDocSignature(ds);
} catch (GeneralSecurityException e) {
sodSignatureValid = false;
// Loglarda kişisel veri veya ham sertifika içeriği tutulmamalıdır.
}checkDocSignature, EF.SOD checks the CMS SignedData signature. The fact that true does not mean the DS certificate is trusted. First, the DS → CSCA chain must be verified, followed by the SOD signature.
Capturing Data Group hash results
EF.SOD contains a table that maps Data Group numbers to their expected hash values. The complete encoded byte sequence of each read DG must be hashed using the digest algorithm specified in the SOD:
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
static Map<Integer, Boolean> verifyDataGroups(
SODFile sod,
Map<Integer, byte[]> encodedDataGroups) throws Exception {
String digestAlgorithm = sod.getDigestAlgorithm();
MessageDigest digest = MessageDigest.getInstance(digestAlgorithm);
Map<Integer, byte[]> expected = sod.getDataGroupHashes();
Map<Integer, Boolean> results = new LinkedHashMap<>();
for (Map.Entry<Integer, byte[]> entry : encodedDataGroups.entrySet()) {
int dgNumber = entry.getKey();
byte[] expectedHash = expected.get(dgNumber);
byte[] actualHash = digest.digest(entry.getValue());
results.put(dgNumber,
expectedHash != null &&
MessageDigest.isEqual(expectedHash, actualHash));
digest.reset();
}
return results;
}Parsed fields or reconstructed objects must not be used in the hash calculation. The encoded content at the LDS level of the DG file read from the chip must be hashed. Otherwise, differences in TLV encoding may produce false negatives.
Aggregating the result into a single object
ChainResult chain = validateDsChain(ds, matchedCsca, validationDate);
if (!chain.trusted()) {
return new PassiveAuthResult(false, false, Map.of(), chain.error());
}
boolean sodOk = sod.checkDocSignature(ds);
Map<Integer, Boolean> dgResults =
verifyDataGroups(sod, encodedDataGroups);
boolean everyReadDgMatches = dgResults.values().stream()
.allMatch(Boolean::booleanValue);
PassiveAuthResult result = new PassiveAuthResult(
true,
sodOk,
dgResults,
sodOk && everyReadDgMatches ? null : "Passive Authentication başarısız"
);It is more accurate to display the sub-results in the application interface rather than a single “PASSED” message:
DS_CHAIN : PASS
REVOCATION : NOT_CHECKED
SOD_SIGNATURE : PASS
DG1_HASH : PASS
DG2_HASH : PASS
DG11_HASH : NOT_READ
OVERALL : CONDITIONAL_PASSNOT_CHECKED and PASS should not be used in exactly the same sense. In particular, when CRL data is absent, the result must clearly indicate this.
Common Mistakes
- Considering the chain valid if the DS issuer DN equals the CSCA subject DN.
- Directly accepting the DS certificate on the chip as trusted.
sod.checkDocSignature(ds)Assuming the result is a Passive Authentication result.- Checking the DS certificate’s validity date against today’s date and ignoring the context in which the document was issued or signed.
- Reporting the result as fully successful even though a CRL check was not performed.
- Use parsed and regenerated data instead of the raw encoded content for the Data Group hash.
- Importing the CSCA Master List file into the trust store without verifying its signature and source trust.
Source and Trust Store
The ICAO Master List is used to distribute CSCA public-key certificates to PKD participants. However, the Master List itself is also signed; the certificates it contains should not be considered trusted without verifying the file’s source and the Master List Signer chain. National/bilateral CSCA distribution must also be managed in accordance with local trust policies.
Conclusion
The DS-to-CSCA pairing process is not a text comparison but a certificate path validation. A robust eMRTD validation system treats the trusted CSCA repository, the DS certificate chain, the revocation policy, the EF.SOD signature, and the hash of each read Data Group as separate results. jMRTD facilitates EF.SOD and DG operations; the application is responsible for the correct configuration of the trust store, PKIX policy, and result model.
References
How would you rate this article?
Your feedback helps improve future articles.