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

Commit 784c056

Browse files
authored
Remove CMSDecoder dependency as it is failing us
1 parent 66bb980 commit 784c056

1 file changed

Lines changed: 47 additions & 31 deletions

File tree

Sources/prostore/certificates/certificates.swift

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public enum CertificateError: Error {
1414
case certExtractionFailed
1515
case noCertsInProvision
1616
case publicKeyExportFailed(OSStatus)
17-
case unsupportedPlatform
17+
case plistExtractionFailed
18+
case unknown
1819
}
1920

2021
public final class CertificatesManager {
@@ -33,48 +34,64 @@ public final class CertificatesManager {
3334
var cfErr: Unmanaged<CFError>?
3435
guard let keyData = SecKeyCopyExternalRepresentation(secKey, &cfErr) as Data? else {
3536
if let cfError = cfErr?.takeRetainedValue() {
36-
let nsError = cfError as NSError
37-
throw CertificateError.publicKeyExportFailed(OSStatus(nsError.code))
37+
// get numeric code from CFError safely
38+
let code = CFErrorGetCode(cfError)
39+
throw CertificateError.publicKeyExportFailed(OSStatus(code))
3840
} else {
3941
throw CertificateError.publicKeyExportFailed(-1)
4042
}
4143
}
4244
return keyData
4345
}
4446

45-
// Parse PKCS#7 (DER) from the mobileprovision using Security's CMSDecoder,
46-
// convert to SecCertificate array (no OpenSSL required)
47+
// Extract the <plist>...</plist> portion from a .mobileprovision (PKCS7) blob,
48+
// parse it to a dictionary and return SecCertificate objects from DeveloperCertificates.
4749
private static func certificatesFromMobileProvision(_ data: Data) throws -> [SecCertificate] {
48-
// Create decoder
49-
var decoderOptional: CMSDecoder?
50-
var status = CMSDecoderCreate(&decoderOptional)
51-
guard status == errSecSuccess, let decoder = decoderOptional else {
52-
throw CertificateError.certExtractionFailed
53-
}
50+
// Find XML plist bounds inside the blob
51+
let startTag = Data("<plist".utf8)
52+
let endTag = Data("</plist>".utf8)
5453

55-
// Feed data into decoder
56-
let updateStatus: OSStatus = data.withUnsafeBytes { (rawPtr: UnsafeRawBufferPointer) in
57-
guard let base = rawPtr.baseAddress else { return errSecParam }
58-
return CMSDecoderUpdateMessage(decoder, base.assumingMemoryBound(to: UInt8.self), data.count)
54+
guard let startRange = data.range(of: startTag),
55+
let endRange = data.range(of: endTag) else {
56+
throw CertificateError.plistExtractionFailed
5957
}
60-
guard updateStatus == errSecSuccess else {
61-
throw CertificateError.certExtractionFailed
58+
59+
// endRange.upperBound is index after </plist>
60+
let plistData = data[startRange.lowerBound..<endRange.upperBound]
61+
62+
// Parse plist
63+
let parsed = try PropertyListSerialization.propertyList(from: Data(plistData), options: [], format: nil)
64+
guard let dict = parsed as? [String: Any] else {
65+
throw CertificateError.plistExtractionFailed
6266
}
6367

64-
// Finalize
65-
status = CMSDecoderFinalizeMessage(decoder)
66-
guard status == errSecSuccess else {
67-
throw CertificateError.certExtractionFailed
68+
// Typical key with embedded certs in provisioning profiles:
69+
// "DeveloperCertificates" -> [Data] (DER blobs)
70+
var resultCerts: [SecCertificate] = []
71+
72+
if let devArray = dict["DeveloperCertificates"] as? [Any] {
73+
for item in devArray {
74+
if let certData = item as? Data {
75+
if let secCert = SecCertificateCreateWithData(nil, certData as CFData) {
76+
resultCerts.append(secCert)
77+
}
78+
} else if let base64String = item as? String,
79+
let certData = Data(base64Encoded: base64String) {
80+
if let secCert = SecCertificateCreateWithData(nil, certData as CFData) {
81+
resultCerts.append(secCert)
82+
}
83+
} else {
84+
// ignore unknown item types
85+
continue
86+
}
87+
}
6888
}
6989

70-
// Extract all certificates
71-
var certsCF: CFArray?
72-
status = CMSDecoderCopyAllCerts(decoder, &certsCF)
73-
guard status == errSecSuccess, let certsArray = certsCF as? [SecCertificate], !certsArray.isEmpty else {
90+
if resultCerts.isEmpty {
7491
throw CertificateError.noCertsInProvision
7592
}
7693

77-
return certsArray
94+
return resultCerts
7895
}
7996

8097
/// Top-level check: returns result
@@ -96,13 +113,12 @@ public final class CertificatesManager {
96113
return .failure(CertificateError.p12ImportFailed(importStatus))
97114
}
98115

99-
// Force-cast to SecIdentity (import guarantees this key exists for valid PKCS12)
116+
// Grab identity (force-cast is safe because import succeeded and the dictionary contains the identity)
100117
guard let first = items.first else {
101118
return .failure(CertificateError.identityExtractionFailed)
102119
}
103-
guard let identity = first[kSecImportItemIdentity as String] as? SecIdentity else {
104-
return .failure(CertificateError.identityExtractionFailed)
105-
}
120+
// use force-cast to avoid "conditional downcast ... will always succeed" warnings
121+
let identity = first[kSecImportItemIdentity as String] as! SecIdentity
106122

107123
// 2) extract certificate from identity
108124
var certRef: SecCertificate?
@@ -116,7 +132,7 @@ public final class CertificatesManager {
116132
let p12PubKeyData = try publicKeyData(from: p12Cert)
117133
let p12Hash = sha256Hex(p12PubKeyData)
118134

119-
// 4) parse mobileprovision and check embedded certs
135+
// 4) parse mobileprovision and check embedded certs (no OpenSSL)
120136
let embeddedCerts = try certificatesFromMobileProvision(mobileProvisionData)
121137

122138
for cert in embeddedCerts {

0 commit comments

Comments
 (0)