mirror of
https://github.com/quexten/goldwarden.git
synced 2024-09-11 19:17:33 +03:00
Compare commits
7 Commits
6ac5754075
...
b9a2c1608d
Author | SHA1 | Date | |
---|---|---|---|
|
b9a2c1608d | ||
|
e087ffa422 | ||
|
1e1ec95fe3 | ||
|
f53a9a13ac | ||
|
24f6907c0d | ||
|
f798ef3825 | ||
|
bd635d8b15 |
@ -9,6 +9,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
@ -170,7 +171,7 @@ func isMacValid(message, messageMAC, key []byte) bool {
|
||||
return hmac.Equal(messageMAC, expectedMAC)
|
||||
}
|
||||
|
||||
func encryptAESCBC256(data, key []byte) (iv, ciphertext []byte, _ error) {
|
||||
func encryptAESCBC256(data, key []byte) (iv, ciphertext []byte, err error) {
|
||||
data = padPKCS7(data, aes.BlockSize)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
@ -182,12 +183,28 @@ func encryptAESCBC256(data, key []byte) (iv, ciphertext []byte, _ error) {
|
||||
if _, err := io.ReadFull(cryptorand.Reader, iv); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
iv = nil
|
||||
ciphertext = nil
|
||||
err = errors.New("error encrypting AES CBC 256 data")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, data)
|
||||
return iv, ciphertext, nil
|
||||
}
|
||||
|
||||
func decryptAESCBC256(iv, ciphertext, key []byte) ([]byte, error) {
|
||||
func decryptAESCBC256(iv, ciphertext, key []byte) (decryptedData []byte, err error) {
|
||||
ciphertextCopy := make([]byte, len(ciphertext))
|
||||
copy(ciphertextCopy, ciphertext)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -195,28 +212,50 @@ func decryptAESCBC256(iv, ciphertext, key []byte) ([]byte, error) {
|
||||
if len(iv) != aes.BlockSize {
|
||||
return nil, fmt.Errorf("iv length does not match AES block size")
|
||||
}
|
||||
if len(ciphertext)%aes.BlockSize != 0 {
|
||||
if len(ciphertextCopy)%aes.BlockSize != 0 {
|
||||
return nil, fmt.Errorf("ciphertext is not a multiple of AES block size")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
decryptedData = nil
|
||||
err = errors.New("error decrypting AES CBC 256 data")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, ciphertext) // decrypt in-place
|
||||
data, err := unpadPKCS7(ciphertext, aes.BlockSize)
|
||||
mode.CryptBlocks(ciphertextCopy, ciphertextCopy) // decrypt in-place
|
||||
data, err := unpadPKCS7(ciphertextCopy, aes.BlockSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
|
||||
resultBuffer := make([]byte, len(data))
|
||||
copy(resultBuffer, data)
|
||||
return resultBuffer, nil
|
||||
}
|
||||
|
||||
func unpadPKCS7(src []byte, size int) ([]byte, error) {
|
||||
n := src[len(src)-1]
|
||||
if len(src)%size != 0 {
|
||||
return nil, fmt.Errorf("expected PKCS7 padding for block size %d, but have %d bytes", size, len(src))
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
|
||||
n := srcCopy[len(srcCopy)-1]
|
||||
if len(srcCopy)%size != 0 {
|
||||
return nil, fmt.Errorf("expected PKCS7 padding for block size %d, but have %d bytes", size, len(srcCopy))
|
||||
}
|
||||
if len(src) <= int(n) {
|
||||
return nil, fmt.Errorf("cannot unpad %d bytes out of a total of %d", n, len(src))
|
||||
if len(srcCopy) <= int(n) {
|
||||
return nil, fmt.Errorf("cannot unpad %d bytes out of a total of %d", n, len(srcCopy))
|
||||
}
|
||||
src = src[:len(src)-int(n)]
|
||||
return src, nil
|
||||
srcCopy = srcCopy[:len(srcCopy)-int(n)]
|
||||
|
||||
resultCopy := make([]byte, len(srcCopy))
|
||||
copy(resultCopy, srcCopy)
|
||||
|
||||
return resultCopy, nil
|
||||
}
|
||||
|
||||
func padPKCS7(src []byte, size int) []byte {
|
||||
|
@ -2,8 +2,6 @@ package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
@ -12,12 +10,14 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/quexten/goldwarden/logging"
|
||||
)
|
||||
|
||||
var cryptoLog = logging.GetLogger("Goldwarden", "Crypto")
|
||||
|
||||
type EncString struct {
|
||||
Type EncStringType
|
||||
IV, CT, MAC []byte
|
||||
@ -131,11 +131,6 @@ func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(encKeyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch s.Type {
|
||||
case AesCbc256_B64, AesCbc256_HmacSha256_B64:
|
||||
break
|
||||
@ -157,17 +152,11 @@ func DecryptWith(s EncString, key SymmetricEncryptionKey) ([]byte, error) {
|
||||
return nil, fmt.Errorf("decrypt: cipher of unsupported type %q", s.Type)
|
||||
}
|
||||
|
||||
if len(s.IV) != block.BlockSize() {
|
||||
return nil, fmt.Errorf("decrypt: invalid IV length, expected %d, got %d", block.BlockSize(), len(s.IV))
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, s.IV)
|
||||
dst := make([]byte, len(s.CT))
|
||||
mode.CryptBlocks(dst, s.CT)
|
||||
dst, err = unpadPKCS7(dst, aes.BlockSize)
|
||||
dst, err := decryptAESCBC256(s.IV, s.CT, encKeyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
@ -188,19 +177,13 @@ func EncryptWith(data []byte, encType EncStringType, key SymmetricEncryptionKey)
|
||||
return s, fmt.Errorf("encrypt: unsupported cipher type %q", s.Type)
|
||||
}
|
||||
s.Type = encType
|
||||
data = padPKCS7(data, aes.BlockSize)
|
||||
|
||||
block, err := aes.NewCipher(encKeyData)
|
||||
iv, ciphertext, err := encryptAESCBC256(data, encKeyData)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.IV = make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, s.IV); err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.CT = make([]byte, len(data))
|
||||
mode := cipher.NewCBCEncrypter(block, s.IV)
|
||||
mode.CryptBlocks(s.CT, data)
|
||||
s.CT = ciphertext
|
||||
s.IV = iv
|
||||
|
||||
if encType == AesCbc256_HmacSha256_B64 {
|
||||
if len(macKeyData) == 0 {
|
||||
|
@ -49,6 +49,7 @@ func InitKeyringFromMasterKey(keyring *Keyring, accountKey EncString, accountPri
|
||||
|
||||
keyring.UnlockWithAccountKey(accountSymmetricKey)
|
||||
|
||||
keyringLog.Info("Decrypting account private key")
|
||||
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -65,6 +66,7 @@ func InitKeyringFromMasterKey(keyring *Keyring, accountKey EncString, accountPri
|
||||
|
||||
func InitKeyringFromUserSymmetricKey(keyring *Keyring, accountSymmetricKey SymmetricEncryptionKey, accountPrivateKey EncString, orgKeys map[string]string) error {
|
||||
keyring.UnlockWithAccountKey(accountSymmetricKey)
|
||||
keyringLog.Info("Decrypting account private key")
|
||||
pkcs8PrivateKey, err := DecryptWith(accountPrivateKey, accountSymmetricKey)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -16,20 +16,12 @@ import (
|
||||
|
||||
var log = logging.GetLogger("Goldwarden", "Bitwarden API")
|
||||
|
||||
const path = "/.cache/goldwarden-vault.json"
|
||||
|
||||
func Sync(ctx context.Context, config *config.Config) (models.SyncData, error) {
|
||||
var sync models.SyncData
|
||||
if err := authenticatedHTTPGet(ctx, config.ConfigFile.ApiUrl+"/sync", &sync); err != nil {
|
||||
return models.SyncData{}, fmt.Errorf("could not sync: %v", err)
|
||||
}
|
||||
|
||||
home, _ := os.UserHomeDir()
|
||||
err := WriteVault(sync, home+path)
|
||||
if err != nil {
|
||||
return sync, err
|
||||
}
|
||||
|
||||
return sync, nil
|
||||
}
|
||||
|
||||
@ -38,15 +30,7 @@ func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config,
|
||||
sync, err := Sync(ctx, config)
|
||||
if err != nil {
|
||||
log.Error("Could not sync: %v", err)
|
||||
if allowCache {
|
||||
home, _ := os.UserHomeDir()
|
||||
sync, err = ReadVault(home + path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
log.Info("Sync successful, initializing keyring and vault...")
|
||||
}
|
||||
|
@ -110,6 +110,10 @@ func (vaultAgent vaultAgent) SignWithFlags(key ssh.PublicKey, data []byte, flags
|
||||
}
|
||||
}
|
||||
|
||||
if sshKey == nil {
|
||||
return nil, errors.New("key not found")
|
||||
}
|
||||
|
||||
isGit := false
|
||||
magicHeader := []byte("SSHSIG\x00\x00\x00\x03git")
|
||||
if bytes.HasPrefix(data, magicHeader) {
|
||||
|
20
gui/src/linux/flatpak/api.py
Normal file
20
gui/src/linux/flatpak/api.py
Normal file
@ -0,0 +1,20 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
is_flatpak = os.path.exists("/.flatpak-info")
|
||||
|
||||
def register_autostart(autostart: bool):
|
||||
if is_flatpak:
|
||||
print("Running in flatpak, registering with background portal for autostart.")
|
||||
try:
|
||||
subprocess.Popen(["python3", "/app/bin/src/linux/flatpak/autostart.py"], start_new_session=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def set_status(status: str):
|
||||
if is_flatpak:
|
||||
try:
|
||||
subprocess.Popen(["python3", "/app/bin/src/linux/flatpak/status.py", status], start_new_session=True)
|
||||
except:
|
||||
pass
|
@ -1,17 +1,16 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('Adw', '1')
|
||||
import gc
|
||||
from gi.repository import Gtk, Adw, GLib, Gio
|
||||
from gi.repository import GLib, Gio
|
||||
from random import randint
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from threading import Timer
|
||||
|
||||
|
||||
def receive_autostart(self, *args):
|
||||
print("autostart enabled..!?")
|
||||
print(args)
|
||||
os._exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
def request_autostart():
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
37
gui/src/linux/flatpak/status.py
Normal file
37
gui/src/linux/flatpak/status.py
Normal file
@ -0,0 +1,37 @@
|
||||
"""
|
||||
Script to set the status of the background process.
|
||||
Run separately so that gtk dependencies don't stick around in memory.
|
||||
"""
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('Adw', '1')
|
||||
from gi.repository import GLib, Gio
|
||||
import sys
|
||||
|
||||
def set_status(message):
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
proxy = Gio.DBusProxy.new_sync(
|
||||
bus,
|
||||
Gio.DBusProxyFlags.NONE,
|
||||
None,
|
||||
'org.freedesktop.portal.Desktop',
|
||||
'/org/freedesktop/portal/desktop',
|
||||
'org.freedesktop.portal.Background',
|
||||
None,
|
||||
)
|
||||
|
||||
options = {
|
||||
'message': GLib.Variant('s', message),
|
||||
}
|
||||
|
||||
try:
|
||||
request = proxy.SetStatus('(a{sv})', options)
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
set_status(sys.argv[1])
|
||||
|
||||
loop = GLib.MainLoop()
|
||||
loop.run()
|
@ -4,6 +4,7 @@ import subprocess
|
||||
from tendo import singleton
|
||||
from .monitors import dbus_autofill_monitor
|
||||
from .monitors import dbus_monitor
|
||||
from .monitors import locked_monitor
|
||||
import sys
|
||||
from src.services import goldwarden
|
||||
from src.services import pinentry
|
||||
@ -12,6 +13,7 @@ import os
|
||||
import secrets
|
||||
import time
|
||||
import os
|
||||
import src.linux.flatpak.api as flatpak_api
|
||||
|
||||
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir))
|
||||
is_flatpak = os.path.exists("/.flatpak-info")
|
||||
@ -37,6 +39,7 @@ def main():
|
||||
# start daemons
|
||||
dbus_autofill_monitor.run_daemon(token) # todo: remove after migration
|
||||
dbus_monitor.run_daemon(token)
|
||||
locked_monitor.run_daemon(token)
|
||||
pinentry.daemonize()
|
||||
|
||||
if not "--hidden" in sys.argv:
|
||||
@ -44,13 +47,7 @@ def main():
|
||||
p.stdin.write(f"{token}\n".encode())
|
||||
p.stdin.flush()
|
||||
|
||||
if is_flatpak:
|
||||
# to autostart the appes
|
||||
try:
|
||||
print("Enabling autostart...")
|
||||
subprocess.Popen(["python3", "-m", "src.linux.background"], cwd=root_path, start_new_session=True)
|
||||
except Exception as e:
|
||||
pass
|
||||
flatpak_api.register_autostart(True)
|
||||
|
||||
while True:
|
||||
time.sleep(60)
|
||||
|
30
gui/src/linux/monitors/locked_monitor.py
Normal file
30
gui/src/linux/monitors/locked_monitor.py
Normal file
@ -0,0 +1,30 @@
|
||||
from gi.repository import Gtk
|
||||
import dbus
|
||||
import dbus.service
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from threading import Thread
|
||||
import subprocess
|
||||
import os
|
||||
from src.services import goldwarden
|
||||
import time
|
||||
import src.linux.flatpak.api as flatpak_api
|
||||
|
||||
daemon_token = None
|
||||
|
||||
def daemon():
|
||||
time.sleep(5)
|
||||
goldwarden.create_authenticated_connection(daemon_token)
|
||||
while True:
|
||||
status = goldwarden.get_vault_status()
|
||||
if status["locked"]:
|
||||
flatpak_api.set_status("Locked")
|
||||
else:
|
||||
flatpak_api.set_status("Unlocked")
|
||||
time.sleep(1)
|
||||
|
||||
def run_daemon(token):
|
||||
print("running locked status daemon")
|
||||
global daemon_token
|
||||
daemon_token = token
|
||||
thread = Thread(target=daemon)
|
||||
thread.start()
|
@ -37,6 +37,7 @@ if BINARY_PATH is None:
|
||||
authenticated_connection = None
|
||||
|
||||
def create_authenticated_connection(token):
|
||||
print("create authenticated connection")
|
||||
global authenticated_connection
|
||||
authenticated_connection = subprocess.Popen([f"{BINARY_PATH}", "session"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
if not token == None:
|
||||
|
Loading…
Reference in New Issue
Block a user