Make Install CA work
This commit is contained in:
parent
eb5032ad6f
commit
cbf356c1e0
|
@ -4,8 +4,8 @@ A description of this package.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* `install-ca` command
|
* Makefile
|
||||||
* `remove-ca` command
|
* Brew formula
|
||||||
|
* README
|
||||||
* Filter for os types
|
* Filter for os types
|
||||||
* `--dry-run` option
|
|
||||||
* `remove` command that removes simulators
|
* `remove` command that removes simulators
|
||||||
|
|
|
@ -8,6 +8,7 @@ import SPMUtility
|
||||||
class InstallCACommand: Command {
|
class InstallCACommand: Command {
|
||||||
private struct Options {
|
private struct Options {
|
||||||
var path: String?
|
var path: String?
|
||||||
|
var dryRun: Bool = false
|
||||||
}
|
}
|
||||||
let name = "install-ca"
|
let name = "install-ca"
|
||||||
let overview = "Install a Certificate Authority"
|
let overview = "Install a Certificate Authority"
|
||||||
|
@ -19,6 +20,14 @@ class InstallCACommand: Command {
|
||||||
private var filteringOptions = FilteringOptions()
|
private var filteringOptions = FilteringOptions()
|
||||||
|
|
||||||
func addOptions(to parser: ArgumentParser) {
|
func addOptions(to parser: ArgumentParser) {
|
||||||
|
binder.bind(option: parser.add(
|
||||||
|
option: "--dry-run",
|
||||||
|
kind: Bool.self,
|
||||||
|
usage: "Don't actually install any CA"
|
||||||
|
), to: { options, dryRun in
|
||||||
|
options.dryRun = dryRun
|
||||||
|
})
|
||||||
|
|
||||||
binder.bind(positional: parser.add(
|
binder.bind(positional: parser.add(
|
||||||
positional: "path",
|
positional: "path",
|
||||||
kind: String.self,
|
kind: String.self,
|
||||||
|
@ -38,11 +47,16 @@ class InstallCACommand: Command {
|
||||||
func run() throws {
|
func run() throws {
|
||||||
let url = URL(fileURLWithPath: options.path!)
|
let url = URL(fileURLWithPath: options.path!)
|
||||||
let certificate = try Certificate.load(from: url)
|
let certificate = try Certificate.load(from: url)
|
||||||
print(certificate)
|
let certificateName = certificate.subjectSummary ?? "<unknown certificate>"
|
||||||
|
|
||||||
print(filteringOptions)
|
|
||||||
for device in try Simctl.flatListDevices().filter(using: filteringOptions) {
|
for device in try Simctl.flatListDevices().filter(using: filteringOptions) {
|
||||||
print(device.name)
|
if options.dryRun {
|
||||||
|
print("Would install \(certificateName) into \(device.name).")
|
||||||
|
} else {
|
||||||
|
print("Installing \(certificateName) into \(device.name).")
|
||||||
|
let trustStore = TrustStore(uuid: device.udid)
|
||||||
|
try trustStore.open().addCertificate(certificate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,14 @@ struct Certificate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let data: Data
|
||||||
private let certificate: SecCertificate
|
private let certificate: SecCertificate
|
||||||
|
|
||||||
init(_ data: Data) throws {
|
init(_ data: Data) throws {
|
||||||
guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else {
|
guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else {
|
||||||
throw Error.invalidDERX509
|
throw Error.invalidDERX509
|
||||||
}
|
}
|
||||||
|
self.data = data
|
||||||
self.certificate = certificate
|
self.certificate = certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +71,7 @@ struct Certificate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func load(from url: URL) throws -> SecCertificate {
|
static func load(from url: URL) throws -> Certificate {
|
||||||
let data = try Data(contentsOf: url)
|
let data = try Data(contentsOf: url)
|
||||||
|
|
||||||
var cfitems: CFArray?
|
var cfitems: CFArray?
|
||||||
|
@ -83,7 +85,8 @@ struct Certificate {
|
||||||
guard type == .itemTypeCertificate, let items = cfitems as? [SecCertificate], let item = items.first else {
|
guard type == .itemTypeCertificate, let items = cfitems as? [SecCertificate], let item = items.first else {
|
||||||
throw Error.notACertficate
|
throw Error.notACertficate
|
||||||
}
|
}
|
||||||
return item
|
let certData = SecCertificateCopyData(item) as Data
|
||||||
|
return try Certificate(certData)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,19 @@ struct TrustStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeCertificate(with sha1: Data) throws {
|
func removeCertificate(with sha1: Data) throws {
|
||||||
let query: Delete = tsettings.where(sha1Column == sha1.datatypeValue).delete()
|
let query = tsettings.where(sha1Column == sha1.datatypeValue).delete()
|
||||||
try connection.run(query)
|
try connection.run(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addCertificate(_ certificate: Certificate) throws {
|
||||||
|
let row = try TrustStoreRow(certificate)
|
||||||
|
let insert = tsettings.insert(
|
||||||
|
subjColumn <- row.subj.datatypeValue,
|
||||||
|
sha1Column <- row.sha1.datatypeValue,
|
||||||
|
tsetColumn <- row.tset?.datatypeValue,
|
||||||
|
dataColumn <- row.data?.datatypeValue
|
||||||
|
)
|
||||||
|
try connection.run(insert)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,15 @@ extension TrustStoreRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(_ certificate: Certificate) throws {
|
||||||
|
let data = certificate.data
|
||||||
|
self.data = data
|
||||||
|
self.sha1 = Digest.sha1(data)
|
||||||
|
self.tset = createTset()
|
||||||
|
self.subj = try subjectContent(from: certificate)
|
||||||
|
assert(isValidTset(self.tset!))
|
||||||
|
}
|
||||||
|
|
||||||
func validatedCertificate() throws -> Certificate {
|
func validatedCertificate() throws -> Certificate {
|
||||||
guard let tset = tset else {
|
guard let tset = tset else {
|
||||||
throw Error.noTsetValue
|
throw Error.noTsetValue
|
||||||
|
@ -43,10 +52,9 @@ extension TrustStoreRow {
|
||||||
throw Error.noData
|
throw Error.noData
|
||||||
}
|
}
|
||||||
let certificate = try Certificate(data)
|
let certificate = try Certificate(data)
|
||||||
let subjectContent = try certificate.normalizedSubjectContent()
|
let subject = try subjectContent(from: certificate)
|
||||||
let tlv = try DERParser().parse(data: subjectContent)
|
|
||||||
|
|
||||||
guard tlv.data == subj else {
|
guard subject == subj else {
|
||||||
throw Error.subjectContentDoesNotMatch
|
throw Error.subjectContentDoesNotMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,13 +68,27 @@ extension TrustStoreRow {
|
||||||
|
|
||||||
return certificate
|
return certificate
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func isValidTset(_ data: Data) -> Bool {
|
private func subjectContent(from certificate: Certificate) throws -> Data {
|
||||||
do {
|
let subjectContent = try certificate.normalizedSubjectContent()
|
||||||
let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil)
|
let tlv = try DERParser().parse(data: subjectContent)
|
||||||
return plist is [Any]
|
return tlv.data
|
||||||
} catch {
|
}
|
||||||
return false
|
|
||||||
}
|
private func isValidTset(_ data: Data) -> Bool {
|
||||||
|
do {
|
||||||
|
let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil)
|
||||||
|
return plist is [Any]
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createTset() -> Data {
|
||||||
|
do {
|
||||||
|
return try PropertyListSerialization.data(fromPropertyList: [], format: .xml, options: 0)
|
||||||
|
} catch {
|
||||||
|
fatalError("Should be able to create propertylist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue