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
+
+