Merge branch 'yubikey'

This commit is contained in:
Max Goedjen 2020-03-06 00:58:15 -08:00
commit a6ed2a6ef7
No known key found for this signature in database
GPG Key ID: E58C21DD77B9B8E8
5 changed files with 100 additions and 8 deletions

View File

@ -5,7 +5,7 @@ import OSLog
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
let store = SecureEnclave.Store() let store = SmartCard.Store()
let notifier = Notifier() let notifier = Notifier()
lazy var agent: Agent = { lazy var agent: Agent = {
Agent(store: store, notifier: notifier) Agent(store: store, notifier: notifier)

View File

@ -4,6 +4,10 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.ctkd.watcher-client</string>
</array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string> <string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>

View File

@ -2,6 +2,8 @@ import Foundation
import Security import Security
import CryptoTokenKit import CryptoTokenKit
// TODO: Might need to split this up into "sub-stores?"
// ie, each token has its own Store.
extension SmartCard { extension SmartCard {
public class Store: SecretStore { public class Store: SecretStore {
@ -10,20 +12,102 @@ extension SmartCard {
public let name = NSLocalizedString("Smart Card", comment: "Smart Card") public let name = NSLocalizedString("Smart Card", comment: "Smart Card")
@Published public fileprivate(set) var secrets: [Secret] = [] @Published public fileprivate(set) var secrets: [Secret] = []
fileprivate let watcher = TKTokenWatcher() fileprivate let watcher = TKTokenWatcher()
fileprivate var id: String?
public init() { public init() {
watcher.setInsertionHandler { (string) in id = watcher.tokenIDs.filter { !$0.contains("setoken") }.first
print(string) watcher.setInsertionHandler { string in
guard self.id == nil else { return }
guard !string.contains("setoken") else { return }
self.id = string
self.secrets.removeAll()
self.loadSecrets()
} }
print(watcher.tokenIDs) loadSecrets()
} }
public func sign(data: Data, with secret: SmartCard.Secret) throws -> Data { // MARK: Public API
fatalError()
public func create(name: String) throws {
fatalError("Keys must be created on the smart card.")
} }
public func delete(secret: SmartCard.Secret) throws { public func delete(secret: Secret) throws {
fatalError("Keys must be deleted on the smart card.")
}
public func sign(data: Data, with secret: SecretType) throws -> Data {
guard let id = id else { fatalError() }
let attributes = [
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id as CFData,
kSecAttrTokenID: id,
kSecReturnRef: true
] as CFDictionary
var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped)
if status != errSecSuccess {
throw KeychainError(statusCode: status)
}
guard let untypedSafe = untyped else {
throw KeychainError(statusCode: errSecSuccess)
}
let key = untypedSafe as! SecKey
var signError: SecurityError?
guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &signError) else {
throw SigningError(error: signError)
}
return signature as Data
} }
} }
}
extension SmartCard.Store {
fileprivate func loadSecrets() {
guard let id = id else { return }
let attributes = [
kSecClass: kSecClassKey,
kSecAttrTokenID: id,
kSecReturnRef: true,
kSecMatchLimit: kSecMatchLimitAll,
kSecReturnAttributes: true
] as CFDictionary
var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped)
print(status)
guard let typed = untyped as? [[CFString: Any]] else { return }
let wrapped: [SmartCard.Secret] = typed.map {
let name = $0[kSecAttrLabel] as? String ?? "Unnamed"
let id = $0[kSecAttrApplicationLabel] as! Data
let publicKeyRef = $0[kSecValueRef] as! SecKey
let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
let publicKey = publicKeyAttributes[kSecValueData] as! Data
return SmartCard.Secret(id: id, name: name, publicKey: publicKey)
}
secrets.append(contentsOf: wrapped)
}
}
extension SmartCard {
public struct KeychainError: Error {
public let statusCode: OSStatus
}
public struct SigningError: Error {
public let error: SecurityError?
}
}
extension SmartCard {
public typealias SecurityError = Unmanaged<CFError>
} }

View File

@ -12,7 +12,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView(store: secureEnclave) let contentView = ContentView(store: yubikey)
// Create the window and set the content view. // Create the window and set the content view.
window = NSWindow( window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),

View File

@ -4,6 +4,10 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.ctkd.watcher-client</string>
</array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string> <string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>