xcode-simulator-cert/Sources/XcodeSimulatorKit/Commands/ExportCACommand.swift

104 lines
3.4 KiB
Swift

//
// Copyright © 2019 Simon Kågedal Reimer. See LICENSE.
//
import Foundation
import Basic
import SPMUtility
class ExportCACommand: Command {
private struct Options {
}
let name = "export-ca"
let overview = "Export Certificate Authorities"
private let binder = ArgumentBinder<Options>()
private var options = Options()
private let filteringBinder = ArgumentBinder<FilteringOptions>()
private var filteringOptions = FilteringOptions()
private var exportedCertificates: Set<Data> = []
func addOptions(to parser: ArgumentParser) {
filteringBinder.bind(to: &filteringOptions, parser: parser)
}
func fillParseResult(_ parseResult: ArgumentParser.Result) throws {
try binder.fill(parseResult: parseResult, into: &options)
try filteringBinder.fill(parseResult: parseResult, into: &filteringOptions)
}
func run() throws {
exportedCertificates = []
for device in try Simctl.flatListDevices().filter(using: filteringOptions) {
exportCertificates(for: device)
}
}
private func exportCertificates(for device: Simctl.Device) {
do {
let store = try TrustStore(uuid: device.udid).open()
for row in try store.rows() where !exportedCertificates.contains(row.sha1) {
guard let data = row.data else { continue }
do {
let path = try exportCertificate(from: data)
print("Exported PEM to \(localFileSystem.printable(path))")
exportedCertificates.insert(row.sha1)
} catch {
print("Found invalid certificate: \(error)")
}
}
} catch {
}
}
private func exportCertificate(from certificateData: Data) throws -> AbsolutePath {
let certificate = try Certificate(certificateData)
let fileName = certificate.subjectSummary?.clean() ?? "unknown-certificate"
let path = try localFileSystem.uniquePath(base: fileName, ext: "crt")
let data = try certificate.exportPEM()
try data.write(to: path.asURL)
return path
}
}
private extension String {
func clean() -> String {
return String(map { $0.isWhitespace || $0 == "/" ? "-" : $0 })
}
}
enum FileSystemError: LocalizedError {
case couldNotGetCurrentyWorkingDirectory
var errorDescription: String? {
switch self {
case .couldNotGetCurrentyWorkingDirectory:
return "Unable to get current working directory"
}
}
}
private extension FileSystem {
/// Returns "base.ext" unless that already exists, in which case it appends a serial number until a unique path has
/// been found: "base.ext" -> "base-1.ext" -> "base-2.ext" etc.
func uniquePath(base: String, ext: String, count: Int? = nil) throws -> AbsolutePath {
guard let directory = currentWorkingDirectory else {
throw FileSystemError.couldNotGetCurrentyWorkingDirectory
}
let path = directory.appending(component: base + (count.map { "-\($0)" } ?? "") + "." + ext)
if exists(path) {
return try uniquePath(base: base, ext: ext, count: count.map { $0 + 1 } ?? 1)
}
return path
}
func printable(_ path: AbsolutePath) -> String {
if let directory = currentWorkingDirectory {
return path.relative(to: directory).description
}
return path.description
}
}