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> {
|
fn unseal_key(&self) -> Option<String> {
|
||||||
let pw = if self.store.name() == "sep" {
|
let pw = (self.prompt)(&format!("Enter {} password:", self.store.name()))?;
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
(self.prompt)(&format!("Enter {} password:", self.store.name()))?
|
|
||||||
};
|
|
||||||
match self.store.load(&self.uid, &pw) {
|
match self.store.load(&self.uid, &pw) {
|
||||||
Ok(mut raw) => {
|
Ok(mut raw) => {
|
||||||
let len = raw.len();
|
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);
|
let (mut key_bytes, server_uid) = auth::login(email, &pw, &args.server, &prompt);
|
||||||
log::info(&format!("authenticated, uid={server_uid}"));
|
log::info(&format!("authenticated, uid={server_uid}"));
|
||||||
|
|
||||||
let auth = if store.name() == "sep" {
|
let auth = prompt(&format!("choose {} password:", store.name()))
|
||||||
String::new()
|
.unwrap_or_else(|| log::fatal("no password provided"));
|
||||||
} else {
|
let auth2 = prompt(&format!("confirm {} password:", store.name()))
|
||||||
let a = prompt(&format!("choose {} password:", store.name()))
|
.unwrap_or_else(|| log::fatal("no password provided"));
|
||||||
.unwrap_or_else(|| log::fatal("no password provided"));
|
if auth != auth2 {
|
||||||
let a2 = prompt(&format!("confirm {} password:", store.name()))
|
log::fatal("passwords don't match");
|
||||||
.unwrap_or_else(|| log::fatal("no password provided"));
|
}
|
||||||
if a != a2 {
|
|
||||||
log::fatal("passwords don't match");
|
|
||||||
}
|
|
||||||
a
|
|
||||||
};
|
|
||||||
|
|
||||||
store
|
store
|
||||||
.store(&uid, &key_bytes, &auth)
|
.store(&uid, &key_bytes, &auth)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Security
|
import Security
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
let service = "com.bitwarden.agent"
|
let service = "com.bitwarden.agent"
|
||||||
let algo = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA256AESGCM
|
let algo = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA256AESGCM
|
||||||
@@ -8,23 +9,37 @@ func sepTag(_ label: String) -> Data {
|
|||||||
"\(service).sep.\(label)".data(using: .utf8)!
|
"\(service).sep.\(label)".data(using: .utf8)!
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSEPKey(_ label: String) -> SecKey? {
|
func getSEPKey(_ label: String, password: String?) -> SecKey? {
|
||||||
let q: [String: Any] = [
|
var q: [String: Any] = [
|
||||||
kSecClass as String: kSecClassKey,
|
kSecClass as String: kSecClassKey,
|
||||||
kSecAttrApplicationTag as String: sepTag(label),
|
kSecAttrApplicationTag as String: sepTag(label),
|
||||||
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
kSecReturnRef as String: true,
|
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?
|
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>?
|
var err: Unmanaged<CFError>?
|
||||||
|
|
||||||
|
let ctx = LAContext()
|
||||||
|
ctx.setCredential(password.data(using: .utf8), type: .applicationPassword)
|
||||||
|
|
||||||
guard let access = SecAccessControlCreateWithFlags(
|
guard let access = SecAccessControlCreateWithFlags(
|
||||||
nil,
|
nil,
|
||||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
[.privateKeyUsage, .biometryCurrentSet],
|
[.privateKeyUsage, .applicationPassword],
|
||||||
&err
|
&err
|
||||||
) else {
|
) else {
|
||||||
fatal("access control: \(err!.takeRetainedValue())")
|
fatal("access control: \(err!.takeRetainedValue())")
|
||||||
@@ -39,6 +54,7 @@ func createSEPKey(_ label: String) -> SecKey {
|
|||||||
kSecAttrApplicationTag as String: sepTag(label),
|
kSecAttrApplicationTag as String: sepTag(label),
|
||||||
kSecAttrAccessControl as String: access,
|
kSecAttrAccessControl as String: access,
|
||||||
] as [String: Any],
|
] as [String: Any],
|
||||||
|
kSecUseAuthenticationContext as String: ctx,
|
||||||
]
|
]
|
||||||
|
|
||||||
guard let key = SecKeyCreateRandomKey(attrs as CFDictionary, &err) else {
|
guard let key = SecKeyCreateRandomKey(attrs as CFDictionary, &err) else {
|
||||||
@@ -135,7 +151,7 @@ func fatal(_ msg: String) -> Never {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func usage() -> 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)
|
exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,21 +159,24 @@ let args = CommandLine.arguments
|
|||||||
if args.count < 3 { usage() }
|
if args.count < 3 { usage() }
|
||||||
let cmd = args[1]
|
let cmd = args[1]
|
||||||
let label = args[2]
|
let label = args[2]
|
||||||
|
let password: String? = args.count > 3 ? args[3] : nil
|
||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "store":
|
case "store":
|
||||||
|
guard let pw = password else { fatal("password required for store") }
|
||||||
let data = readStdin()
|
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") }
|
guard let pubKey = SecKeyCopyPublicKey(privKey) else { fatal("no public key") }
|
||||||
let ct = encrypt(pubKey, data)
|
let ct = encrypt(pubKey, data)
|
||||||
storeBlob(label, ct)
|
storeBlob(label, ct)
|
||||||
|
|
||||||
case "load":
|
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)") }
|
guard let ct = loadBlob(label) else { fatal("no data for \(label)") }
|
||||||
let pt = decrypt(privKey, ct)
|
let pt = decrypt(privKey, ct)
|
||||||
let b64 = pt.base64EncodedString()
|
print(pt.base64EncodedString())
|
||||||
print(b64)
|
|
||||||
|
|
||||||
case "remove":
|
case "remove":
|
||||||
removeBlob(label)
|
removeBlob(label)
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ impl KeyStore for SEPKeyStore {
|
|||||||
.unwrap_or(false)
|
.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 b64 = B64.encode(data);
|
||||||
let out = Command::new(helper_path())
|
let out = Command::new(helper_path())
|
||||||
.args(["store", uid])
|
.args(["store", uid, auth])
|
||||||
.stdin(std::process::Stdio::piped())
|
.stdin(std::process::Stdio::piped())
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
.stderr(std::process::Stdio::piped())
|
.stderr(std::process::Stdio::piped())
|
||||||
@@ -57,9 +57,9 @@ impl KeyStore for SEPKeyStore {
|
|||||||
Ok(())
|
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())
|
let out = Command::new(helper_path())
|
||||||
.args(["load", uid])
|
.args(["load", uid, auth])
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user