11package id.walt.x509
22
3- actual val platformSupportsPkixCertificatePathValidation: Boolean = false
3+ import kotlinx.cinterop.COpaquePointer
4+ import kotlinx.cinterop.ExperimentalForeignApi
5+ import kotlinx.cinterop.MemScope
6+ import kotlinx.cinterop.addressOf
7+ import kotlinx.cinterop.alloc
8+ import kotlinx.cinterop.memScoped
9+ import kotlinx.cinterop.ptr
10+ import kotlinx.cinterop.reinterpret
11+ import kotlinx.cinterop.usePinned
12+ import kotlinx.cinterop.value
13+ import platform.CoreFoundation.CFArrayAppendValue
14+ import platform.CoreFoundation.CFArrayCreateMutable
15+ import platform.CoreFoundation.CFArrayRef
16+ import platform.CoreFoundation.CFDataCreate
17+ import platform.CoreFoundation.CFErrorRefVar
18+ import platform.CoreFoundation.CFRelease
19+ import platform.CoreFoundation.kCFAllocatorDefault
20+ import platform.Foundation.CFBridgingRelease
21+ import platform.Foundation.NSError
22+ import platform.Security.SecCertificateCreateWithData
23+ import platform.Security.SecCertificateRef
24+ import platform.Security.SecPolicyCreateBasicX509
25+ import platform.Security.SecPolicyCreateRevocation
26+ import platform.Security.SecPolicyRef
27+ import platform.Security.SecTrustCreateWithCertificates
28+ import platform.Security.SecTrustEvaluateWithError
29+ import platform.Security.SecTrustRef
30+ import platform.Security.SecTrustRefVar
31+ import platform.Security.SecTrustSetAnchorCertificates
32+ import platform.Security.SecTrustSetAnchorCertificatesOnly
33+ import platform.Security.errSecSuccess
34+ import platform.Security.kSecRevocationUseAnyAvailableMethod
435
36+ @OptIn(ExperimentalForeignApi ::class )
37+ actual val platformSupportsPkixCertificatePathValidation: Boolean = true
38+
39+ @OptIn(ExperimentalForeignApi ::class )
540@Throws(X509ValidationException ::class )
641actual fun validateCertificateChain (
742 leaf : CertificateDer ,
@@ -10,18 +45,133 @@ actual fun validateCertificateChain(
1045 enableTrustedChainRoot : Boolean ,
1146 enableSystemTrustAnchors : Boolean ,
1247 enableRevocation : Boolean
13- ) {
14- if (enableSystemTrustAnchors) {
15- throw X509ValidationException (" System trust anchors are not supported for iOS certificate validation." )
48+ ) = memScoped {
49+ val certificateReferences = mutableListOf<SecCertificateRef >()
50+ val policyReferences = mutableListOf<SecPolicyRef >()
51+ val arrayReferences = mutableListOf<CFArrayRef >()
52+ var trustReference: SecTrustRef ? = null
53+
54+ try {
55+ val anchorDers = buildList {
56+ addAll(trustAnchors.orEmpty())
57+ if (enableTrustedChainRoot) {
58+ addAll(chain.filter { certificate ->
59+ runCatching {
60+ PlatformX509Certificate .parse(certificate).isSelfSigned()
61+ }.getOrDefault(false )
62+ })
63+ }
64+ }.distinct()
65+ val certificateDers = (listOf (leaf) + chain)
66+ .distinct()
67+
68+ if (anchorDers.isEmpty() && ! enableSystemTrustAnchors) {
69+ throw X509ValidationException (
70+ " No trust anchors available: provide trustAnchors, include a trusted root, or enable system trust anchors."
71+ )
72+ }
73+
74+ certificateReferences + = certificateDers.mapIndexed { index, certificate ->
75+ certificate.toSecCertificate(" certificate at position $index " )
76+ }
77+ val certificates = if (certificateReferences.size == 1 ) {
78+ certificateReferences.single()
79+ } else {
80+ createCFArray(certificateReferences).also (arrayReferences::add)
81+ }
82+
83+ policyReferences + = SecPolicyCreateBasicX509 ()
84+ ? : throw X509ValidationException (" Certificate validation failed: could not create the X.509 policy." )
85+ if (enableRevocation) {
86+ policyReferences + = SecPolicyCreateRevocation (kSecRevocationUseAnyAvailableMethod)
87+ ? : throw X509ValidationException (" Certificate validation failed: could not create the revocation policy." )
88+ }
89+ val policies = if (policyReferences.size == 1 ) {
90+ policyReferences.single()
91+ } else {
92+ createCFArray(policyReferences).also (arrayReferences::add)
93+ }
94+
95+ val trust = alloc<SecTrustRefVar >().apply { value = null }
96+ checkStatus(
97+ operation = " create the certificate trust object" ,
98+ status = SecTrustCreateWithCertificates (certificates, policies, trust.ptr),
99+ )
100+ trustReference = trust.value
101+ ? : throw X509ValidationException (" Certificate validation failed: trust object was not created." )
102+
103+ if (anchorDers.isNotEmpty()) {
104+ certificateReferences + = anchorDers.mapIndexed { index, certificate ->
105+ certificate.toSecCertificate(" trust anchor at position $index " )
106+ }
107+ val anchorsArray = createCFArray(
108+ certificateReferences.takeLast(anchorDers.size)
109+ ).also (arrayReferences::add)
110+
111+ checkStatus(
112+ operation = " set certificate trust anchors" ,
113+ status = SecTrustSetAnchorCertificates (trustReference, anchorsArray),
114+ )
115+ checkStatus(
116+ operation = " configure certificate trust anchors" ,
117+ status = SecTrustSetAnchorCertificatesOnly (trustReference, ! enableSystemTrustAnchors),
118+ )
119+ }
120+
121+ val error = alloc<CFErrorRefVar >().apply { value = null }
122+ if (! SecTrustEvaluateWithError (trustReference, error.ptr)) {
123+ val description = error.value?.let {
124+ (CFBridgingRelease (it) as NSError ).localizedDescription
125+ } ? : " unknown Security framework error"
126+ throw X509ValidationException (
127+ " Certificate path invalid: $description "
128+ )
129+ }
130+ } catch (cause: X509ValidationException ) {
131+ throw cause
132+ } catch (cause: Exception ) {
133+ throw X509ValidationException (" Certificate validation failed: ${cause.message} " , cause)
134+ } finally {
135+ trustReference?.let (::CFRelease )
136+ arrayReferences.forEach(::CFRelease )
137+ policyReferences.forEach(::CFRelease )
138+ certificateReferences.forEach(::CFRelease )
16139 }
17- if (enableRevocation) {
18- throw X509ValidationException (" Revocation checking is not supported for iOS certificate validation." )
140+ }
141+
142+ @OptIn(ExperimentalForeignApi ::class )
143+ private fun CertificateDer.toSecCertificate (description : String ): SecCertificateRef {
144+ val byteArray = bytes.toByteArray()
145+ val data = byteArray.usePinned { pinned ->
146+ CFDataCreate (
147+ allocator = kCFAllocatorDefault,
148+ bytes = pinned.addressOf(0 ).reinterpret(),
149+ length = byteArray.size.toLong(),
150+ )
151+ } ? : throw X509ValidationException (" Certificate validation failed: could not create certificate data." )
152+ return try {
153+ SecCertificateCreateWithData (kCFAllocatorDefault, data)
154+ ? : throw X509ValidationException (
155+ " Certificate chain validation failed: invalid X.509 DER in $description ."
156+ )
157+ } finally {
158+ CFRelease (data)
19159 }
160+ }
20161
21- validateCertificateChainWithExplicitTrust(
22- leaf = leaf,
23- chain = chain,
24- trustAnchors = trustAnchors,
25- enableTrustedChainRoot = enableTrustedChainRoot,
26- )
162+ @OptIn(ExperimentalForeignApi ::class )
163+ private fun MemScope.createCFArray (values : List <COpaquePointer >): CFArrayRef {
164+ val array = CFArrayCreateMutable (
165+ allocator = kCFAllocatorDefault,
166+ capacity = values.size.toLong(),
167+ callBacks = null ,
168+ ) ? : throw X509ValidationException (" Certificate validation failed: could not create a Core Foundation array." )
169+ values.forEach { value -> CFArrayAppendValue (array, value) }
170+ return array
171+ }
172+
173+ private fun checkStatus (operation : String , status : Int ) {
174+ if (status != errSecSuccess) {
175+ throw X509ValidationException (" Certificate validation failed: could not $operation (OSStatus $status )." )
176+ }
27177}
0 commit comments