diff --git a/SecureEnclaveToken.xcodeproj/project.pbxproj b/SecureEnclaveToken.xcodeproj/project.pbxproj index fa738b9..02b7e5e 100644 --- a/SecureEnclaveToken.xcodeproj/project.pbxproj +++ b/SecureEnclaveToken.xcodeproj/project.pbxproj @@ -439,6 +439,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.mwielgoszewski.SecureEnclaveToken; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -468,6 +469,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.mwielgoszewski.SecureEnclaveToken; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -488,6 +490,7 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.mwielgoszewski.SecureEnclaveToken.SecureEnclaveTokenExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -509,6 +512,7 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.mwielgoszewski.SecureEnclaveToken.SecureEnclaveTokenExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/SecureEnclaveToken/ContentView.swift b/SecureEnclaveToken/ContentView.swift index 363b2ca..e7ad71f 100644 --- a/SecureEnclaveToken/ContentView.swift +++ b/SecureEnclaveToken/ContentView.swift @@ -12,8 +12,10 @@ import CertificateSigningRequest struct ContentView: View { @State private var keysIsEmpty = false - @State private var loadButton = "Query Token Configuration" + @State private var loadButton = "Query Token" @State private var keysLoaded = 0 + @State private var certificateLabel = "" + @State private var keyLabel = "" @State private var generateKeyDescription = "" @State private var showDeleteConfirmation = false @State private var commonName = "" @@ -76,7 +78,6 @@ struct ContentView: View { var body: some View { let tag = "com.mwielgoszewski.SecureEnclaveToken.Key".data(using: .utf8)! - var keysLoaded = tokenConfig.keychainItems.count VStack(alignment: .leading, spacing: 5) { HStack { @@ -90,16 +91,33 @@ struct ContentView: View { let certificate = panel.url!.absoluteURL _ = loadCertificateForTagIntoTokenConfig(certificatePath: certificate, tag: tag, tokenConfig: tokenConfig) } - } else if self.loadButton == "Unload SE Keys" { + } else if self.loadButton == "Unload Token" { tokenConfig.keychainItems.removeAll() } - self.loadButton = tokenConfig.keychainItems.isEmpty ? "Load SE Keys" : "Unload SE Keys" + self.loadButton = tokenConfig.keychainItems.isEmpty ? "Load Token" : "Unload Token" keysLoaded = tokenConfig.keychainItems.count + if keysLoaded > 0 { + do { + let tkcert = try tokenConfig.certificate(for: tag) + let tkkey = try tokenConfig.key(for: tag) + certificateLabel = " • \(tkcert.label ?? "")" + keyLabel = " • \(tkkey.label ?? "")" + } catch { + } + } else { + certificateLabel = "" + keyLabel = "" + } + }) { Text(loadButton) } - Text("\(keysLoaded) token keychain items loaded") + VStack(alignment: .leading) { + Text("\(keysLoaded) token keychain items loaded") + Text(certificateLabel) + Text(keyLabel) + } } HStack(alignment: .top) { diff --git a/SecureEnclaveToken/Info.plist b/SecureEnclaveToken/Info.plist index 8aa8d09..ef05b5d 100644 --- a/SecureEnclaveToken/Info.plist +++ b/SecureEnclaveToken/Info.plist @@ -17,13 +17,15 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion 1 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Marcin Wielgoszewski NSMainStoryboardFile Main NSPrincipalClass diff --git a/SecureEnclaveToken/SecureEnclaveTokenUtils.swift b/SecureEnclaveToken/SecureEnclaveTokenUtils.swift index 4e9d7ea..35cec4c 100644 --- a/SecureEnclaveToken/SecureEnclaveTokenUtils.swift +++ b/SecureEnclaveToken/SecureEnclaveTokenUtils.swift @@ -7,6 +7,7 @@ import Foundation import Security +import CryptoKit import CryptoTokenKit func generateKeyInEnclave(tag: Data, accessibility: CFString, accessControlFlags: Int) -> SecKey { @@ -95,7 +96,7 @@ func deleteSecureEnclaveKey(tag: Data) -> Bool { return status == errSecSuccess } -func loadCertificateForTagIntoTokenConfig(certificatePath: URL, tag: Data, tokenConfig: TKToken.Configuration) -> Bool { +func loadCertificateForTagIntoTokenConfig(certificatePath: URL, tag: Data, tokenConfig: TKToken.Configuration) -> SecCertificate? { if FileManager.default.fileExists(atPath: certificatePath.path) { do { @@ -122,27 +123,36 @@ func loadCertificateForTagIntoTokenConfig(certificatePath: URL, tag: Data, token throw NSError() } + let publicKeyHash = Insecure.SHA1.hash(data: bytes) + + var commonName: CFString? + _ = SecCertificateCopyCommonName(certificate, &commonName) + let tokenCertificate = TKTokenKeychainCertificate(certificate: certificate, objectID: tag) - tokenCertificate?.label = "se certificate" + tokenCertificate?.label = "Certificate for PIV Authentication (\(commonName ?? "Secure Enclave" as CFString))" let tokenKey = TKTokenKeychainKey(certificate: certificate, objectID: tag) - tokenKey?.label = "se key" + tokenKey?.label = "PIV AUTH key" tokenKey?.canSign = true tokenKey?.canPerformKeyExchange = true tokenKey?.isSuitableForLogin = true tokenKey?.canDecrypt = false + tokenKey?.applicationTag = tag + tokenKey?.keySizeInBits = 256 + tokenKey?.keyType = kSecAttrKeyTypeECSECPrimeRandom as String + tokenKey?.publicKeyData = bytes + tokenKey?.publicKeyHash = publicKeyHash.data tokenConfig.keychainItems.append(tokenKey!) tokenConfig.keychainItems.append(tokenCertificate!) - return true + return certificate } catch { print("Failed to create cert??") - return false } } else { print("Certificate is not a file") } - return false + return nil } func importCertificateAndCreateSecIdentity(key: SecKey, certificatePath: URL, tag: Data) -> SecIdentity? { diff --git a/SecureEnclaveTokenExtension/Info.plist b/SecureEnclaveTokenExtension/Info.plist index 18e69c1..c678d8e 100644 --- a/SecureEnclaveTokenExtension/Info.plist +++ b/SecureEnclaveTokenExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion 1 LSMinimumSystemVersion diff --git a/SecureEnclaveTokenExtension/Token.swift b/SecureEnclaveTokenExtension/Token.swift index 758685e..8a3d91f 100644 --- a/SecureEnclaveTokenExtension/Token.swift +++ b/SecureEnclaveTokenExtension/Token.swift @@ -14,6 +14,13 @@ class Token: TKSmartCardToken, TKTokenDelegate { NSLog("Got instanceID: \(configuration.instanceID)") super.init(smartCard: smartCard, aid: nil, instanceID: configuration.instanceID, tokenDriver: tokenDriver) self.keychainContents?.fill(with: configuration.keychainItems) + do { + let tag = "com.mwielgoszewski.SecureEnclaveToken.Key".data(using: .utf8)! + let certificate = try self.keychainContents?.certificate(forObjectID: tag) + NSLog("Got certificate for \(String(describing: certificate?.label)) -> \(String(describing: certificate?.data.base64EncodedString()))") + } catch { + NSLog("Failed pulling certificate") + } NSLog("Got keychain items: \(String(describing: self.keychainContents?.items.count))") } diff --git a/SecureEnclaveTokenExtension/TokenDriver.swift b/SecureEnclaveTokenExtension/TokenDriver.swift index 6dd49d0..c5dadc7 100644 --- a/SecureEnclaveTokenExtension/TokenDriver.swift +++ b/SecureEnclaveTokenExtension/TokenDriver.swift @@ -18,5 +18,4 @@ class TokenDriver: TKSmartCardTokenDriver, TKSmartCardTokenDriverDelegate { func tokenDriver(_ driver: TKSmartCardTokenDriver, createTokenFor smartCard: TKSmartCard, aid AID: Data?) throws -> TKSmartCardToken { return try Token(smartCard: smartCard, aid: AID, tokenDriver: self) } - } diff --git a/SecureEnclaveTokenExtension/TokenSession.swift b/SecureEnclaveTokenExtension/TokenSession.swift index c65e619..9300fdd 100644 --- a/SecureEnclaveTokenExtension/TokenSession.swift +++ b/SecureEnclaveTokenExtension/TokenSession.swift @@ -17,114 +17,85 @@ class TokenSession: TKSmartCardTokenSession, TKTokenSessionDelegate { func tokenSession(_ session: TKTokenSession, supports operation: TKTokenOperation, keyObjectID: Any, algorithm: TKTokenKeyAlgorithm) -> Bool { // Indicate whether the given key supports the specified operation and algorithm. - NSLog("Determining if \(keyObjectID) supports \(operation.rawValue)") - do { - let tokenKey = try self.token.keychainContents?.key(forObjectID: keyObjectID) - - switch operation { - case .signData: - NSLog("Checking if key can sign for algorithm \(algorithm)") - if tokenKey!.canSign { - if algorithm.isAlgorithm(.ecdsaSignatureRFC4754) { - NSLog("Can sign ecdsaSignatureRFC4754") - return true - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962) { - NSLog("Can sign ecdsaSignatureDigestX962") - return true - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA1) { - NSLog("Can sign ecdsaSignatureDigestX962SHA1") - return true - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA224) { - NSLog("Can sign ecdsaSignatureDigestX962SHA224") - return true - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA256) { - NSLog("Can sign ecdsaSignatureDigestX962SHA256") - return true - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA384) { - NSLog("Can sign ecdsaSignatureDigestX962SHA384") - return true - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA512) { - NSLog("Can sign ecdsaSignatureDigestX962SHA512") - return true - } - return false - } - - case .performKeyExchange: - NSLog("Checking if key can perform key exchange \(algorithm)") - if tokenKey!.canPerformKeyExchange { - return true - } - - case .none: - break - case .readData: - break - case .decryptData: - break - @unknown default: - NSLog("Unhandled token operation requested: \(operation.rawValue)") - } - } catch { - NSLog("Could not find private key: \(keyObjectID)") + let tag = String(data: keyObjectID as! Data, encoding: .utf8)! + + NSLog("Querying for keyObjectID: \(tag) to determine whether TKTokenOperation:\(operation.rawValue) is supported") + + var item: CFTypeRef? + var privateKey: SecKey + let query: [String: Any] = [kSecClass as String: kSecClassKey, + kSecAttrApplicationTag as String: tag, + kSecAttrKeyType as String: kSecAttrKeyTypeEC, + kSecReturnRef as String: true] + + let status = SecItemCopyMatching(query as CFDictionary, &item) + guard status == errSecSuccess else { + NSLog("Could not find private key with tag: \(tag)") return false } - NSLog("Key \(keyObjectID) does not support operation: \(operation.rawValue)") + privateKey = (item as! SecKey) + + guard let alg = tokenAlgorithmToSecKeyAlgorithm(algorithm) else { + return false + } + + switch operation { + case .signData: + NSLog("Checking if keyObjectID: \(tag) can sign for algorithm \(alg.rawValue)") + return SecKeyIsAlgorithmSupported(privateKey, SecKeyOperationType.sign, alg) + case .performKeyExchange: + NSLog("Checking if keyObjectID: \(tag) can perform key exchange \(alg.rawValue)") + return SecKeyIsAlgorithmSupported(privateKey, SecKeyOperationType.keyExchange, alg) + case .none: + break + case .readData: + break + case .decryptData: + NSLog("Checking if keyObjectID: \(tag) can decrypt for algorithm \(alg.rawValue)") + return SecKeyIsAlgorithmSupported(privateKey, SecKeyOperationType.decrypt, alg) + @unknown default: + NSLog("Unhandled token operation requested: \(operation.rawValue)") + } + + NSLog("Key \(tag) does not support operation: \(operation.rawValue)") return false } func tokenSession(_ session: TKTokenSession, sign dataToSign: Data, keyObjectID: Any, algorithm: TKTokenKeyAlgorithm) throws -> Data { - var signature: Data? - var item: CFTypeRef? - var privateKey: SecKey + let tag = String(data: keyObjectID as! Data, encoding: .utf8)! - NSLog("Querying for key \(keyObjectID)") + guard let alg = tokenAlgorithmToSecKeyAlgorithm(algorithm) else { + throw NSError(domain: TKErrorDomain, code: TKError.Code.badParameter.rawValue, userInfo: nil) + } + + NSLog("Querying for keyObjectID: \(tag) to sign \(dataToSign) with \(alg.rawValue)") + var item: CFTypeRef? + var privateKey: SecKey let query: [String: Any] = [kSecClass as String: kSecClassKey, - kSecAttrApplicationTag as String: keyObjectID, + kSecAttrApplicationTag as String: tag, kSecAttrKeyType as String: kSecAttrKeyTypeEC, kSecReturnRef as String: true] let status = SecItemCopyMatching(query as CFDictionary, &item) - - if status == errSecSuccess { - privateKey = (item as! SecKey) - var error: Unmanaged? - var alg: SecKeyAlgorithm = .ecdsaSignatureDigestX962 - - if algorithm.isAlgorithm(.ecdsaSignatureRFC4754) { - alg = .ecdsaSignatureRFC4754 - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962) { - NSLog("Can sign ecdsaSignatureDigestX962") - alg = .ecdsaSignatureDigestX962 - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA1) { - NSLog("Can sign ecdsaSignatureDigestX962SHA1") - alg = .ecdsaSignatureDigestX962SHA1 - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA224) { - NSLog("Can sign ecdsaSignatureDigestX962SHA224") - alg = .ecdsaSignatureDigestX962SHA224 - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA256) { - NSLog("Can sign ecdsaSignatureDigestX962SHA256") - alg = .ecdsaSignatureDigestX962SHA256 - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA384) { - NSLog("Can sign ecdsaSignatureDigestX962SHA384") - alg = .ecdsaSignatureDigestX962SHA384 - } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA512) { - NSLog("Can sign ecdsaSignatureDigestX962SHA512") - alg = .ecdsaSignatureDigestX962SHA512 - } - - signature = SecKeyCreateSignature(privateKey, - alg, - dataToSign as CFData, - &error) as Data? - return signature! - } else { + guard status == errSecSuccess else { // If the operation failed for some reason, fill in an appropriate error like objectNotFound, corruptedData, etc. // Note that responding with TKErrorCodeAuthenticationNeeded will trigger user authentication after which the current operation will be re-attempted. throw NSError(domain: TKErrorDomain, code: TKError.Code.objectNotFound.rawValue, userInfo: nil) } + + privateKey = (item as! SecKey) + + var error: Unmanaged? + guard let signature = SecKeyCreateSignature(privateKey, + alg, + dataToSign as CFData, + &error) as Data? else { + throw error!.takeRetainedValue() as Error + } + + return signature } func tokenSession(_ session: TKTokenSession, decrypt ciphertext: Data, keyObjectID: Any, algorithm: TKTokenKeyAlgorithm) throws -> Data { @@ -132,35 +103,96 @@ class TokenSession: TKSmartCardTokenSession, TKTokenSessionDelegate { } func tokenSession(_ session: TKTokenSession, performKeyExchange otherPartyPublicKeyData: Data, keyObjectID objectID: Any, algorithm: TKTokenKeyAlgorithm, parameters: TKTokenKeyExchangeParameters) throws -> Data { - var secret: Data? + + let tag = String(data: objectID as! Data, encoding: .utf8)! + + NSLog("Querying for keyObjectID: \(tag) to perform key exchange") var item: CFTypeRef? var privateKey: SecKey - let query: [String: Any] = [kSecClass as String: kSecClassKey, - kSecAttrApplicationTag as String: objectID, + kSecAttrApplicationTag as String: tag, kSecAttrKeyType as String: kSecAttrKeyTypeEC, kSecReturnRef as String: true] let status = SecItemCopyMatching(query as CFDictionary, &item) + guard status == errSecSuccess else { + // If the operation failed for some reason, fill in an appropriate error like objectNotFound, corruptedData, etc. + // Note that responding with TKErrorCodeAuthenticationNeeded will trigger user authentication after which the current operation will be re-attempted. + throw NSError(domain: TKErrorDomain, code: TKError.Code.objectNotFound.rawValue, userInfo: nil) + } - if status == errSecSuccess { - privateKey = (item as! SecKey) - var error: Unmanaged? - - let attributes: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, - kSecAttrKeyClass as String: kSecAttrKeyClassPublic, - kSecAttrKeySizeInBits as String: 256] + privateKey = (item as! SecKey) - let publicKey = SecKeyCreateWithData(otherPartyPublicKeyData as CFData, attributes as CFDictionary, &error)! + let attributes: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeyClass as String: kSecAttrKeyClassPublic, + kSecAttrKeySizeInBits as String: 256] - secret = SecKeyCopyKeyExchangeResult(privateKey, SecKeyAlgorithm.ecdhKeyExchangeStandard, publicKey, parameters as! CFDictionary, &error) as Data? - return secret! + var error: Unmanaged? + guard let publicKey = SecKeyCreateWithData(otherPartyPublicKeyData as CFData, + attributes as CFDictionary, + &error) else { + throw error!.takeRetainedValue() as Error + } - } else { - // If the operation failed for some reason, fill in an appropriate error like objectNotFound, corruptedData, etc. - // Note that responding with TKErrorCodeAuthenticationNeeded will trigger user authentication after which the current operation will be re-attempted. + guard let kexAlg = tokenAlgorithmToSecKeyAlgorithm(algorithm) else { throw NSError(domain: TKErrorDomain, code: TKError.Code.badParameter.rawValue, userInfo: nil) } + + guard let secret = SecKeyCopyKeyExchangeResult(privateKey, + kexAlg, + publicKey, + parameters as! CFDictionary, + &error) as Data? else { + throw error!.takeRetainedValue() as Error + } + return secret } + + private func tokenAlgorithmToSecKeyAlgorithm(_ algorithm: TKTokenKeyAlgorithm) -> SecKeyAlgorithm? { + + if algorithm.isAlgorithm(.ecdsaSignatureRFC4754) { + return .ecdsaSignatureRFC4754 + } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962) { + return .ecdsaSignatureDigestX962 + } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA1) { + return .ecdsaSignatureDigestX962SHA1 + } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA224) { + return .ecdsaSignatureDigestX962SHA224 + } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA256) { + return .ecdsaSignatureDigestX962SHA256 + } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA384) { + return .ecdsaSignatureDigestX962SHA384 + } else if algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA512) { + return .ecdsaSignatureDigestX962SHA512 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeStandard) { + return .ecdhKeyExchangeStandard + } else if algorithm.isAlgorithm(.ecdhKeyExchangeStandardX963SHA1) { + return .ecdhKeyExchangeStandardX963SHA1 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeStandardX963SHA224) { + return .ecdhKeyExchangeStandardX963SHA224 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeStandardX963SHA256) { + return .ecdhKeyExchangeStandardX963SHA256 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeStandardX963SHA384) { + return .ecdhKeyExchangeStandardX963SHA384 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeStandardX963SHA512) { + return .ecdhKeyExchangeStandardX963SHA512 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeCofactor) { + return .ecdhKeyExchangeCofactor + } else if algorithm.isAlgorithm(.ecdhKeyExchangeCofactorX963SHA1) { + return .ecdhKeyExchangeCofactorX963SHA1 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeCofactorX963SHA224) { + return .ecdhKeyExchangeCofactorX963SHA224 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeCofactorX963SHA256) { + return .ecdhKeyExchangeCofactorX963SHA256 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeCofactorX963SHA384) { + return .ecdhKeyExchangeCofactorX963SHA384 + } else if algorithm.isAlgorithm(.ecdhKeyExchangeCofactorX963SHA512) { + return .ecdhKeyExchangeCofactorX963SHA512 + } + + return nil + + } + } diff --git a/images/SecureEnclaveToken.png b/images/SecureEnclaveToken.png index 8b26745..2f28984 100644 Binary files a/images/SecureEnclaveToken.png and b/images/SecureEnclaveToken.png differ