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

Commit 9fce03c

Browse files
authored
Remove cert error stuff
1 parent 5d8e714 commit 9fce03c

File tree

3 files changed

+334
-293
lines changed

3 files changed

+334
-293
lines changed
Lines changed: 111 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
// certificates.swift
12
import Foundation
23
import Security
34
import CryptoKit
4-
import Combine
55

66
public enum CertificateCheckResult {
77
case incorrectPassword
@@ -19,152 +19,156 @@ public enum CertificateError: Error {
1919
case unknown
2020
}
2121

22-
public final class CertificatesManager: ObservableObject {
23-
public static let shared = CertificatesManager()
24-
private init() {}
25-
26-
/// Returns the currently selected SecIdentity by loading from the selected folder in UserDefaults
27-
public var selectedIdentity: SecIdentity? {
28-
guard let folderName = UserDefaults.standard.string(forKey: "selectedCertificateFolder"),
29-
!folderName.isEmpty else {
30-
return nil
31-
}
32-
33-
let certDir = CertificateFileManager.shared.certificatesDirectory
34-
.appendingPathComponent(folderName)
35-
36-
let p12URL = certDir.appendingPathComponent("certificate.p12")
37-
let pwURL = certDir.appendingPathComponent("password.txt")
38-
39-
guard let p12Data = try? Data(contentsOf: p12URL),
40-
let passwordRaw = try? String(contentsOf: pwURL, encoding: .utf8) else {
41-
return nil
42-
}
43-
44-
let password = passwordRaw.trimmingCharacters(in: .whitespacesAndNewlines)
45-
46-
var items: CFArray?
47-
let options = [kSecImportExportPassphrase as String: password] as CFDictionary
48-
49-
let status = SecPKCS12Import(p12Data as CFData, options, &items)
50-
51-
guard status == errSecSuccess,
52-
let cfItems = items as? [[String: Any]],
53-
let identityAny = cfItems.first?[kSecImportItemIdentity as String],
54-
CFGetTypeID(identityAny as CFTypeRef) == SecIdentityGetTypeID() else {
55-
return nil
56-
}
57-
58-
let identity = identityAny as! SecIdentity
59-
return identity
60-
}
61-
62-
// MARK: - SHA256 hex
63-
public static func sha256Hex(_ d: Data) -> String {
22+
public final class CertificatesManager {
23+
// SHA256 hex from Data
24+
static func sha256Hex(_ d: Data) -> String {
6425
let digest = SHA256.hash(data: d)
6526
return digest.map { String(format: "%02x", $0) }.joined()
6627
}
67-
68-
// MARK: - Public key data
28+
29+
// Export public key bytes for a certificate (SecCertificate -> SecKey -> external representation)
6930
private static func publicKeyData(from cert: SecCertificate) throws -> Data {
7031
guard let secKey = SecCertificateCopyKey(cert) else {
7132
throw CertificateError.certExtractionFailed
7233
}
73-
var error: Unmanaged<CFError>?
74-
guard let data = SecKeyCopyExternalRepresentation(secKey, &error) as Data? else {
75-
let code = error.map { OSStatus(CFErrorGetCode($0.takeRetainedValue())) } ?? -1
76-
throw CertificateError.publicKeyExportFailed(code)
34+
35+
var cfErr: Unmanaged<CFError>?
36+
guard let keyData = SecKeyCopyExternalRepresentation(secKey, &cfErr) as Data? else {
37+
if let cfError = cfErr?.takeRetainedValue() {
38+
let code = CFErrorGetCode(cfError)
39+
throw CertificateError.publicKeyExportFailed(OSStatus(code))
40+
} else {
41+
throw CertificateError.publicKeyExportFailed(-1)
42+
}
7743
}
78-
return data
44+
45+
return keyData
7946
}
80-
81-
// MARK: - Extract certs from mobileprovision
47+
48+
// Extract the <plist>...</plist> portion from a .mobileprovision (PKCS7) blob,
49+
// parse it to a dictionary and return SecCertificate objects from DeveloperCertificates.
8250
private static func certificatesFromMobileProvision(_ data: Data) throws -> [SecCertificate] {
8351
let startTag = Data("<plist".utf8)
8452
let endTag = Data("</plist>".utf8)
85-
53+
8654
guard let startRange = data.range(of: startTag),
8755
let endRange = data.range(of: endTag) else {
8856
throw CertificateError.plistExtractionFailed
8957
}
90-
58+
9159
let plistData = data[startRange.lowerBound..<endRange.upperBound]
9260
let parsed = try PropertyListSerialization.propertyList(from: Data(plistData), options: [], format: nil)
93-
94-
guard let dict = parsed as? [String: Any],
95-
let devArray = dict["DeveloperCertificates"] as? [Any] else {
96-
throw CertificateError.noCertsInProvision
61+
62+
guard let dict = parsed as? [String: Any] else {
63+
throw CertificateError.plistExtractionFailed
9764
}
98-
99-
var result: [SecCertificate] = []
100-
for item in devArray {
101-
if let certData = item as? Data,
102-
let cert = SecCertificateCreateWithData(nil, certData as CFData) {
103-
result.append(cert)
104-
} else if let base64 = item as? String,
105-
let certData = Data(base64Encoded: base64),
106-
let cert = SecCertificateCreateWithData(nil, certData as CFData) {
107-
result.append(cert)
65+
66+
var resultCerts: [SecCertificate] = []
67+
if let devArray = dict["DeveloperCertificates"] as? [Any] {
68+
for item in devArray {
69+
if let certData = item as? Data {
70+
if let secCert = SecCertificateCreateWithData(nil, certData as CFData) {
71+
resultCerts.append(secCert)
72+
}
73+
} else if let base64String = item as? String,
74+
let certData = Data(base64Encoded: base64String) {
75+
if let secCert = SecCertificateCreateWithData(nil, certData as CFData) {
76+
resultCerts.append(secCert)
77+
}
78+
}
10879
}
10980
}
110-
111-
guard !result.isEmpty else { throw CertificateError.noCertsInProvision }
112-
return result
81+
82+
if resultCerts.isEmpty {
83+
throw CertificateError.noCertsInProvision
84+
}
85+
86+
return resultCerts
11387
}
114-
115-
// MARK: - Display name from provision
116-
public func getCertificateName(mobileProvisionData: Data) -> String? {
88+
89+
/// Get the certificate's display name (subject summary)
90+
public static func getCertificateName(mobileProvisionData: Data) -> String? {
91+
// Extract the <plist>...</plist> block from the mobileprovision (PKCS7) blob
11792
let startTag = Data("<plist".utf8)
11893
let endTag = Data("</plist>".utf8)
11994
guard let startRange = mobileProvisionData.range(of: startTag),
120-
let endRange = mobileProvisionData.range(of: endTag) else { return nil }
121-
122-
let plistData = mobileProvisionData[startRange.lowerBound..<endRange.upperBound]
123-
guard let parsed = try? PropertyListSerialization.propertyList(from: Data(plistData), options: [], format: nil),
124-
let dict = parsed as? [String: Any] else { return nil }
125-
126-
return (dict["TeamName"] as? String) ?? (dict["Name"] as? String)
127-
}
128-
129-
// MARK: - Check p12 ↔ mobileprovision match
130-
public static func check(p12Data: Data, password: String, mobileProvisionData: Data) -> Result<CertificateCheckResult, Error> {
131-
let options = [kSecImportExportPassphrase as String: password] as CFDictionary
132-
var items: CFArray?
95+
let endRange = mobileProvisionData.range(of: endTag) else {
96+
return nil
97+
}
13398

134-
let status = SecPKCS12Import(p12Data as CFData, options, &items)
99+
let plistDataSlice = mobileProvisionData[startRange.lowerBound..<endRange.upperBound]
100+
let plistData = Data(plistDataSlice)
135101

136-
if status == errSecAuthFailed { return .success(.incorrectPassword) }
102+
// Parse plist into a dictionary
103+
guard let parsed = try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil),
104+
let dict = parsed as? [String: Any] else {
105+
return nil
106+
}
137107

138-
guard status == errSecSuccess,
139-
let itemsArray = items as? [[String: Any]],
140-
let identityAny = itemsArray.first?[kSecImportItemIdentity as String],
141-
CFGetTypeID(identityAny as CFTypeRef) == SecIdentityGetTypeID() else {
142-
return .failure(CertificateError.p12ImportFailed(status))
108+
// Prefer TeamName if present
109+
if let teamName = dict["TeamName"] as? String, !teamName.isEmpty {
110+
return teamName
143111
}
144112

145-
let identity = identityAny as! SecIdentity
113+
// Fallback to Name (string)
114+
if let name = dict["Name"] as? String, !name.isEmpty {
115+
return name
116+
}
146117

118+
return nil
119+
}
120+
121+
/// Top-level check: returns result
122+
public static func check(p12Data: Data, password: String, mobileProvisionData: Data) -> Result<CertificateCheckResult, Error> {
123+
let options = [kSecImportExportPassphrase as String: password] as CFDictionary
124+
var itemsCF: CFArray?
125+
126+
let importStatus = SecPKCS12Import(p12Data as CFData, options, &itemsCF)
127+
128+
if importStatus == errSecAuthFailed {
129+
return .success(.incorrectPassword)
130+
}
131+
132+
guard importStatus == errSecSuccess, let items = itemsCF as? [[String: Any]], items.count > 0 else {
133+
return .failure(CertificateError.p12ImportFailed(importStatus))
134+
}
135+
136+
guard let first = items.first else {
137+
return .failure(CertificateError.identityExtractionFailed)
138+
}
139+
140+
let identity = first[kSecImportItemIdentity as String] as! SecIdentity
141+
147142
var certRef: SecCertificate?
148-
guard SecIdentityCopyCertificate(identity, &certRef) == errSecSuccess,
149-
let p12Cert = certRef else {
143+
let certStatus = SecIdentityCopyCertificate(identity, &certRef)
144+
145+
guard certStatus == errSecSuccess, let p12Cert = certRef else {
150146
return .failure(CertificateError.certExtractionFailed)
151147
}
152-
148+
153149
do {
154-
let p12KeyData = try publicKeyData(from: p12Cert)
155-
let p12Hash = sha256Hex(p12KeyData)
156-
157-
let embedded = try certificatesFromMobileProvision(mobileProvisionData)
158-
159-
for cert in embedded {
160-
let keyData = try publicKeyData(from: cert)
161-
if sha256Hex(keyData) == p12Hash {
162-
return .success(.success)
150+
let p12PubKeyData = try publicKeyData(from: p12Cert)
151+
let p12Hash = sha256Hex(p12PubKeyData)
152+
153+
let embeddedCerts = try certificatesFromMobileProvision(mobileProvisionData)
154+
155+
for cert in embeddedCerts {
156+
do {
157+
let embPubKeyData = try publicKeyData(from: cert)
158+
let embHash = sha256Hex(embPubKeyData)
159+
160+
if embHash == p12Hash {
161+
return .success(.success)
162+
}
163+
} catch {
164+
continue
163165
}
164166
}
167+
165168
return .success(.noMatch)
166169
} catch {
167170
return .failure(error)
168171
}
169172
}
173+
170174
}

0 commit comments

Comments
 (0)