Skip to content

Commit

Permalink
Replaced system for extracting certificates
Browse files Browse the repository at this point in the history
Allows for independent parsing of certificates for external tooling. Breaking change.
  • Loading branch information
James Sherlock committed Nov 20, 2018
1 parent 3cda1bd commit e0bb732
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/.build
/Packages
/*.xcodeproj
*.mobileprovision
Tests/SwiftyProvisioningProfileTests/Resources/*
48 changes: 48 additions & 0 deletions Sources/SwiftyProvisioningProfile/Model/Certificate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Certificate.swift
// SwiftyProvisioningProfile
//
// Created by Sherlock, James on 20/11/2018.
//

import Foundation

public struct Certificate: Encodable, Equatable {

public enum InitError: Error {
case failedToFindValue(key: String)
case failedToCastValue(expected: String, actual: String)
}

public let notValidBefore: Date
public let notValidAfter: Date

init(results: [CFString: Any]) throws {
notValidBefore = try Certificate.getValue(for: kSecOIDX509V1ValidityNotBefore, from: results)
notValidAfter = try Certificate.getValue(for: kSecOIDX509V1ValidityNotAfter, from: results)
// TODO: Add more values to this
}

static func getValue<T>(for key: CFString, from values: [CFString: Any]) throws -> T {
let node = values[key] as? [CFString: Any]

guard let rawValue = node?[kSecPropertyKeyValue] else {
throw InitError.failedToFindValue(key: key as String)
}

if T.self is Date.Type {
if let value = rawValue as? TimeInterval {
// Force unwrap here is fine as we've validated the type above
return Date(timeIntervalSinceReferenceDate: value) as! T
}
}

guard let value = rawValue as? T else {
let type = (node?[kSecPropertyKeyType] as? String) ?? String(describing: rawValue)
throw InitError.failedToCastValue(expected: String(describing: T.self), actual: type)
}

return value
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import Foundation
public struct DeveloperCertificate: Codable, Equatable {

public let data: Data
public let certificate: SecureCertificate?
public let certificate: Certificate?

// MARK: - Codable

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
data = try container.decode(Data.self)
certificate = try? SecureCertificate(base64EncodedData: data)
certificate = try? Certificate.parse(from: data)
}

public func encode(to encoder: Encoder) throws {
Expand Down
69 changes: 0 additions & 69 deletions Sources/SwiftyProvisioningProfile/Model/SecureCertificate.swift

This file was deleted.

44 changes: 44 additions & 0 deletions Sources/SwiftyProvisioningProfile/SwiftyCertificate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// SwiftyCertificate.swift
// SwiftyProvisioningProfile
//
// Created by Sherlock, James on 20/11/2018.
//

import Foundation
import Security

public extension Certificate {

public enum ParseError: Error {
case failedToCreateCertificate
case failedToCreateTrust
case failedToExtractValues
}

public static func parse(from data: Data) throws -> Certificate {
let certificate = try getSecCertificate(data: data)

var error: Unmanaged<CFError>?
let values = SecCertificateCopyValues(certificate, nil, &error)

if let error = error {
throw error.takeRetainedValue() as Error
}

guard let valuesDict = values as? [CFString: Any] else {
throw ParseError.failedToExtractValues
}

return try Certificate(results: valuesDict)
}

private static func getSecCertificate(data: Data) throws -> SecCertificate {
guard let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, data as CFData) else {
throw ParseError.failedToCreateCertificate
}

return certificate
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class SwiftyProvisioningProfileTests: XCTestCase {
return Bundle(path: "\(currentBundle.bundlePath)/../../../../Tests/SwiftyProvisioningProfileTests/Resources")
}()

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

lazy var testCertificateURLs: [URL] = {
guard let bundle = resourceBundle else {
fatalError("Tests are being run through Xcode, or the project structure no longer matches up")
}

guard let urls = bundle.urls(forResourcesWithExtension: "cer", subdirectory: nil) else {
fatalError("No `cer` files found in `Tests/SwiftyProvisioningProfileTests/Resources`")
}

return urls
}()

func testParseIOS() {

do {
for url in testProfileURL {
for url in testProfileURLs {
let data = try Data(contentsOf: url)
let profile = try ProvisioningProfile.parse(from: data)

print(profile.developerCertificates.flatMap({ $0.certificate?.description }).joined(separator: "\n"))
print(profile.name)
}

// TODO: Create or find a simple & usable profile and wrtie actual tests for it
// TODO: Create or find a simple & usable profile and write actual tests for it
} catch {
XCTFail(String(describing: error))
}

}

func testParseCertificate() {

do {
for url in testCertificateURLs {
let data = try Data(contentsOf: url)
let certificate = try Certificate.parse(from: data)

print(certificate)
}

// TODO: Create or find a simple & usable certificate and write actual tests for it
} catch {
XCTFail(String(describing: error))
}

}

func testParseMAC() {

// TODO
Expand All @@ -52,5 +81,6 @@ class SwiftyProvisioningProfileTests: XCTestCase {
static var allTests = [
("testParseIOS", testParseIOS),
("testParseMAC", testParseMAC),
("testParseCertificate", testParseCertificate),
]
}

0 comments on commit e0bb732

Please sign in to comment.