eMRTD Passive Authentication: DS Sertifikası CSCA ile Nasıl Eşleştirilir?
EF.SOD içindeki Document Signer sertifikasının güvenilir CSCA ile eşleştirilmesini, PKIX zincir doğrulamasını, SOD imzasını ve Data Group hash sonuçlarını Java ve jMRTD örnekleriyle adım adım ele alıyoruz.
Elektronik pasaport doğrulamasında en sık karıştırılan konulardan biri, Document Signer sertifikasının (DS veya DSC) Country Signing Certification Authority (CSCA) sertifikasıyla nasıl “match” edileceğidir. İsim alanlarını karşılaştırmak veya aynı ülke kodunu görmek yeterli değildir. Güven ilişkisi, DS sertifikasının imzasının önceden güvenilir kabul edilmiş CSCA açık anahtarıyla kriptografik olarak doğrulanmasıyla kurulur.
ICAO Doc 9303 Part 12'ye göre CSCA, alıcı sistem için güven noktasıdır. CSCA özel anahtarı DS sertifikalarını imzalar; DS özel anahtarı ise pasaport çipindeki EF.SOD nesnesini imzalar. Inspection System daha sonra EF.SOD içindeki hash değerlerini okunan Data Group dosyalarının hash'leriyle karşılaştırır. Bu kontrollerin tamamı Passive Authentication akışını oluşturur.
Güven zinciri nasıl çalışır?
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 ...Burada dört farklı sonuç vardır ve uygulamada ayrı ayrı raporlanmalıdır:
- DS → CSCA zinciri: DS sertifikası güvenilen bir CSCA tarafından mı imzalanmış?
- DS geçerliliği ve iptal durumu: Sertifika kontrol zamanında geçerli mi ve ilgili CRL politikasına göre kullanılabilir mi?
- SOD imzası: EF.SOD içindeki CMS imzası DS açık anahtarıyla doğrulanıyor mu?
- Data Group bütünlüğü: Okunan DG dosyalarının hash değerleri EF.SOD ile aynı mı?
DS ve CSCA aday eşleştirmesi
Bir sistemde yüzlerce CSCA sertifikası bulunabilir. Doğrulama öncesinde adayları daraltmak için DS sertifikasının issuer alanı ile CSCA'nın subject alanı, ayrıca Authority Key Identifier (AKI) ve Subject Key Identifier (SKI) uzantıları kullanılabilir. Ancak bunlar yalnızca aday seçme sinyalleridir. Nihai eşleşme sertifika imzası veya PKIX yol doğrulaması ile yapılmalıdır.
Yalnızca şu kontrol güvenli değildir:
boolean sameName = ds.getIssuerX500Principal()
.equals(csca.getSubjectX500Principal());
// sameName == true olması kriptografik güven kanıtı değildir.Aynı Distinguished Name ile birden fazla anahtar, rollover sertifikası veya hatalı kaynak bulunabilir. Bu nedenle aday CSCA bulunduktan sonra DS sertifikasının imzası mutlaka doğrulanmalıdır.
Basit kriptografik kontrol
Tek bir güvenilir CSCA adayınız varsa, Java X.509 API ile temel imza kontrolü şu şekilde yapılabilir:
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()) DS sertifikasının gerçekten bu CSCA anahtarı tarafından imzalandığını kontrol eder. Ancak üretim sistemi için algoritma kısıtları, sertifika profili, kritik uzantılar, doğrulama zamanı ve iptal kontrolü de ele alınmalıdır. Bu nedenle genel çözüm PKIX doğrulamasıdır.
PKIX CertPath ile doğru zincir doğrulaması
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());
}
}DS sertifikası doğrudan CSCA tarafından imzalandığı için yol çoğunlukla tek sertifikadan oluşur; güven noktası CSCA'dır. CSCA rollover senaryolarında self-issued link certificate ve yerel güven politikası ayrıca ele alınmalıdır.
Birden fazla CSCA arasından doğru olanı bulmak
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();
}Üretim uygulamasında sertifikaları ülke kodu, subject DN, SKI ve AKI ile indekslemek aramayı hızlandırır. Buna rağmen adayın güvenilir kaynaktan gelmesi gerekir. Çipte bulunan DS sertifikası güven noktası değildir; güven noktası doğrulanmış CSCA deposudur.
jMRTD ile EF.SOD içinden DS sertifikasını alma
jMRTD'de SODFile, EF.SOD içeriğini temsil eder. Çip erişimi BAC veya PACE sonrasında sağlandıktan sonra EF.SOD okunabilir:
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ı"
);
}ICAO akışında DS sertifikası EF.SOD içinde bulunabilir, ancak uygulama yalnızca buna bağımlı olmamalıdır. Gerektiğinde DS sertifikası güvenilir PKD/ulusal dağıtım kaynağından issuer ve serial bilgileriyle bulunmalıdır.
SOD imzasını doğrulamak
DS zinciri doğrulandıktan sonra aynı DS sertifikası EF.SOD imzasını kontrol etmek için kullanılır:
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 CMS SignedData imzasını kontrol eder. Bu sonucun true olması DS sertifikasının güvenilir olduğu anlamına gelmez. Önce DS → CSCA zinciri, sonra SOD imzası kontrol edilmelidir.
Data Group hash sonuçlarını yakalamak
EF.SOD, Data Group numaralarını beklenen hash değerleriyle eşleyen bir tablo taşır. Okunan her DG'nin tam encoded byte dizisi, SOD'da belirtilen digest algoritmasıyla hash edilmelidir:
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;
}Hash hesabında parse edilmiş alanlar veya yeniden oluşturulmuş nesne kullanılmamalıdır. Çipten okunan DG dosyasının LDS seviyesindeki encoded içeriği hash edilmelidir. Aksi hâlde TLV kodlama farkları yanlış negatif üretebilir.
Sonucu tek nesnede toplamak
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"
);Uygulama arayüzünde tek bir “PASSED” bilgisi yerine alt sonuçların gösterilmesi daha doğrudur:
DS_CHAIN : PASS
REVOCATION : NOT_CHECKED
SOD_SIGNATURE : PASS
DG1_HASH : PASS
DG2_HASH : PASS
DG11_HASH : NOT_READ
OVERALL : CONDITIONAL_PASSNOT_CHECKED ile PASS kesinlikle aynı anlamda kullanılmamalıdır. Özellikle CRL verisi bulunmadığında sonuç bunu açıkça taşımalıdır.
Sık yapılan hatalar
- DS issuer DN ile CSCA subject DN eşitse zinciri geçerli saymak.
- Çipteki DS sertifikasını doğrudan güvenilir kabul etmek.
sod.checkDocSignature(ds)sonucunu tüm Passive Authentication sonucu sanmak.- DS sertifikası geçerlilik tarihini bugüne göre kontrol edip belgenin düzenlenme/imzalanma bağlamını yok saymak.
- CRL kontrolü yapılmadığı hâlde sonucu tam başarılı raporlamak.
- Data Group hash'i için ham encoded içerik yerine parse edilip yeniden üretilmiş veri kullanmak.
- CSCA Master List dosyasını, imzasını ve kaynak güvenini doğrulamadan trust store'a aktarmak.
Kaynak ve güven deposu
ICAO Master List, PKD katılımcılarının CSCA açık anahtar sertifikalarını dağıtmak için kullanılır. Ancak Master List'in kendisi de imzalıdır; dosyanın kaynağı ve Master List Signer zinciri doğrulanmadan içindeki sertifikalar güvenilir kabul edilmemelidir. Ulusal/bilateral CSCA dağıtımı da yerel güven politikasına göre yönetilmelidir.
Sonuç
DS ile CSCA eşleştirme işlemi bir metin karşılaştırması değil, sertifika yolu doğrulamasıdır. Sağlam bir eMRTD doğrulama sistemi; güvenilir CSCA deposunu, DS sertifika zincirini, iptal politikasını, EF.SOD imzasını ve okunan her Data Group hash'ini ayrı sonuçlar olarak ele alır. jMRTD, EF.SOD ve DG işlemlerini kolaylaştırır; güven deposu, PKIX politikası ve sonuç modelinin doğru kurulması uygulamanın sorumluluğundadır.
Referanslar
Bu makaleyi nasıl değerlendirirsin?
Geri bildirimin sonraki içerikleri geliştirmeme yardımcı olur.