mirror of
https://github.com/morgan9e/bitwarden-desktop-agent
synced 2026-04-14 00:04:06 +09:00
SEP backend uses passphrase via LAContext applicationPassword
This commit is contained in:
@@ -171,11 +171,7 @@ impl BiometricBridge {
|
||||
}
|
||||
|
||||
fn unseal_key(&self) -> Option<String> {
|
||||
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();
|
||||
|
||||
19
src/main.rs
19
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)
|
||||
|
||||
@@ -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<CFError>?
|
||||
|
||||
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 <store|load|remove|has> <label>\n".data(using: .utf8)!)
|
||||
FileHandle.standardError.write("usage: sep-helper <store|load|remove|has> <label> [password]\n".data(using: .utf8)!)
|
||||
exit(2)
|
||||
}
|
||||
|
||||
@@ -143,21 +159,24 @@ let args = CommandLine.arguments
|
||||
if args.count < 3 { usage() }
|
||||
let cmd = args[1]
|
||||
let label = args[2]
|
||||
let password: String? = args.count > 3 ? args[3] : nil
|
||||
|
||||
switch cmd {
|
||||
case "store":
|
||||
guard let pw = password else { fatal("password required for store") }
|
||||
let data = readStdin()
|
||||
let privKey = getSEPKey(label) ?? createSEPKey(label)
|
||||
removeSEPKey(label)
|
||||
let privKey = createSEPKey(label, password: pw)
|
||||
guard let pubKey = SecKeyCopyPublicKey(privKey) else { fatal("no public key") }
|
||||
let ct = encrypt(pubKey, data)
|
||||
storeBlob(label, ct)
|
||||
|
||||
case "load":
|
||||
guard let privKey = getSEPKey(label) else { fatal("no SEP key for \(label)") }
|
||||
guard let pw = password else { fatal("password required for load") }
|
||||
guard let privKey = getSEPKey(label, password: pw) else { fatal("no SEP key for \(label)") }
|
||||
guard let ct = loadBlob(label) else { fatal("no data for \(label)") }
|
||||
let pt = decrypt(privKey, ct)
|
||||
let b64 = pt.base64EncodedString()
|
||||
print(b64)
|
||||
print(pt.base64EncodedString())
|
||||
|
||||
case "remove":
|
||||
removeBlob(label)
|
||||
|
||||
@@ -36,10 +36,10 @@ impl KeyStore for SEPKeyStore {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn store(&self, uid: &str, data: &[u8], _auth: &str) -> Result<(), String> {
|
||||
fn store(&self, uid: &str, data: &[u8], auth: &str) -> Result<(), String> {
|
||||
let b64 = B64.encode(data);
|
||||
let out = Command::new(helper_path())
|
||||
.args(["store", uid])
|
||||
.args(["store", uid, auth])
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
@@ -57,9 +57,9 @@ impl KeyStore for SEPKeyStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load(&self, uid: &str, _auth: &str) -> Result<Vec<u8>, String> {
|
||||
fn load(&self, uid: &str, auth: &str) -> Result<Vec<u8>, String> {
|
||||
let out = Command::new(helper_path())
|
||||
.args(["load", uid])
|
||||
.args(["load", uid, auth])
|
||||
.output()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user