İçeriğe geç
Muhammed Ali
Kocabey

Passive Authentication: eID Belgesinin Bütünlüğünü Doğrulamak

Bir uygulama eID çipini okuduğunda asıl soru veriye güvenip güvenemeyeceğidir. Passive Authentication (PA), okunan veriyi düzenleyen ülkenin belge kişiselleştirilirken çipe koyduğu imzalı kayıtla karşılaştırır: Document Security Object, yani SOD. Her veri grubunun hash değeri SOD'daki değerle eşleşiyor ve SOD imzası güvenilen ulusal makama uzanıyorsa veri özgündür ve belgenin düzenlenmesinden sonra değiştirilmemiştir.

Bu sonuç, karşınızdaki çipin özgün olduğunun kanıtı değildir. Sistemin en çok karıştırılan sınırı budur; aşağıda ayrıntısını açıklıyorum.

Buradaki bilgiler açık standartlara dayanır. Passive Authentication, veri yapıları Part 10'da açıklanan ICAO Doc 9303 Part 11 içinde tanımlanır. Açık standart ve public framework API'leri seviyesinde kalıyorum; herhangi bir işverenin sistemi veya tedarikçinin SDK'sını tarif etmiyorum.

Temel noktalar

  • SOD, her veri grubunun hash değerini içeren imzalı liste ve imzaya ilişkin sertifikadır.
  • PA üç kontrolü sırasıyla yapar: SOD imzası, güvenilen CSCA'ya uzanan sertifika zinciri, okunan veri gruplarının yeniden hesaplanan hash değerleri.
  • PA verinin bütünlüğünü ve özgünlüğünü kanıtlar. Byte düzeyinde aynı bir kopya da PA'dan geçer; klon çipi Active Authentication veya Chip Authentication tespit eder.

Okuma akışında Passive Authentication'ın yeri

Android'de eID çipi okuma yazısında işi dört aşamaya ayırmıştım: NFC keşfi, erişim kontrolü (BAC veya PACE), APDU üzerinden veri gruplarını okuma ve doğrulama. PA son aşamadır. Başladığında güvenli kanal zaten açılmış, DG1 (MRZ), DG2 (yüz) ve EF.SOD dosyasındaki SOD belleğe alınmıştır.

Okumakla güvenmek ayrı sorumluluklardır. PA tamamlanana kadar okunan veri doğrulanmış sayılmaz. Başarılı bir READ BINARY, byte'ların alındığını gösterir; gerçek bir makamın gerçek bir kişi için düzenlediği veriler olduklarını göstermez.

SOD'un içi: imzalı hash listesi

SOD özel bir format değildir. RFC 5652'de tanımlanan CMS SignedData yapısını kullanır; önceki referansları PKCS#7 / RFC 3369'dur. İmzalı e-posta ve belgelerde de aynı kriptografik zarf kullanıldığı için JDK ve Bouncy Castle gibi araçlar bu yapıyı ayrıştırma ve doğrulama imkânı sunar.

Onu eID SOD yapan, imzalanan içeriğidir. encapContentInfo.eContent alanında DER kodlu ASN.1 LDSSecurityObject bulunur. Bu yapı şunları içerir:

  • yapı sürümü,
  • veri gruplarının hash algoritması; modern belgelerde SHA-2 ailesinden bir algoritma,
  • veri grubu numarası ile içerik hash değerini eşleyen DataGroupHash girdileri.

SOD adınızı veya fotoğrafınızı tutmaz; bunların yer aldığı dosyaların hash değerlerini tutar ve listeyi imzalar. DG1'in tek bir byte'ı değişirse hash eşleşmez; fotoğraf değişirse DG2 hash'i bozulur. Listenin imzası, listenin kendisine müdahaleyi de ortaya çıkarır.

CMS certificates alanındaki Document Signer (DS) sertifikası, SOD imzasını doğrulayan açık anahtarı taşır. DS sertifikasını ülkenin Country Signing CA (CSCA) otoritesi imzalar. Güven kararı bu iki katmanlı PKI'ye dayanır.

Sırasıyla üç kontrol

Kontroller tek başlarına yeterli olmadığından sırayla birlikte uygulanmalıdır.

1. SOD imzasını doğrula

Önce SOD'un içindeki DS sertifikasıyla gerçekten imzalandığını ve imzalı içeriğin değiştirilmediğini doğrularız. CMS dilinde messageDigest imzalı niteliği, eContent yani LDSSecurityObject üzerinden yeniden hesaplanan hash'e eşit olmalıdır. İmzalı niteliklerin imzası da DS açık anahtarıyla doğrulanmalıdır. CMS kütüphanesine SOD byte'ları ve imzalayan sertifika verilerek bu kontrol yapılır. Bu adım listenin kendi içinde tutarlı ve imzalı olduğunu gösterir; imzalayanın güvenilirliğini henüz göstermez.

2. Sertifika zincirini güvenilen CSCA'ya kadar doğrula

DS sertifikası, zaten güvenilen bir CSCA'ya bağlanıyorsa anlamlıdır. Güvenilen CSCA sertifikaları ICAO Public Key Directory (PKD) master list'lerinden veya ülkeler arasındaki doğrudan değişimden edinilir. Trust anchor olarak dağıtılmalı ve güncel tutulmalıdır. java.security zincir doğrulama araçları kullanılabilir:

import java.security.cert.CertPathValidator
import java.security.cert.CertificateFactory
import java.security.cert.PKIXParameters
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate

/**
 * Step 2: the Document Signer certificate inside the SOD must chain up
 * to a Country Signing CA we already trust (from the ICAO PKD master list).
 */
fun verifyDocumentSignerTrust(
    docSigner: X509Certificate,
    cscaTrustAnchors: Set<X509Certificate>,
): Boolean = try {
    val factory = CertificateFactory.getInstance("X.509")
    val path = factory.generateCertPath(listOf(docSigner))

    val anchors = cscaTrustAnchors.map { TrustAnchor(it, null) }.toSet()
    val params = PKIXParameters(anchors).apply {
        // Supply CRLs or an OCSP CertStore here; never leave this off in production.
        isRevocationEnabled = false
    }

    CertPathValidator.getInstance("PKIX").validate(path, params)
    true
} catch (e: Exception) {
    false
}

Örnekte iptal denetimi kodu kısa tutmak için kapalıdır. Gerçek dağıtımda DS sertifikası güncel CRL ile kontrol edilmelidir; iptal edilmiş imzalayan sertifika tam da yakalanması gereken durumdur. Trust anchor güncelliği de operasyonel bir sorumluluktur: CSCA sertifikaları değişir ve eski master list geçerli yeni belgelerin reddedilmesine yol açabilir.

3. Veri gruplarının hash değerlerini yeniden hesapla

Okunan veri burada doğrulamaya girer. Çipten alınan her veri grubunun hash'ini SOD'un belirttiği algoritmayla hesaplayıp kayıtlı değerle karşılaştırırız. Tek uyumsuzluk doğrulamanın başarısız olması için yeterlidir. Kontrol, gerçekten okunan gruplardan SOD'a doğru yapılır. SOD DG14 veya DG15'i de kapsayabilir; akışınız yalnız DG1 ve DG2'yi okuduysa diğerleri doğrulanmış olmaz. Okumadığınız ve hash'ini karşılaştırmadığınız gruba güvenmeyin.

import org.jmrtd.lds.SODFile
import java.security.MessageDigest

/**
 * Step 3: the data groups we actually read must hash to exactly the
 * values the SOD committed to. One mismatch fails the whole document.
 */
fun dataGroupsMatchSod(
    sod: SODFile,
    readDataGroups: Map<Int, ByteArray>, // DG number -> encoded data group bytes
): Boolean {
    val expected: Map<Int, ByteArray> = sod.dataGroupHashes
    val digest = MessageDigest.getInstance(sod.digestAlgorithm) // e.g. "SHA-256"

    return readDataGroups.all { (dgNumber, bytes) ->
        // The SOD must actually commit to every group we read; if it
        // doesn't list this one, we have no stored hash to trust it against.
        val storedHash = expected[dgNumber] ?: return@all false
        val computed = digest.digest(bytes) // digest() already resets the instance
        MessageDigest.isEqual(computed, storedHash) // constant-time compare
    }
}

Burada jMRTD'nin SODFile sınıfını kullanıyorum. Kendi ayrıştırıcınızı yazsanız bile okunmaya değer açık bir referanstır. getDataGroupHashes() grup numarasına göre hash değerlerini, getDigestAlgorithm() algoritmayı, getDocSigningCertificate() ise ikinci adımın DS sertifikasını verir. Karşılaştırmada Arrays.equals yerine MessageDigest.isEqual seçilmesi zamanlama bilgisi sızdırmayan karşılaştırma içindir; SecureCheck yazısındaki savunma yaklaşımının küçük bir örneğidir.

Üç kontrol de geçerse şu belirli sonucu kanıtlarız: okunan veri, CSCA'sına güvendiğimiz bir makam tarafından düzenlenmiş ve sonradan değiştirilmemiştir. PA'nın vaadi ve sınırı budur.

Passive Authentication neyi kanıtlamaz?

PA veriyi doğrular; çipin özgünlüğünü doğrulamaz.

Birisi gerçek belgedeki veri grupları ve SOD'u alıp aynı byte'ları boş çipe yazsın. Kopya aynı imzalı hash listesini ve geçerli DS sertifikasını taşır. Üç PA kontrolünün tamamından geçer. PA, asıl silisyumla onun birebir kopyasını ayıramaz.

Klon tespiti için çip, dışarı çıkarılamayan bir sırra sahip olduğunu kanıtlamalıdır. ICAO Doc 9303 bunun için iki aktif challenge–response mekanizması tanımlar:

  • Active Authentication (AA): çipten çıkmayan özel anahtarın eş açık anahtarı DG15'tedir. Okuyucu rastgele challenge gönderir, çip imzalar, okuyucu DG15 anahtarıyla doğrular. Özel anahtara sahip olmayan kopya geçerli imza üretemez.
  • Chip Authentication (CA): Extended Access Control / PACE ailesiyle ilişkili mekanizmanın anahtar bilgisi DG14'tedir. Çipin anahtar çiftine dayanan yeni güvenli kanal, çipin anahtara sahip olduğunu kanıtlar ve kanalı güçlendirir.

Destek belgeye göre değişir; eski belgeler ikisini de sunmayabilir. Bu nedenle her belgedeki anahtar bilgisini inceleyip klon tespitinin mümkün olup olmadığını belirlemek gerekir. Değişmeyen bağımlılık şudur: AA veya CA, DG15/DG14'ün özgünlüğü PA ile doğrulandıktan sonra anlam kazanır. Önce anahtarı doğrular, sonra o anahtarla klonu yakalarsınız. PA atlanırsa saldırgan kendi DG15'ini yazabilir.

Sonucu anlaşılır kıl

Doğrulayıcıdan yalnız boolean döndürmek, hatanın hangi aşamada olduğunu gizler. Anten kopması, iptal edilmiş sertifika ve değiştirilmiş veri farklı müdahaleler gerektirir. Sonuç modelinde aşamayı açık tutarım:

sealed interface PassiveAuthResult {
    data object Trusted : PassiveAuthResult
    data class Failed(val stage: Stage, val detail: String) : PassiveAuthResult

    enum class Stage { SOD_SIGNATURE, TRUST_CHAIN, DATA_GROUP_HASH }
}

TRUST_CHAIN hatası trust anchor ayarına veya iptal edilmiş sertifikaya işaret eder. DATA_GROUP_HASH, verinin imzalı kayıtla uyuşmadığını söyler. SOD_SIGNATURE ise eksik veya bozulmuş okumanın işareti olabilir. Bu aşamayı tanı çıktısına taşımak “çalışmıyor” ifadesini araştırılabilir bir soruna dönüştürür.

Passive Authentication dışarıdan ağır kriptografi gibi görünür. Pratikte açık standart ve anlaşılır bir açık anahtar hiyerarşisi üzerine kurulu üç doğrulama adımıdır. Mühendislik işi, her kontrolün neyi sağladığını ve çip klonlama gibi hangi problemi çözmek için tasarlanmadığını açık tutmaktır.

BİRLİKTE ÇALIŞALIM

İyi bir problemle başlayalım.

Ekibiniz için bir Android mühendisi arıyorsanız veya bir teknik problemi birlikte değerlendirmek istiyorsanız konuşalım.

30 dakika görüşelim[email protected]Tüm iletişim ve podcast bağlantıları