Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 499cecc

Browse files
authored
Fix cert parsing fallback & OSStatus cast
1 parent f1fd195 commit 499cecc

File tree

1 file changed

+87
-8
lines changed

1 file changed

+87
-8
lines changed

Sources/prostore/certificates/certificates.swift

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
// certificates/certificates.swift
2-
// Put this file under certificates/ in your project
2+
// Put this file under Sources/prostore/certificates/
33

44
import Foundation
55
import Security
66
import CryptoKit
77

8+
#if canImport(CoreServices)
9+
import CoreServices
10+
#endif
11+
12+
#if canImport(OpenSSL)
13+
import OpenSSL
14+
#endif
15+
816
public 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

2333
public 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

Comments
 (0)