1+ // certificates/certificates.swift
2+ // Put this file under certificates/ in your project
3+
4+ import Foundation
5+ import Security
6+ import CryptoKit
7+
8+ public enum CertificateCheckResult {
9+ case incorrectPassword
10+ case noMatch
11+ case success
12+ }
13+
14+ public enum CertificateError : Error {
15+ case p12ImportFailed( OSStatus )
16+ case identityExtractionFailed
17+ case certExtractionFailed
18+ case cmsDecodeFailed( OSStatus )
19+ case noCertsInProvision
20+ case publicKeyExportFailed( OSStatus )
21+ }
22+
23+ public final class CertificatesManager {
24+ /// SHA256 hex from Data
25+ private static func sha256Hex( _ d: Data ) -> String {
26+ let digest = SHA256 . hash ( data: d)
27+ return digest. map { String ( format: " %02x " , $0) } . joined ( )
28+ }
29+
30+ /// Export public key bytes for a certificate (SecCertificate -> SecKey -> external representation)
31+ private static func publicKeyData( from cert: SecCertificate ) throws -> Data {
32+ guard let secKey = SecCertificateCopyKey ( cert) else {
33+ throw CertificateError . certExtractionFailed
34+ }
35+ var cfErr : Unmanaged < CFError > ?
36+ guard let keyData = SecKeyCopyExternalRepresentation ( secKey, & cfErr) as Data ? else {
37+ if let err = cfErr? . takeRetainedValue ( ) {
38+ throw CertificateError . publicKeyExportFailed ( ( err as NSError ) . code as OSStatus )
39+ } else {
40+ throw CertificateError . publicKeyExportFailed ( - 1 )
41+ }
42+ }
43+ return keyData
44+ }
45+
46+ /// Parse .mobileprovision CMS/PKCS#7 and return embedded SecCertificate array
47+ private static func certificatesFromMobileProvision( _ data: Data ) throws -> [ SecCertificate ] {
48+ var decoder : CMSDecoder ? = nil
49+ var status = CMSDecoderCreate ( & decoder)
50+ guard status == errSecSuccess, let dec = decoder else {
51+ throw CertificateError . cmsDecodeFailed ( status)
52+ }
53+
54+ // feed bytes
55+ _ = data. withUnsafeBytes { ( ptr: UnsafeRawBufferPointer ) -> OSStatus in
56+ guard let base = ptr. baseAddress else { return errSecParam }
57+ return CMSDecoderUpdateMessage ( dec, base, data. count)
58+ }
59+
60+ status = CMSDecoderFinalizeMessage ( dec)
61+ guard status == errSecSuccess else {
62+ throw CertificateError . cmsDecodeFailed ( status)
63+ }
64+
65+ var certsCF : CFArray ? = nil
66+ status = CMSDecoderCopyAllCerts ( dec, & certsCF)
67+ guard status == errSecSuccess, let certs = certsCF as? [ SecCertificate ] , certs. count > 0 else {
68+ throw CertificateError . noCertsInProvision
69+ }
70+
71+ return certs
72+ }
73+
74+ /// Top-level check: returns result
75+ /// - Parameters:
76+ /// - p12Data: contents of .p12
77+ /// - password: p12 password
78+ /// - mobileProvisionData: contents of .mobileprovision
79+ public static func check( p12Data: Data , password: String , mobileProvisionData: Data ) -> Result < CertificateCheckResult , Error > {
80+ // 1) try import .p12 (also verifies password)
81+ let options = [ kSecImportExportPassphrase as String : password] as CFDictionary
82+ var itemsCF : CFArray ?
83+ let importStatus = SecPKCS12Import ( p12Data as CFData , options, & itemsCF)
84+
85+ if importStatus == errSecAuthFailed {
86+ return . success( . incorrectPassword)
87+ }
88+
89+ guard importStatus == errSecSuccess, let items = itemsCF as? [ [ String : Any ] ] , items. count > 0 else {
90+ return . failure( CertificateError . p12ImportFailed ( importStatus) )
91+ }
92+
93+ guard let first = items. first,
94+ let identity = first [ kSecImportItemIdentity as String ] as? SecIdentity else {
95+ return . failure( CertificateError . identityExtractionFailed)
96+ }
97+
98+ // 2) extract certificate from identity
99+ var certRef : SecCertificate ?
100+ let certStatus = SecIdentityCopyCertificate ( identity, & certRef)
101+ guard certStatus == errSecSuccess, let p12Cert = certRef else {
102+ return . failure( CertificateError . certExtractionFailed)
103+ }
104+
105+ // 3) get public key bytes and hash
106+ do {
107+ let p12PubKeyData = try publicKeyData ( from: p12Cert)
108+ let p12Hash = sha256Hex ( p12PubKeyData)
109+
110+ // 4) parse mobileprovision and check embedded certs
111+ let embeddedCerts = try certificatesFromMobileProvision ( mobileProvisionData)
112+
113+ for cert in embeddedCerts {
114+ do {
115+ let embPubKeyData = try publicKeyData ( from: cert)
116+ let embHash = sha256Hex ( embPubKeyData)
117+ if embHash == p12Hash {
118+ return . success( . success)
119+ }
120+ } catch {
121+ // ignore this cert and continue
122+ continue
123+ }
124+ }
125+
126+ // if none matched
127+ return . success( . noMatch)
128+ } catch {
129+ return . failure( error)
130+ }
131+ }
132+ }
0 commit comments