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

Commit f814cf6

Browse files
authored
Add back CertificatesManager.swift file
1 parent 926af33 commit f814cf6

File tree

2 files changed

+174
-261
lines changed

2 files changed

+174
-261
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// certificates.swift
2+
import Foundation
3+
import Security
4+
import CryptoKit
5+
6+
public enum CertificateCheckResult {
7+
case incorrectPassword
8+
case noMatch
9+
case success
10+
}
11+
12+
public enum CertificateError: Error {
13+
case p12ImportFailed(OSStatus)
14+
case identityExtractionFailed
15+
case certExtractionFailed
16+
case noCertsInProvision
17+
case publicKeyExportFailed(OSStatus)
18+
case plistExtractionFailed
19+
case unknown
20+
}
21+
22+
public final class CertificatesManager {
23+
// SHA256 hex from Data
24+
static func sha256Hex(_ d: Data) -> String {
25+
let digest = SHA256.hash(data: d)
26+
return digest.map { String(format: "%02x", $0) }.joined()
27+
}
28+
29+
// Export public key bytes for a certificate (SecCertificate -> SecKey -> external representation)
30+
private static func publicKeyData(from cert: SecCertificate) throws -> Data {
31+
guard let secKey = SecCertificateCopyKey(cert) else {
32+
throw CertificateError.certExtractionFailed
33+
}
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+
}
43+
}
44+
45+
return keyData
46+
}
47+
48+
// Extract the <plist>...</plist> portion from a .mobileprovision (PKCS7) blob,
49+
// parse it to a dictionary and return SecCertificate objects from DeveloperCertificates.
50+
private static func certificatesFromMobileProvision(_ data: Data) throws -> [SecCertificate] {
51+
let startTag = Data("<plist".utf8)
52+
let endTag = Data("</plist>".utf8)
53+
54+
guard let startRange = data.range(of: startTag),
55+
let endRange = data.range(of: endTag) else {
56+
throw CertificateError.plistExtractionFailed
57+
}
58+
59+
let plistData = data[startRange.lowerBound..<endRange.upperBound]
60+
let parsed = try PropertyListSerialization.propertyList(from: Data(plistData), options: [], format: nil)
61+
62+
guard let dict = parsed as? [String: Any] else {
63+
throw CertificateError.plistExtractionFailed
64+
}
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+
}
79+
}
80+
}
81+
82+
if resultCerts.isEmpty {
83+
throw CertificateError.noCertsInProvision
84+
}
85+
86+
return resultCerts
87+
}
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
92+
let startTag = Data("<plist".utf8)
93+
let endTag = Data("</plist>".utf8)
94+
guard let startRange = mobileProvisionData.range(of: startTag),
95+
let endRange = mobileProvisionData.range(of: endTag) else {
96+
return nil
97+
}
98+
99+
let plistDataSlice = mobileProvisionData[startRange.lowerBound..<endRange.upperBound]
100+
let plistData = Data(plistDataSlice)
101+
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+
}
107+
108+
// Prefer TeamName if present
109+
if let teamName = dict["TeamName"] as? String, !teamName.isEmpty {
110+
return teamName
111+
}
112+
113+
// Fallback to Name (string)
114+
if let name = dict["Name"] as? String, !name.isEmpty {
115+
return name
116+
}
117+
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+
142+
var certRef: SecCertificate?
143+
let certStatus = SecIdentityCopyCertificate(identity, &certRef)
144+
145+
guard certStatus == errSecSuccess, let p12Cert = certRef else {
146+
return .failure(CertificateError.certExtractionFailed)
147+
}
148+
149+
do {
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
165+
}
166+
}
167+
168+
return .success(.noMatch)
169+
} catch {
170+
return .failure(error)
171+
}
172+
}
173+
174+
}

0 commit comments

Comments
 (0)