Port bw-bio-handler for browser biometrics
This commit is contained in:
parent
fbf843d090
commit
350124c7e6
15 changed files with 596 additions and 6 deletions
36
agent/actions/browserbiometrics.go
Normal file
36
agent/actions/browserbiometrics.go
Normal file
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
68
browserbiometrics/communication.go
Normal file
68
browserbiometrics/communication.go
Normal file
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
118
browserbiometrics/crypto.go
Normal file
118
browserbiometrics/crypto.go
Normal file
|
|
@ -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
|
||||
}
|
||||
12
browserbiometrics/logging/nooplogger.go
Normal file
12
browserbiometrics/logging/nooplogger.go
Normal file
|
|
@ -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{}) {
|
||||
}
|
||||
64
browserbiometrics/main.go
Normal file
64
browserbiometrics/main.go
Normal file
|
|
@ -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
|
||||
}
|
||||
23
browserbiometrics/manifests.go
Normal file
23
browserbiometrics/manifests.go
Normal file
|
|
@ -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/"
|
||||
]
|
||||
}`
|
||||
45
browserbiometrics/messages.go
Normal file
45
browserbiometrics/messages.go
Normal file
|
|
@ -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"`
|
||||
}
|
||||
136
browserbiometrics/protocol.go
Normal file
136
browserbiometrics/protocol.go
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
34
ipc/ipc.go
34
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
|
||||
}
|
||||
|
|
|
|||
9
main.go
9
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,4 +40,13 @@
|
|||
<allow_active>auth_self</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
<action id="com.quexten.goldwarden.browserbiometrics">
|
||||
<description>Browser Biometrics</description>
|
||||
<message>Authenticate to allow Goldwarden to unlock your browser.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_self</allow_any>
|
||||
<allow_inactive>auth_self</allow_inactive>
|
||||
<allow_active>auth_self</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
|
|
|
|||
Loading…
Reference in a new issue