2023-07-17 03:23:26 +02:00
|
|
|
package bitwarden
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-07-17 06:00:26 +02:00
|
|
|
"encoding/json"
|
2023-07-17 03:23:26 +02:00
|
|
|
"fmt"
|
2023-07-17 06:00:26 +02:00
|
|
|
"os"
|
2023-12-26 21:52:49 +01:00
|
|
|
"time"
|
2023-07-17 03:23:26 +02:00
|
|
|
|
|
|
|
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
|
|
|
"github.com/quexten/goldwarden/agent/bitwarden/models"
|
|
|
|
"github.com/quexten/goldwarden/agent/config"
|
|
|
|
"github.com/quexten/goldwarden/agent/vault"
|
2023-08-21 18:37:34 +02:00
|
|
|
"github.com/quexten/goldwarden/logging"
|
2023-07-17 03:23:26 +02:00
|
|
|
)
|
|
|
|
|
2023-08-21 18:37:34 +02:00
|
|
|
var log = logging.GetLogger("Goldwarden", "Bitwarden API")
|
2023-07-17 03:23:26 +02:00
|
|
|
|
2023-07-17 06:00:26 +02:00
|
|
|
const path = "/.cache/goldwarden-vault.json"
|
|
|
|
|
2023-07-17 03:23:26 +02:00
|
|
|
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)
|
|
|
|
}
|
2023-07-17 06:00:26 +02:00
|
|
|
|
|
|
|
home, _ := os.UserHomeDir()
|
|
|
|
WriteVault(sync, home+path)
|
2023-07-17 03:23:26 +02:00
|
|
|
return sync, nil
|
|
|
|
}
|
|
|
|
|
2023-07-17 06:00:26 +02:00
|
|
|
func DoFullSync(ctx context.Context, vault *vault.Vault, config *config.Config, userSymmetricKey *crypto.SymmetricEncryptionKey, allowCache bool) error {
|
2023-07-17 03:23:26 +02:00
|
|
|
log.Info("Performing full sync...")
|
|
|
|
sync, err := Sync(ctx, config)
|
|
|
|
if err != nil {
|
2023-12-22 10:44:49 +01:00
|
|
|
log.Error("Could not sync: %v", err)
|
2023-07-17 06:00:26 +02:00
|
|
|
if allowCache {
|
|
|
|
home, _ := os.UserHomeDir()
|
|
|
|
sync, err = ReadVault(home + path)
|
2023-12-22 12:01:21 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-17 06:00:26 +02:00
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
2023-12-22 10:44:49 +01:00
|
|
|
} else {
|
2023-12-26 21:52:49 +01:00
|
|
|
vault.SetLastSynced(time.Now().Unix())
|
2023-12-22 10:44:49 +01:00
|
|
|
log.Info("Sync successful, initializing keyring and vault...")
|
2023-07-17 03:23:26 +02:00
|
|
|
}
|
|
|
|
|
2023-07-17 20:58:36 +02:00
|
|
|
var orgKeys map[string]string = make(map[string]string)
|
2023-12-22 12:01:21 +01:00
|
|
|
log.Info("Reading %d org keys...", len(sync.Profile.Organizations))
|
2023-07-17 20:58:36 +02:00
|
|
|
for _, org := range sync.Profile.Organizations {
|
|
|
|
orgId := org.Id.String()
|
|
|
|
orgKeys[orgId] = org.Key
|
|
|
|
}
|
2023-07-17 05:02:29 +02:00
|
|
|
if userSymmetricKey != nil {
|
2023-12-22 10:44:49 +01:00
|
|
|
log.Info("Initializing keyring from user symmetric key...")
|
2023-07-17 05:02:29 +02:00
|
|
|
crypto.InitKeyringFromUserSymmetricKey(vault.Keyring, *userSymmetricKey, sync.Profile.PrivateKey, orgKeys)
|
2023-07-17 03:23:26 +02:00
|
|
|
}
|
|
|
|
|
2023-12-22 10:44:49 +01:00
|
|
|
log.Info("Clearing vault...")
|
2023-07-17 03:23:26 +02:00
|
|
|
vault.Clear()
|
2023-12-22 10:44:49 +01:00
|
|
|
log.Info("Adding %d ciphers to vault...", len(sync.Ciphers))
|
2023-07-17 03:23:26 +02:00
|
|
|
for _, cipher := range sync.Ciphers {
|
|
|
|
switch cipher.Type {
|
|
|
|
case models.CipherLogin:
|
|
|
|
vault.AddOrUpdateLogin(cipher)
|
|
|
|
case models.CipherNote:
|
|
|
|
vault.AddOrUpdateSecureNote(cipher)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-17 06:00:26 +02:00
|
|
|
|
|
|
|
func WriteVault(data models.SyncData, path string) error {
|
|
|
|
dataJson, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// write to disk
|
|
|
|
os.Remove(path)
|
|
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
_, err = file.Write(dataJson)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ReadVault(path string) (models.SyncData, error) {
|
|
|
|
file, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return models.SyncData{}, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
decoder := json.NewDecoder(file)
|
|
|
|
data := models.SyncData{}
|
|
|
|
err = decoder.Decode(&data)
|
|
|
|
if err != nil {
|
|
|
|
return models.SyncData{}, err
|
|
|
|
}
|
|
|
|
return data, nil
|
|
|
|
}
|