From 350124c7e65b36e86df89cb65478e227761fb5f7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 17 Jul 2023 05:02:29 +0200 Subject: [PATCH] Port bw-bio-handler for browser biometrics --- agent/actions/browserbiometrics.go | 36 +++++++ agent/actions/login.go | 1 + agent/bitwarden/sync.go | 6 +- agent/config/config.go | 33 ++++++ agent/systemauth/biometrics.go | 8 +- browserbiometrics/communication.go | 68 ++++++++++++ browserbiometrics/crypto.go | 118 ++++++++++++++++++++ browserbiometrics/logging/nooplogger.go | 12 +++ browserbiometrics/main.go | 64 +++++++++++ browserbiometrics/manifests.go | 23 ++++ browserbiometrics/messages.go | 45 ++++++++ browserbiometrics/protocol.go | 136 ++++++++++++++++++++++++ ipc/ipc.go | 34 ++++++ main.go | 9 ++ resources/com.quexten.goldwarden.policy | 9 ++ 15 files changed, 596 insertions(+), 6 deletions(-) create mode 100644 agent/actions/browserbiometrics.go create mode 100644 browserbiometrics/communication.go create mode 100644 browserbiometrics/crypto.go create mode 100644 browserbiometrics/logging/nooplogger.go create mode 100644 browserbiometrics/main.go create mode 100644 browserbiometrics/manifests.go create mode 100644 browserbiometrics/messages.go create mode 100644 browserbiometrics/protocol.go diff --git a/agent/actions/browserbiometrics.go b/agent/actions/browserbiometrics.go new file mode 100644 index 0000000..df0c918 --- /dev/null +++ b/agent/actions/browserbiometrics.go @@ -0,0 +1,36 @@ +package actions + +import ( + "encoding/base64" + "fmt" + + "github.com/quexten/goldwarden/agent/config" + "github.com/quexten/goldwarden/agent/sockets" + "github.com/quexten/goldwarden/agent/systemauth" + "github.com/quexten/goldwarden/agent/vault" + "github.com/quexten/goldwarden/ipc" +) + +func handleGetBiometricsKey(request ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, ctx sockets.CallingContext) (response interface{}, err error) { + if approved, err := systemauth.GetApproval("Approve Credential Access", fmt.Sprintf("%s on %s>%s>%s is trying to access your vault encryption key for browser biometric unlock.", ctx.UserName, ctx.GrandParentProcessName, ctx.ParentProcessName, ctx.ProcessName)); err != nil || !approved { + response, err = ipc.IPCMessageFromPayload(ipc.ActionResponse{ + Success: false, + Message: "not approved", + }) + if err != nil { + return nil, err + } + return response, nil + } + + masterKey, err := cfg.GetMasterKey() + masterKeyB64 := base64.StdEncoding.EncodeToString(masterKey) + response, err = ipc.IPCMessageFromPayload(ipc.GetBiometricsKeyResponse{ + Key: masterKeyB64, + }) + return response, err +} + +func init() { + AgentActionsRegistry.Register(ipc.IPCMessageTypeGetBiometricsKeyRequest, ensureEverything(systemauth.BrowserBiometrics, handleGetBiometricsKey)) +} diff --git a/agent/actions/login.go b/agent/actions/login.go index 5c9a4ed..2379ccb 100644 --- a/agent/actions/login.go +++ b/agent/actions/login.go @@ -71,6 +71,7 @@ func handleLogin(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, cal cfg.SetUserSymmetricKey(vault.Keyring.AccountKey.Bytes()) cfg.SetMasterPasswordHash([]byte(masterpasswordHash)) + cfg.SetMasterKey([]byte(masterKey.GetBytes())) protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(vault.Keyring.AccountKey.Bytes()) if err != nil { var payload = ipc.ActionResponse{ diff --git a/agent/bitwarden/sync.go b/agent/bitwarden/sync.go index 70743b4..118f7d1 100644 --- a/agent/bitwarden/sync.go +++ b/agent/bitwarden/sync.go @@ -21,7 +21,7 @@ func Sync(ctx context.Context, config *config.Config) (models.SyncData, error) { return sync, nil } -func SyncToVault(ctx context.Context, vault *vault.Vault, config *config.Config, masterKey *crypto.SymmetricEncryptionKey) error { +func SyncToVault(ctx context.Context, vault *vault.Vault, config *config.Config, userSymmetricKey *crypto.SymmetricEncryptionKey) error { log.Info("Performing full sync...") sync, err := Sync(ctx, config) @@ -29,13 +29,13 @@ func SyncToVault(ctx context.Context, vault *vault.Vault, config *config.Config, return err } - if masterKey != nil { + if userSymmetricKey != nil { var orgKeys map[string]string = make(map[string]string) for _, org := range sync.Profile.Organizations { orgId := org.Id.String() orgKeys[orgId] = org.Key } - crypto.InitKeyringFromUserSymmetricKey(vault.Keyring, *masterKey, sync.Profile.PrivateKey, orgKeys) + crypto.InitKeyringFromUserSymmetricKey(vault.Keyring, *userSymmetricKey, sync.Profile.PrivateKey, orgKeys) } vault.Clear() diff --git a/agent/config/config.go b/agent/config/config.go index 874a13c..6d60fd6 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -35,6 +35,7 @@ type ConfigFile struct { EncryptedToken string EncryptedUserSymmetricKey string EncryptedMasterPasswordHash string + EncryptedMasterKey string } type LoginToken struct { @@ -63,6 +64,7 @@ func DefaultConfig() Config { EncryptedToken: "", EncryptedUserSymmetricKey: "", EncryptedMasterPasswordHash: "", + EncryptedMasterKey: "", }, sync.Mutex{}, } @@ -111,6 +113,7 @@ func (c *Config) Purge() { c.ConfigFile.EncryptedToken = "" c.ConfigFile.EncryptedUserSymmetricKey = "" c.ConfigFile.ConfigKeyHash = "" + c.ConfigFile.EncryptedMasterKey = "" c.key = memguard.NewBuffer(32) } @@ -131,6 +134,7 @@ func (c *Config) UpdatePin(password string, write bool) { plaintextToken, err1 := c.decryptString(c.ConfigFile.EncryptedToken) plaintextUserSymmetricKey, err3 := c.decryptString(c.ConfigFile.EncryptedUserSymmetricKey) plaintextEncryptedMasterPasswordHash, err4 := c.decryptString(c.ConfigFile.EncryptedMasterPasswordHash) + plaintextMasterKey, err5 := c.decryptString(c.ConfigFile.EncryptedMasterKey) c.key = memguard.NewBufferFromBytes(newKey) @@ -143,6 +147,9 @@ func (c *Config) UpdatePin(password string, write bool) { if err4 == nil { c.ConfigFile.EncryptedMasterPasswordHash, err4 = c.encryptString(plaintextEncryptedMasterPasswordHash) } + if err5 == nil { + c.ConfigFile.EncryptedMasterKey, err5 = c.encryptString(plaintextMasterKey) + } if write { c.WriteConfig() @@ -240,6 +247,32 @@ func (c *Config) SetMasterPasswordHash(hash []byte) error { return nil } +func (c *Config) GetMasterKey() ([]byte, error) { + if c.IsLocked() { + return []byte{}, errors.New("config is locked") + } + decrypted, err := c.decryptString(c.ConfigFile.EncryptedMasterKey) + if err != nil { + return []byte{}, err + } + return []byte(decrypted), nil +} + +func (c *Config) SetMasterKey(key []byte) error { + if c.IsLocked() { + return errors.New("config is locked") + } + encryptedKey, err := c.encryptString(string(key)) + if err != nil { + return err + } + // c.mu.Lock() + c.ConfigFile.EncryptedMasterKey = encryptedKey + // c.mu.Unlock() + c.WriteConfig() + return nil +} + func (c *Config) encryptString(data string) (string, error) { if c.IsLocked() { return "", errors.New("config is locked") diff --git a/agent/systemauth/biometrics.go b/agent/systemauth/biometrics.go index 47e2c08..0099be3 100644 --- a/agent/systemauth/biometrics.go +++ b/agent/systemauth/biometrics.go @@ -10,9 +10,11 @@ var log = llamalog.NewLogger("Goldwarden", "Systemauth") type Approval string const ( - AccessCredential Approval = "com.quexten.goldwarden.accesscredential" - ChangePin Approval = "com.quexten.goldwarden.changepin" - SSHKey Approval = "com.quexten.goldwarden.usesshkey" + AccessCredential Approval = "com.quexten.goldwarden.accesscredential" + ChangePin Approval = "com.quexten.goldwarden.changepin" + SSHKey Approval = "com.quexten.goldwarden.usesshkey" + ModifyVault Approval = "com.quexten.goldwarden.modifyvault" + BrowserBiometrics Approval = "com.quexten.goldwarden.browserbiometrics" ) func (a Approval) String() string { diff --git a/browserbiometrics/communication.go b/browserbiometrics/communication.go new file mode 100644 index 0000000..534cc6d --- /dev/null +++ b/browserbiometrics/communication.go @@ -0,0 +1,68 @@ +package browserbiometrics + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "os" + "unsafe" + + "github.com/quexten/goldwarden/browserbiometrics/logging" +) + +const bufferSize = 8192 * 8 + +var nativeEndian binary.ByteOrder + +func setupCommunication() { + // determine native endianess + var one int16 = 1 + b := (*byte)(unsafe.Pointer(&one)) + if *b == 0 { + nativeEndian = binary.BigEndian + } else { + nativeEndian = binary.LittleEndian + } +} + +func dataToBytes(msg SendMessage) []byte { + byteMsg, err := json.Marshal(msg) + if err != nil { + logging.Panicf("Unable to marshal OutgoingMessage struct to slice of bytes: " + err.Error()) + } + return byteMsg +} + +func writeMessageLength(msg []byte) { + err := binary.Write(os.Stdout, nativeEndian, uint32(len(msg))) + if err != nil { + logging.Panicf("Unable to write message length to Stdout: " + err.Error()) + } +} + +func readMessageLength(msg []byte) int { + var length uint32 + buf := bytes.NewBuffer(msg) + err := binary.Read(buf, nativeEndian, &length) + if err != nil { + logging.Panicf("Unable to read bytes representing message length:" + err.Error()) + } + return int(length) +} + +func send(msg SendMessage) { + byteMsg := dataToBytes(msg) + logging.Debugf("Sending message: " + string(byteMsg)) + writeMessageLength(byteMsg) + + var msgBuf bytes.Buffer + _, err := msgBuf.Write(byteMsg) + if err != nil { + logging.Panicf(err.Error()) + } + + _, err = msgBuf.WriteTo(os.Stdout) + if err != nil { + logging.Panicf(err.Error()) + } +} diff --git a/browserbiometrics/crypto.go b/browserbiometrics/crypto.go new file mode 100644 index 0000000..4809b76 --- /dev/null +++ b/browserbiometrics/crypto.go @@ -0,0 +1,118 @@ +package browserbiometrics + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "errors" + "io" +) + +var ( + ErrInvalidBlockSize = errors.New("invalid blocksize") + ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)") + ErrInvalidPKCS7Padding = errors.New("invalid padding on input") +) + +func pkcs7Pad(b []byte, blocksize int) ([]byte, error) { + if blocksize <= 0 { + return nil, ErrInvalidBlockSize + } + if b == nil || len(b) == 0 { + return nil, ErrInvalidPKCS7Data + } + n := blocksize - (len(b) % blocksize) + pb := make([]byte, len(b)+n) + copy(pb, b) + copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) + return pb, nil +} + +func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) { + if blocksize <= 0 { + return nil, ErrInvalidBlockSize + } + if b == nil || len(b) == 0 { + return nil, ErrInvalidPKCS7Data + } + if len(b)%blocksize != 0 { + return nil, ErrInvalidPKCS7Padding + } + c := b[len(b)-1] + n := int(c) + if n == 0 || n > len(b) { + return nil, ErrInvalidPKCS7Padding + } + for i := 0; i < n; i++ { + if b[len(b)-n+i] != c { + return nil, ErrInvalidPKCS7Padding + } + } + return b[:len(b)-n], nil +} + +func decryptStringSymmetric(key []byte, ivb64 string, data string) string { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + iv, _ := base64.StdEncoding.DecodeString(ivb64) + ciphertext, _ := base64.StdEncoding.DecodeString(data) + bm := cipher.NewCBCDecrypter(block, iv) + bm.CryptBlocks(ciphertext, ciphertext) + ciphertext, _ = pkcs7Unpad(ciphertext, aes.BlockSize) + + return string(ciphertext) +} + +func encryptStringSymmetric(key []byte, data []byte) EncryptedString { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + data, _ = pkcs7Pad(data, block.BlockSize()) + ciphertext := make([]byte, aes.BlockSize+len(data)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic(err) + } + bm := cipher.NewCBCEncrypter(block, iv) + bm.CryptBlocks(ciphertext[aes.BlockSize:], data) + + return EncryptedString{ + IV: base64.StdEncoding.EncodeToString(ciphertext[:aes.BlockSize]), + Data: base64.StdEncoding.EncodeToString(ciphertext[aes.BlockSize:]), + EncType: 0, + } +} + +func generateTransportKey() []byte { + key := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, key); err != nil { + panic(err) + } + return key +} + +func rsaEncrypt(keyB64 string, message []byte) (string, error) { + publicKey, err := base64.StdEncoding.DecodeString(keyB64) + if err != nil { + return "", err + } + + test, err := x509.ParsePKIXPublicKey(publicKey) + if err != nil { + return "", err + } + rsatest := test.(*rsa.PublicKey) + oaepDigest := sha1.New() + ciphertext, _ := rsa.EncryptOAEP(oaepDigest, rand.Reader, rsatest, message, []byte{}) + b64cipherText := base64.StdEncoding.EncodeToString(ciphertext) + + return b64cipherText, nil +} diff --git a/browserbiometrics/logging/nooplogger.go b/browserbiometrics/logging/nooplogger.go new file mode 100644 index 0000000..a769ab2 --- /dev/null +++ b/browserbiometrics/logging/nooplogger.go @@ -0,0 +1,12 @@ +//go:build !logging + +package logging + +func Debugf(format string, args ...interface{}) { +} + +func Errorf(format string, args ...interface{}) { +} + +func Panicf(format string, args ...interface{}) { +} diff --git a/browserbiometrics/main.go b/browserbiometrics/main.go new file mode 100644 index 0000000..051f329 --- /dev/null +++ b/browserbiometrics/main.go @@ -0,0 +1,64 @@ +package browserbiometrics + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +const appID = "com.quexten.bw-bio-handler" + +var transportKey []byte + +func Main() { + if os.Args[1] == "install" { + var err error + err = detectAndInstallBrowsers(".config") + if err != nil { + panic("Failed to detect browsers: " + err.Error()) + } + err = detectAndInstallBrowsers(".mozilla") + if err != nil { + panic("Failed to detect browsers: " + err.Error()) + } + return + } + + transportKey = generateTransportKey() + + setupCommunication() + readLoop() +} + +func detectAndInstallBrowsers(startPath string) error { + home := os.Getenv("HOME") + err := filepath.Walk(home+"/"+startPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + var tempPath string + if !strings.HasPrefix(path, home) { + return nil + } else { + tempPath = strings.TrimPrefix(path, home) + } + if strings.Count(tempPath, "/") > 3 { + return nil + } + + if info.IsDir() && info.Name() == "native-messaging-hosts" { + fmt.Printf("Found mozilla-like browser: %s\n", path) + manifest := strings.Replace(templateMozilla, "PATH", os.Getenv("PWD")+"/bw-bio-handler", 1) + err = os.WriteFile(path+"/com.8bit.bitwarden.json", []byte(manifest), 0644) + } else if info.IsDir() && info.Name() == "NativeMessagingHosts" { + fmt.Printf("Found chrome-like browser: %s\n", path) + manifest := strings.Replace(templateChrome, "PATH", os.Getenv("PWD")+"/bw-bio-handler", 1) + err = os.WriteFile(path+"/com.8bit.bitwarden.json", []byte(manifest), 0644) + } + + return err + }) + + return err +} diff --git a/browserbiometrics/manifests.go b/browserbiometrics/manifests.go new file mode 100644 index 0000000..cab481f --- /dev/null +++ b/browserbiometrics/manifests.go @@ -0,0 +1,23 @@ +package browserbiometrics + +const templateMozilla = `{ + "name": "com.8bit.bitwarden", + "description": "Bitwarden desktop <-> browser bridge", + "path": "PATH", + "type": "stdio", + "allowed_extensions": [ + "{446900e4-71c2-419f-a6a7-df9c091e268b}" + ] +}` + +const templateChrome = `{ + "name": "com.8bit.bitwarden", + "description": "Bitwarden desktop <-> browser bridge", + "path": "PATH", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://nngceckbapebfimnlniiiahkandclblb/", + "chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh/", + "chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn/" + ] + }` diff --git a/browserbiometrics/messages.go b/browserbiometrics/messages.go new file mode 100644 index 0000000..c28c804 --- /dev/null +++ b/browserbiometrics/messages.go @@ -0,0 +1,45 @@ +package browserbiometrics + +// top level messages +type GenericRecvMessage struct { + AppID string `json:"appId"` + Message interface{} `json:"message"` +} + +type UnencryptedRecvMessage struct { + AppID string `json:"appId"` + Message PayloadMessage `json:"message"` +} + +type EncryptedRecvMessage struct { + AppID string `json:"appId"` + Message EncryptedString `json:"message"` +} + +type ReceiveMessage struct { + Timestamp int64 `json:"timestamp"` + Command string `json:"command"` + Response string `json:"response"` + KeyB64 string `json:"keyB64"` +} + +type SendMessage struct { + Command string `json:"command"` + AppID string `json:"appId"` + SharedSecret string `json:"sharedSecret"` + Message EncryptedString `json:"message"` +} + +type EncryptedString struct { + IV string `json:"iv"` + Mac string `json:"mac"` + Data string `json:"data"` + EncType int `json:"encryptionType"` +} + +type PayloadMessage struct { + Command string `json:"command"` + UserId string `json:"userId"` + Timestamp int64 `json:"timestamp"` + PublicKey string `json:"publicKey"` +} diff --git a/browserbiometrics/protocol.go b/browserbiometrics/protocol.go new file mode 100644 index 0000000..7f3203f --- /dev/null +++ b/browserbiometrics/protocol.go @@ -0,0 +1,136 @@ +package browserbiometrics + +import ( + "bufio" + "encoding/json" + "io" + "os" + + "github.com/quexten/goldwarden/browserbiometrics/logging" + "github.com/quexten/goldwarden/client" + "github.com/quexten/goldwarden/ipc" +) + +func readLoop() { + v := bufio.NewReader(os.Stdin) + s := bufio.NewReaderSize(v, bufferSize) + + lengthBytes := make([]byte, 4) + lengthNum := int(0) + + send(SendMessage{ + Command: "connected", + AppID: appID, + }) + + for b, err := s.Read(lengthBytes); b > 0 && err == nil; b, err = s.Read(lengthBytes) { + lengthNum = readMessageLength(lengthBytes) + + content := make([]byte, lengthNum) + _, err := s.Read(content) + if err != nil && err != io.EOF { + logging.Panicf(err.Error()) + } + + parseMessage(content) + } +} + +func parseMessage(msg []byte) { + logging.Debugf("Received message: " + string(msg)) + + var genericMessage GenericRecvMessage + err := json.Unmarshal(msg, &genericMessage) + if err != nil { + logging.Panicf("Unable to unmarshal json to struct: " + err.Error()) + } + if _, ok := (genericMessage.Message.(map[string]interface{})["command"]); ok { + logging.Debugf("Message is unencrypted") + + var unmsg UnencryptedRecvMessage + err := json.Unmarshal(msg, &unmsg) + if err != nil { + logging.Panicf("Unable to unmarshal json to struct: " + err.Error()) + } + + handleUnencryptedMessage(unmsg) + } else { + logging.Debugf("Message is encrypted") + + var encmsg EncryptedRecvMessage + err := json.Unmarshal(msg, &encmsg) + if err != nil { + logging.Panicf("Unable to unmarshal json to struct: " + err.Error()) + } + + decryptedMessage := decryptStringSymmetric(transportKey, encmsg.Message.IV, encmsg.Message.Data) + var payloadMsg PayloadMessage + err = json.Unmarshal([]byte(decryptedMessage), &payloadMsg) + if err != nil { + logging.Panicf("Unable to unmarshal json to struct: " + err.Error()) + } + + handlePayloadMessage(payloadMsg, genericMessage.AppID) + } +} + +func handleUnencryptedMessage(msg UnencryptedRecvMessage) { + logging.Debugf("Received unencrypted message: %+v", msg.Message) + logging.Debugf(" with command: %s", msg.Message.Command) + + switch msg.Message.Command { + case "setupEncryption": + sharedSecret, err := rsaEncrypt(msg.Message.PublicKey, transportKey) + if err != nil { + logging.Panicf(err.Error()) + } + send(SendMessage{ + Command: "setupEncryption", + AppID: msg.AppID, + SharedSecret: sharedSecret, + }) + break + } +} +func handlePayloadMessage(msg PayloadMessage, appID string) { + logging.Debugf("Received unencrypted message: %+v", msg) + + switch msg.Command { + case "biometricUnlock": + logging.Debugf("Biometric unlock requested") + // logging.Debugf("Biometrics authorized: %t", isAuthorized) + result, err := client.SendToAgent(ipc.GetBiometricsKeyRequest{}) + if err != nil { + logging.Errorf("Unable to send message to agent: %s", err.Error()) + return + } + switch result.(type) { + case ipc.GetBiometricsKeyResponse: + if err != nil { + logging.Panicf(err.Error()) + } + + var key = result.(ipc.GetBiometricsKeyResponse).Key + var payloadMsg ReceiveMessage = ReceiveMessage{ + Command: "biometricUnlock", + Response: "unlocked", + Timestamp: msg.Timestamp, + KeyB64: key, + } + payloadStr, err := json.Marshal(payloadMsg) + if err != nil { + logging.Panicf(err.Error()) + } + logging.Debugf("Payload: %s", payloadStr) + + encStr := encryptStringSymmetric(transportKey, payloadStr) + send(SendMessage{ + AppID: appID, + Message: encStr, + }) + break + } + + break + } +} diff --git a/ipc/ipc.go b/ipc/ipc.go index c91cce5..4529fdd 100644 --- a/ipc/ipc.go +++ b/ipc/ipc.go @@ -47,6 +47,9 @@ const ( IPCMessageTypeSetAPIUrlRequest IPCMessageType = 30 IPCMessageTypeSetIdentityURLRequest IPCMessageType = 31 + + IPCMessageTypeGetBiometricsKeyRequest IPCMessageType = 8 + IPCMessageTypeGetBiometricsKeyResponse IPCMessageType = 9 ) type IPCMessage struct { @@ -194,6 +197,20 @@ func (m IPCMessage) ParsedPayload() interface{} { panic("Unmarshal: " + err.Error()) } return res + case IPCMessageTypeGetBiometricsKeyRequest: + var res GetBiometricsKeyRequest + err := json.Unmarshal(m.Payload, &res) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return res + case IPCMessageTypeGetBiometricsKeyResponse: + var res GetBiometricsKeyResponse + err := json.Unmarshal(m.Payload, &res) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return res default: return nil } @@ -326,6 +343,16 @@ func IPCMessageFromPayload(payload interface{}) (IPCMessage, error) { Type: IPCMessageListLoginsRequest, Payload: jsonBytes, }, nil + case GetBiometricsKeyRequest: + return IPCMessage{ + Type: IPCMessageTypeGetBiometricsKeyRequest, + Payload: jsonBytes, + }, nil + case GetBiometricsKeyResponse: + return IPCMessage{ + Type: IPCMessageTypeGetBiometricsKeyResponse, + Payload: jsonBytes, + }, nil default: payloadBytes, err := json.Marshal(payload) if err != nil { @@ -454,3 +481,10 @@ type SetIdentityURLRequest struct { type ListLoginsRequest struct { } + +type GetBiometricsKeyRequest struct { +} + +type GetBiometricsKeyResponse struct { + Key string +} diff --git a/main.go b/main.go index 69e029f..acebbcf 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,18 @@ package main import ( + "os" + "strings" + + "github.com/quexten/goldwarden/browserbiometrics" "github.com/quexten/goldwarden/cmd" ) func main() { + if len(os.Args) > 1 && strings.Contains(os.Args[1], "com.8bit.bitwarden.json") { + browserbiometrics.Main() + return + } + cmd.Execute() } diff --git a/resources/com.quexten.goldwarden.policy b/resources/com.quexten.goldwarden.policy index 641db10..b7fe542 100644 --- a/resources/com.quexten.goldwarden.policy +++ b/resources/com.quexten.goldwarden.policy @@ -40,4 +40,13 @@ auth_self + + Browser Biometrics + Authenticate to allow Goldwarden to unlock your browser. + + auth_self + auth_self + auth_self + +