Merge branch 'yubikey'
This commit is contained in:
commit
a6ed2a6ef7
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue