Compare commits

...

7 Commits

Author SHA1 Message Date
Bernd Schoolmann
b9a2c1608d
Merge pull request #152 from quexten/feature/background-status
Add background status support
2024-04-05 08:08:15 +02:00
Bernd Schoolmann
e087ffa422
Add error on missing ssh key 2024-04-05 08:06:43 +02:00
Bernd Schoolmann
1e1ec95fe3
Remove unused code 2024-04-05 08:06:31 +02:00
Bernd Schoolmann
f53a9a13ac
Add debug logging to keyhierarchy 2024-04-05 08:06:17 +02:00
Bernd Schoolmann
24f6907c0d
Fix sync error 2024-04-05 08:06:02 +02:00
Bernd Schoolmann
f798ef3825
Add error handling for encryption routines 2024-04-05 08:05:35 +02:00
Bernd Schoolmann
bd635d8b15
Add background status support 2024-04-05 02:45:23 +02:00
11 changed files with 163 additions and 67 deletions

View File

@ -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 {

View File

@ -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 {

View File

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

View File

@ -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...")
}

View File

@ -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) {

View 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

View File

@ -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)

View 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()

View File

@ -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)

View 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()

View File

@ -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: