Skip to content

Commit e0bb732

Browse files
author
James Sherlock
committed
Replaced system for extracting certificates
Allows for independent parsing of certificates for external tooling. Breaking change.
1 parent 3cda1bd commit e0bb732

File tree

6 files changed

+129
-76
lines changed

6 files changed

+129
-76
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
/.build
33
/Packages
44
/*.xcodeproj
5-
*.mobileprovision
5+
Tests/SwiftyProvisioningProfileTests/Resources/*
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// Certificate.swift
3+
// SwiftyProvisioningProfile
4+
//
5+
// Created by Sherlock, James on 20/11/2018.
6+
//
7+
8+
import Foundation
9+
10+
public struct Certificate: Encodable, Equatable {
11+
12+
public enum InitError: Error {
13+
case failedToFindValue(key: String)
14+
case failedToCastValue(expected: String, actual: String)
15+
}
16+
17+
public let notValidBefore: Date
18+
public let notValidAfter: Date
19+
20+
init(results: [CFString: Any]) throws {
21+
notValidBefore = try Certificate.getValue(for: kSecOIDX509V1ValidityNotBefore, from: results)
22+
notValidAfter = try Certificate.getValue(for: kSecOIDX509V1ValidityNotAfter, from: results)
23+
// TODO: Add more values to this
24+
}
25+
26+
static func getValue<T>(for key: CFString, from values: [CFString: Any]) throws -> T {
27+
let node = values[key] as? [CFString: Any]
28+
29+
guard let rawValue = node?[kSecPropertyKeyValue] else {
30+
throw InitError.failedToFindValue(key: key as String)
31+
}
32+
33+
if T.self is Date.Type {
34+
if let value = rawValue as? TimeInterval {
35+
// Force unwrap here is fine as we've validated the type above
36+
return Date(timeIntervalSinceReferenceDate: value) as! T
37+
}
38+
}
39+
40+
guard let value = rawValue as? T else {
41+
let type = (node?[kSecPropertyKeyType] as? String) ?? String(describing: rawValue)
42+
throw InitError.failedToCastValue(expected: String(describing: T.self), actual: type)
43+
}
44+
45+
return value
46+
}
47+
48+
}

Sources/SwiftyProvisioningProfile/Model/DeveloperCertificate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import Foundation
1010
public struct DeveloperCertificate: Codable, Equatable {
1111

1212
public let data: Data
13-
public let certificate: SecureCertificate?
13+
public let certificate: Certificate?
1414

1515
// MARK: - Codable
1616

1717
public init(from decoder: Decoder) throws {
1818
let container = try decoder.singleValueContainer()
1919
data = try container.decode(Data.self)
20-
certificate = try? SecureCertificate(base64EncodedData: data)
20+
certificate = try? Certificate.parse(from: data)
2121
}
2222

2323
public func encode(to encoder: Encoder) throws {

Sources/SwiftyProvisioningProfile/Model/SecureCertificate.swift

Lines changed: 0 additions & 69 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// SwiftyCertificate.swift
3+
// SwiftyProvisioningProfile
4+
//
5+
// Created by Sherlock, James on 20/11/2018.
6+
//
7+
8+
import Foundation
9+
import Security
10+
11+
public extension Certificate {
12+
13+
public enum ParseError: Error {
14+
case failedToCreateCertificate
15+
case failedToCreateTrust
16+
case failedToExtractValues
17+
}
18+
19+
public static func parse(from data: Data) throws -> Certificate {
20+
let certificate = try getSecCertificate(data: data)
21+
22+
var error: Unmanaged<CFError>?
23+
let values = SecCertificateCopyValues(certificate, nil, &error)
24+
25+
if let error = error {
26+
throw error.takeRetainedValue() as Error
27+
}
28+
29+
guard let valuesDict = values as? [CFString: Any] else {
30+
throw ParseError.failedToExtractValues
31+
}
32+
33+
return try Certificate(results: valuesDict)
34+
}
35+
36+
private static func getSecCertificate(data: Data) throws -> SecCertificate {
37+
guard let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, data as CFData) else {
38+
throw ParseError.failedToCreateCertificate
39+
}
40+
41+
return certificate
42+
}
43+
44+
}

Tests/SwiftyProvisioningProfileTests/SwiftyProvisioningProfileTests.swift

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class SwiftyProvisioningProfileTests: XCTestCase {
1414
return Bundle(path: "\(currentBundle.bundlePath)/../../../../Tests/SwiftyProvisioningProfileTests/Resources")
1515
}()
1616

17-
lazy var testProfileURL: [URL] = {
17+
lazy var testProfileURLs: [URL] = {
1818
guard let bundle = resourceBundle else {
1919
fatalError("Tests are being run through Xcode, or the project structure no longer matches up")
2020
}
@@ -26,23 +26,52 @@ class SwiftyProvisioningProfileTests: XCTestCase {
2626
return urls
2727
}()
2828

29+
lazy var testCertificateURLs: [URL] = {
30+
guard let bundle = resourceBundle else {
31+
fatalError("Tests are being run through Xcode, or the project structure no longer matches up")
32+
}
33+
34+
guard let urls = bundle.urls(forResourcesWithExtension: "cer", subdirectory: nil) else {
35+
fatalError("No `cer` files found in `Tests/SwiftyProvisioningProfileTests/Resources`")
36+
}
37+
38+
return urls
39+
}()
40+
2941
func testParseIOS() {
3042

3143
do {
32-
for url in testProfileURL {
44+
for url in testProfileURLs {
3345
let data = try Data(contentsOf: url)
3446
let profile = try ProvisioningProfile.parse(from: data)
3547

36-
print(profile.developerCertificates.flatMap({ $0.certificate?.description }).joined(separator: "\n"))
48+
print(profile.name)
3749
}
3850

39-
// TODO: Create or find a simple & usable profile and wrtie actual tests for it
51+
// TODO: Create or find a simple & usable profile and write actual tests for it
4052
} catch {
4153
XCTFail(String(describing: error))
4254
}
4355

4456
}
4557

58+
func testParseCertificate() {
59+
60+
do {
61+
for url in testCertificateURLs {
62+
let data = try Data(contentsOf: url)
63+
let certificate = try Certificate.parse(from: data)
64+
65+
print(certificate)
66+
}
67+
68+
// TODO: Create or find a simple & usable certificate and write actual tests for it
69+
} catch {
70+
XCTFail(String(describing: error))
71+
}
72+
73+
}
74+
4675
func testParseMAC() {
4776

4877
// TODO
@@ -52,5 +81,6 @@ class SwiftyProvisioningProfileTests: XCTestCase {
5281
static var allTests = [
5382
("testParseIOS", testParseIOS),
5483
("testParseMAC", testParseMAC),
84+
("testParseCertificate", testParseCertificate),
5585
]
5686
}

0 commit comments

Comments
 (0)