Certificate Validation
The IDK provides comprehensive X.509 certificate validation capabilities, including chain building, signature verification, revocation checking, and policy enforcement. This is essential for validating mDoc issuer certificates and other PKI-based trust relationships.
Certificate Validator
- Android/kotlin
- iOS/Swift
val certificateValidator = session.component.certificateValidator
let certificateValidator = session.component.certificateValidator
Basic Validation
- Android/kotlin
- iOS/Swift
// Validate a certificate chain
val result = certificateValidator.validate(
certificate = endEntityCertificate,
intermediates = intermediateCertificates,
trustAnchors = rootCertificates
)
when (result) {
is ValidationResult.Valid -> {
println("Certificate is valid")
println("Chain: ${result.certPath.certificates.size} certificates")
println("Trust anchor: ${result.trustAnchor.subject}")
}
is ValidationResult.Invalid -> {
println("Validation failed: ${result.reason}")
when (result.reason) {
is CertificateExpired -> println("Certificate has expired")
is CertificateNotYetValid -> println("Certificate not yet valid")
is CertificateRevoked -> println("Certificate has been revoked")
is SignatureInvalid -> println("Signature verification failed")
is PathBuildingFailed -> println("Could not build chain to trust anchor")
else -> println("Other error: ${result.reason}")
}
}
}
// Validate a certificate chain
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
intermediates: intermediateCertificates,
trustAnchors: rootCertificates
)
switch result {
case .valid(let validation):
print("Certificate is valid")
print("Chain: \(validation.certPath.certificates.count) certificates")
print("Trust anchor: \(validation.trustAnchor.subject)")
case .invalid(let reason):
print("Validation failed: \(reason)")
switch reason {
case .expired:
print("Certificate has expired")
case .notYetValid:
print("Certificate not yet valid")
case .revoked:
print("Certificate has been revoked")
case .signatureInvalid:
print("Signature verification failed")
case .pathBuildingFailed:
print("Could not build chain to trust anchor")
default:
print("Other error: \(reason)")
}
}
Chain Building
The validator automatically builds certificate chains:
- Android/kotlin
- iOS/Swift
// Configure chain building options
val options = ChainBuildingOptions(
// Maximum chain depth
maxPathLength = 5,
// Include certificates from these URLs if needed
fetchIntermediates = true,
intermediateUrls = listOf(
"https://certs.example.com/intermediates/"
),
// Timeout for fetching
fetchTimeout = Duration.ofSeconds(10)
)
val result = certificateValidator.validate(
certificate = endEntityCertificate,
options = options
)
// Configure chain building options
let options = ChainBuildingOptions(
// Maximum chain depth
maxPathLength: 5,
// Include certificates from these URLs if needed
fetchIntermediates: true,
intermediateUrls: [
"https://certs.example.com/intermediates/"
],
// Timeout for fetching
fetchTimeout: 10
)
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
options: options
)
Revocation Checking
OCSP (Online Certificate Status Protocol)
- Android/kotlin
- iOS/Swift
// Check revocation via OCSP
val result = certificateValidator.validate(
certificate = endEntityCertificate,
trustAnchors = rootCertificates,
revocationOptions = RevocationOptions(
checkOcsp = true,
ocspResponderUrl = "https://ocsp.example.com", // Optional override
ocspTimeout = Duration.ofSeconds(5),
allowOcspNoCheck = false, // Require OCSP response
cacheOcspResponses = true,
ocspCacheDuration = Duration.ofHours(1)
)
)
// Access OCSP details
if (result is ValidationResult.Valid) {
val ocspResponse = result.ocspResponse
println("OCSP status: ${ocspResponse?.certStatus}")
println("Produced at: ${ocspResponse?.producedAt}")
println("Next update: ${ocspResponse?.nextUpdate}")
}
// Check revocation via OCSP
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
trustAnchors: rootCertificates,
revocationOptions: RevocationOptions(
checkOcsp: true,
ocspResponderUrl: "https://ocsp.example.com", // Optional override
ocspTimeout: 5,
allowOcspNoCheck: false, // Require OCSP response
cacheOcspResponses: true,
ocspCacheDuration: 60 * 60 // 1 hour
)
)
// Access OCSP details
if case .valid(let validation) = result {
if let ocspResponse = validation.ocspResponse {
print("OCSP status: \(ocspResponse.certStatus)")
print("Produced at: \(ocspResponse.producedAt)")
print("Next update: \(ocspResponse.nextUpdate)")
}
}
CRL (Certificate Revocation List)
- Android/kotlin
- iOS/Swift
// Check revocation via CRL
val result = certificateValidator.validate(
certificate = endEntityCertificate,
trustAnchors = rootCertificates,
revocationOptions = RevocationOptions(
checkCrl = true,
crlDistributionPoints = listOf(
"https://crl.example.com/ca.crl"
),
crlTimeout = Duration.ofSeconds(10),
cacheCrls = true,
crlCacheDuration = Duration.ofHours(24)
)
)
// Check revocation via CRL
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
trustAnchors: rootCertificates,
revocationOptions: RevocationOptions(
checkCrl: true,
crlDistributionPoints: [
"https://crl.example.com/ca.crl"
],
crlTimeout: 10,
cacheCrls: true,
crlCacheDuration: 24 * 60 * 60 // 24 hours
)
)
Combined Revocation Checking
- Android/kotlin
- iOS/Swift
val result = certificateValidator.validate(
certificate = endEntityCertificate,
trustAnchors = rootCertificates,
revocationOptions = RevocationOptions(
// Try OCSP first, fall back to CRL
checkOcsp = true,
checkCrl = true,
preferOcsp = true,
// Soft fail: continue if revocation check fails
softFail = false, // Set true to allow when revocation info unavailable
// What to do if no revocation information
unknownRevocationPolicy = UnknownRevocationPolicy.REJECT
)
)
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
trustAnchors: rootCertificates,
revocationOptions: RevocationOptions(
// Try OCSP first, fall back to CRL
checkOcsp: true,
checkCrl: true,
preferOcsp: true,
// Soft fail: continue if revocation check fails
softFail: false, // Set true to allow when revocation info unavailable
// What to do if no revocation information
unknownRevocationPolicy: .reject
)
)
Key Usage Validation
Validate certificate key usage extensions:
- Android/kotlin
- iOS/Swift
val result = certificateValidator.validate(
certificate = endEntityCertificate,
trustAnchors = rootCertificates,
keyUsageOptions = KeyUsageOptions(
// Required key usages
requiredKeyUsages = setOf(
KeyUsage.DIGITAL_SIGNATURE,
KeyUsage.KEY_ENCIPHERMENT
),
// Required extended key usages
requiredExtendedKeyUsages = setOf(
ExtendedKeyUsage.SERVER_AUTH,
ExtendedKeyUsage.CLIENT_AUTH
),
// Strict: fail if extension is missing
strictKeyUsage = true
)
)
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
trustAnchors: rootCertificates,
keyUsageOptions: KeyUsageOptions(
// Required key usages
requiredKeyUsages: Set([.digitalSignature, .keyEncipherment]),
// Required extended key usages
requiredExtendedKeyUsages: Set([.serverAuth, .clientAuth]),
// Strict: fail if extension is missing
strictKeyUsage: true
)
)
mDoc Certificate Validation
Validate certificates for ISO 18013-5 mDoc:
- Android/kotlin
- iOS/Swift
// Validate IACA (Issuing Authority Certificate Authority) certificate
val iacaResult = certificateValidator.validateIaca(
certificate = iacaCertificate,
trustAnchors = iacaRootCertificates
)
// Validate Document Signer certificate
val dsResult = certificateValidator.validateDocumentSigner(
certificate = documentSignerCertificate,
iacaCertificate = iacaCertificate
)
// Validate complete mDoc certificate chain
val mdocResult = certificateValidator.validateMdocChain(
mso = mobileSecurityObject,
iacaTrustAnchors = iacaRootCertificates
)
when (mdocResult) {
is MdocValidationResult.Valid -> {
println("mDoc issuer chain is valid")
println("Issuing country: ${mdocResult.issuingCountry}")
println("Issuing authority: ${mdocResult.issuingAuthority}")
}
is MdocValidationResult.Invalid -> {
println("mDoc validation failed: ${mdocResult.reason}")
}
}
// Validate IACA (Issuing Authority Certificate Authority) certificate
let iacaResult = try await certificateValidator.validateIaca(
certificate: iacaCertificate,
trustAnchors: iacaRootCertificates
)
// Validate Document Signer certificate
let dsResult = try await certificateValidator.validateDocumentSigner(
certificate: documentSignerCertificate,
iacaCertificate: iacaCertificate
)
// Validate complete mDoc certificate chain
let mdocResult = try await certificateValidator.validateMdocChain(
mso: mobileSecurityObject,
iacaTrustAnchors: iacaRootCertificates
)
switch mdocResult {
case .valid(let validation):
print("mDoc issuer chain is valid")
print("Issuing country: \(validation.issuingCountry)")
print("Issuing authority: \(validation.issuingAuthority)")
case .invalid(let reason):
print("mDoc validation failed: \(reason)")
}
Time Validation
Control time-based validation:
- Android/kotlin
- iOS/Swift
val result = certificateValidator.validate(
certificate = endEntityCertificate,
trustAnchors = rootCertificates,
timeOptions = TimeValidationOptions(
// Use specific time for validation (e.g., historical validation)
validationTime = Instant.now(),
// Allow some clock skew
clockSkew = Duration.ofMinutes(5),
// Grace period for recently expired certificates
expirationGracePeriod = Duration.ofDays(1)
)
)
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
trustAnchors: rootCertificates,
timeOptions: TimeValidationOptions(
// Use specific time for validation (e.g., historical validation)
validationTime: Date(),
// Allow some clock skew
clockSkew: 5 * 60, // 5 minutes
// Grace period for recently expired certificates
expirationGracePeriod: 24 * 60 * 60 // 1 day
)
)
Policy Constraints
Enforce certificate policies:
- Android/kotlin
- iOS/Swift
val result = certificateValidator.validate(
certificate = endEntityCertificate,
trustAnchors = rootCertificates,
policyOptions = PolicyValidationOptions(
// Required certificate policies
requiredPolicies = setOf(
"2.16.840.1.101.2.1.11.5", // Example policy OID
"1.3.6.1.4.1.12345.1.1"
),
// Inhibit policy mapping
inhibitPolicyMapping = false,
// Require explicit policy
requireExplicitPolicy = true,
// Inhibit any policy
inhibitAnyPolicy = false
)
)
let result = try await certificateValidator.validate(
certificate: endEntityCertificate,
trustAnchors: rootCertificates,
policyOptions: PolicyValidationOptions(
// Required certificate policies
requiredPolicies: Set([
"2.16.840.1.101.2.1.11.5", // Example policy OID
"1.3.6.1.4.1.12345.1.1"
]),
// Inhibit policy mapping
inhibitPolicyMapping: false,
// Require explicit policy
requireExplicitPolicy: true,
// Inhibit any policy
inhibitAnyPolicy: false
)
)
Certificate Parsing
Parse and inspect certificate details:
- Android/kotlin
- iOS/Swift
// Parse a certificate from PEM
val certificate = certificateValidator.parseCertificate(pemString)
// Or from DER bytes
val certificate = certificateValidator.parseCertificate(derBytes)
// Inspect certificate details
println("Subject: ${certificate.subject}")
println("Issuer: ${certificate.issuer}")
println("Serial: ${certificate.serialNumber}")
println("Not Before: ${certificate.notBefore}")
println("Not After: ${certificate.notAfter}")
println("Public Key: ${certificate.publicKey.algorithm}")
// Extensions
certificate.extensions.forEach { ext ->
println("Extension: ${ext.oid} (critical: ${ext.isCritical})")
}
// Subject Alternative Names
certificate.subjectAltNames?.forEach { san ->
println("SAN: ${san.type} = ${san.value}")
}
// Parse a certificate from PEM
let certificate = try certificateValidator.parseCertificate(pem: pemString)
// Or from DER bytes
let certificate = try certificateValidator.parseCertificate(der: derBytes)
// Inspect certificate details
print("Subject: \(certificate.subject)")
print("Issuer: \(certificate.issuer)")
print("Serial: \(certificate.serialNumber)")
print("Not Before: \(certificate.notBefore)")
print("Not After: \(certificate.notAfter)")
print("Public Key: \(certificate.publicKey.algorithm)")
// Extensions
for ext in certificate.extensions {
print("Extension: \(ext.oid) (critical: \(ext.isCritical))")
}
// Subject Alternative Names
if let sans = certificate.subjectAltNames {
for san in sans {
print("SAN: \(san.type) = \(san.value)")
}
}
Configuration
Configure certificate validation via properties:
# Trust anchors
certificate.trust-anchors.path=/path/to/trust-anchors/
certificate.trust-anchors.include-system=true
# Chain building
certificate.chain.max-depth=10
certificate.chain.fetch-intermediates=true
# Revocation checking
certificate.revocation.check-ocsp=true
certificate.revocation.check-crl=true
certificate.revocation.prefer-ocsp=true
certificate.revocation.soft-fail=false
certificate.revocation.cache-enabled=true
certificate.revocation.cache-duration-hours=1
# Time validation
certificate.time.clock-skew-minutes=5
certificate.time.expiration-grace-days=0
# mDoc/IACA validation
certificate.iaca.trust-anchors.path=/path/to/iaca-roots/
Best Practices
Always validate complete chains. Don't just validate the end-entity certificate; ensure the entire chain to a trust anchor is valid.
Enable revocation checking for production systems. Use OCSP for real-time status and CRL as a fallback.
Cache revocation information. OCSP responses and CRLs can be cached to improve performance and reduce network traffic.
Use appropriate trust anchors. Only include root certificates you explicitly trust. Don't blindly trust system certificates for high-security applications.
Monitor certificate expiration. Track certificate validity periods and plan for renewal before expiration.