diff --git a/src/bridge.rs b/src/bridge.rs index b83381a..09b34bf 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -171,11 +171,7 @@ impl BiometricBridge { } fn unseal_key(&self) -> Option { - let pw = if self.store.name() == "sep" { - String::new() - } else { - (self.prompt)(&format!("Enter {} password:", self.store.name()))? - }; + let pw = (self.prompt)(&format!("Enter {} password:", self.store.name()))?; match self.store.load(&self.uid, &pw) { Ok(mut raw) => { let len = raw.len(); diff --git a/src/main.rs b/src/main.rs index 3abe142..4909197 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,18 +78,13 @@ fn main() { let (mut key_bytes, server_uid) = auth::login(email, &pw, &args.server, &prompt); log::info(&format!("authenticated, uid={server_uid}")); - let auth = if store.name() == "sep" { - String::new() - } else { - let a = prompt(&format!("choose {} password:", store.name())) - .unwrap_or_else(|| log::fatal("no password provided")); - let a2 = prompt(&format!("confirm {} password:", store.name())) - .unwrap_or_else(|| log::fatal("no password provided")); - if a != a2 { - log::fatal("passwords don't match"); - } - a - }; + let auth = prompt(&format!("choose {} password:", store.name())) + .unwrap_or_else(|| log::fatal("no password provided")); + let auth2 = prompt(&format!("confirm {} password:", store.name())) + .unwrap_or_else(|| log::fatal("no password provided")); + if auth != auth2 { + log::fatal("passwords don't match"); + } store .store(&uid, &key_bytes, &auth) diff --git a/src/sep/sep-helper.swift b/src/sep/sep-helper.swift index b57457d..b4ba4fd 100644 --- a/src/sep/sep-helper.swift +++ b/src/sep/sep-helper.swift @@ -1,5 +1,6 @@ import Foundation import Security +import LocalAuthentication let service = "com.bitwarden.agent" let algo = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA256AESGCM @@ -8,23 +9,37 @@ func sepTag(_ label: String) -> Data { "\(service).sep.\(label)".data(using: .utf8)! } -func getSEPKey(_ label: String) -> SecKey? { - let q: [String: Any] = [ +func getSEPKey(_ label: String, password: String?) -> SecKey? { + var q: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: sepTag(label), kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecReturnRef as String: true, ] + + if let pw = password { + let ctx = LAContext() + ctx.setCredential(pw.data(using: .utf8), type: .applicationPassword) + q[kSecUseAuthenticationContext as String] = ctx + } + var ref: CFTypeRef? - return SecItemCopyMatching(q as CFDictionary, &ref) == errSecSuccess ? (ref as! SecKey) : nil + let s = SecItemCopyMatching(q as CFDictionary, &ref) + if s == errSecSuccess { return (ref as! SecKey) } + if s != errSecItemNotFound { fatal("keychain query: \(s)") } + return nil } -func createSEPKey(_ label: String) -> SecKey { +func createSEPKey(_ label: String, password: String) -> SecKey { var err: Unmanaged? + + let ctx = LAContext() + ctx.setCredential(password.data(using: .utf8), type: .applicationPassword) + guard let access = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - [.privateKeyUsage, .biometryCurrentSet], + [.privateKeyUsage, .applicationPassword], &err ) else { fatal("access control: \(err!.takeRetainedValue())") @@ -39,6 +54,7 @@ func createSEPKey(_ label: String) -> SecKey { kSecAttrApplicationTag as String: sepTag(label), kSecAttrAccessControl as String: access, ] as [String: Any], + kSecUseAuthenticationContext as String: ctx, ] guard let key = SecKeyCreateRandomKey(attrs as CFDictionary, &err) else { @@ -135,7 +151,7 @@ func fatal(_ msg: String) -> Never { } func usage() -> Never { - FileHandle.standardError.write("usage: sep-helper