11// certificates/certificates.swift
2- // Put this file under certificates/ in your project
2+ // Put this file under Sources/prostore/ certificates/
33
44import Foundation
55import Security
66import CryptoKit
77
8+ #if canImport(CoreServices)
9+ import CoreServices
10+ #endif
11+
12+ #if canImport(OpenSSL)
13+ import OpenSSL
14+ #endif
15+
816public enum CertificateCheckResult {
917 case incorrectPassword
1018 case noMatch
@@ -18,44 +26,54 @@ public enum CertificateError: Error {
1826 case cmsDecodeFailed( OSStatus )
1927 case noCertsInProvision
2028 case publicKeyExportFailed( OSStatus )
29+ case unsupportedPlatform
30+ case opensslError( String )
2131}
2232
2333public final class CertificatesManager {
24- /// SHA256 hex from Data
34+ // SHA256 hex from Data
2535 private static func sha256Hex( _ d: Data ) -> String {
2636 let digest = SHA256 . hash ( data: d)
2737 return digest. map { String ( format: " %02x " , $0) } . joined ( )
2838 }
2939
30- /// Export public key bytes for a certificate (SecCertificate -> SecKey -> external representation)
40+ // Export public key bytes for a certificate (SecCertificate -> SecKey -> external representation)
3141 private static func publicKeyData( from cert: SecCertificate ) throws -> Data {
3242 guard let secKey = SecCertificateCopyKey ( cert) else {
3343 throw CertificateError . certExtractionFailed
3444 }
3545 var cfErr : Unmanaged < CFError > ?
3646 guard let keyData = SecKeyCopyExternalRepresentation ( secKey, & cfErr) as Data ? else {
3747 if let err = cfErr? . takeRetainedValue ( ) {
38- throw CertificateError . publicKeyExportFailed ( ( err as NSError ) . code as OSStatus )
48+ // fixed: cast to OSStatus correctly
49+ throw CertificateError . publicKeyExportFailed ( OSStatus ( ( err as NSError ) . code) )
3950 } else {
4051 throw CertificateError . publicKeyExportFailed ( - 1 )
4152 }
4253 }
4354 return keyData
4455 }
4556
46- /// Parse .mobileprovision CMS/PKCS#7 and return embedded SecCertificate array
57+ // Try to extract SecCertificate objects from a .mobileprovision Data blob.
58+ // Strategy:
59+ // - If CoreServices/CMSDecoder APIs are available, use them (preferred).
60+ // - Otherwise, if OpenSSL is available via SPM, parse with OpenSSL and convert X509 -> SecCertificate.
4761 private static func certificatesFromMobileProvision( _ data: Data ) throws -> [ SecCertificate ] {
62+ // --- Option A: Use CMSDecoder if available in this SDK ---
63+ #if canImport(CoreServices)
4864 var decoder : CMSDecoder ? = nil
4965 var status = CMSDecoderCreate ( & decoder)
5066 guard status == errSecSuccess, let dec = decoder else {
5167 throw CertificateError . cmsDecodeFailed ( status)
5268 }
5369
54- // feed bytes
55- _ = data. withUnsafeBytes { ( ptr: UnsafeRawBufferPointer ) -> OSStatus in
70+ let updateStatus : OSStatus = data. withUnsafeBytes { ( ptr: UnsafeRawBufferPointer ) -> OSStatus in
5671 guard let base = ptr. baseAddress else { return errSecParam }
5772 return CMSDecoderUpdateMessage ( dec, base, data. count)
5873 }
74+ guard updateStatus == errSecSuccess else {
75+ throw CertificateError . cmsDecodeFailed ( updateStatus)
76+ }
5977
6078 status = CMSDecoderFinalizeMessage ( dec)
6179 guard status == errSecSuccess else {
@@ -67,8 +85,67 @@ public final class CertificatesManager {
6785 guard status == errSecSuccess, let certs = certsCF as? [ SecCertificate ] , certs. count > 0 else {
6886 throw CertificateError . noCertsInProvision
6987 }
88+ return certs
89+ #else
90+ // --- Option B: Use OpenSSL fallback if available ---
91+ #if canImport(OpenSSL)
92+ // We'll parse PKCS#7 (DER) from the mobileprovision using OpenSSL functions.
93+ // Steps:
94+ // - create BIO from memory
95+ // - d2i_PKCS7_bio to parse
96+ // - PKCS7_get0_signers to get stack of X509
97+ // - convert each X509 to DER (i2d_X509) and then to SecCertificate
98+ var certs : [ SecCertificate ] = [ ]
99+
100+ // Create BIO from data
101+ let bio = data. withUnsafeBytes { ( ptr: UnsafeRawBufferPointer ) -> OpaquePointer ? in
102+ guard let base = ptr. baseAddress else { return nil }
103+ return BIO_new_mem_buf ( UnsafeMutableRawPointer ( mutating: base) , Int32 ( data. count) )
104+ }
105+
106+ guard let bioPtr = bio else {
107+ throw CertificateError . opensslError ( " BIO_new_mem_buf failed " )
108+ }
109+ defer { BIO_free ( bioPtr) }
70110
111+ guard let p7 = d2i_PKCS7_bio ( bioPtr, nil ) else {
112+ throw CertificateError . opensslError ( " d2i_PKCS7_bio failed " )
113+ }
114+ defer { PKCS7_free ( p7) }
115+
116+ // Get signers (stack of X509). PKCS7_get0_signers often returns a newly allocated stack.
117+ guard let signers = PKCS7_get0_signers ( p7, nil , 0 ) else {
118+ throw CertificateError . noCertsInProvision
119+ }
120+ // iterate
121+ let count = Int ( sk_X509_num ( signers) )
122+ for i in 0 ..< count {
123+ guard let x509 = sk_X509_value ( signers, i) else { continue }
124+ // convert X509 -> DER
125+ var derPtr : UnsafeMutablePointer < UInt8 > ? = nil
126+ let derLen = i2d_X509 ( x509, & derPtr)
127+ guard derLen > 0 , let dptr = derPtr else { continue }
128+ // wrap into Data
129+ let derData = Data ( bytes: dptr, count: Int ( derLen) )
130+ // free OpenSSL buffer produced by i2d_X509
131+ OPENSSL_free ( dptr)
132+ // create SecCertificate from DER
133+ if let secCert = SecCertificateCreateWithData ( nil , derData as CFData ) {
134+ certs. append ( secCert)
135+ } else {
136+ // skip if cannot create
137+ continue
138+ }
139+ }
140+ // free the signers stack
141+ sk_X509_pop_free ( signers, X509_free)
142+ guard certs. count > 0 else { throw CertificateError . noCertsInProvision }
71143 return certs
144+ #else
145+ // Neither CMSDecoder nor OpenSSL available — not supported
146+ throw CertificateError . unsupportedPlatform
147+ #endif
148+ #endif
72149 }
73150
74151 /// Top-level check: returns result
@@ -90,6 +167,8 @@ public final class CertificatesManager {
90167 return . failure( CertificateError . p12ImportFailed ( importStatus) )
91168 }
92169
170+ // The import result contains kSecImportItemIdentity. It's safe to downcast to SecIdentity,
171+ // but we'll be cautious and fail if not present.
93172 guard let first = items. first,
94173 let identity = first [ kSecImportItemIdentity as String ] as? SecIdentity else {
95174 return . failure( CertificateError . identityExtractionFailed)
@@ -129,4 +208,4 @@ public final class CertificatesManager {
129208 return . failure( error)
130209 }
131210 }
132- }
211+ }
0 commit comments