ente/cli/pkg/secrets/key_holder.go
2024-03-01 12:39:01 +05:30

76 lines
3 KiB
Go

package secrets
import (
"context"
"fmt"
"github.com/ente-io/cli/internal/api"
eCrypto "github.com/ente-io/cli/internal/crypto"
"github.com/ente-io/cli/pkg/model"
"github.com/ente-io/cli/utils/encoding"
)
type KeyHolder struct {
// DeviceKey is the key used to encrypt/decrypt the data while storing sensitive
// information on the disk. Usually, it should be stored in OS Keychain.
DeviceKey []byte
AccountSecrets map[string]*model.AccSecretInfo
CollectionKeys map[string][]byte
}
func NewKeyHolder(deviceKey []byte) *KeyHolder {
return &KeyHolder{
AccountSecrets: make(map[string]*model.AccSecretInfo),
CollectionKeys: make(map[string][]byte),
DeviceKey: deviceKey,
}
}
// LoadSecrets loads the secrets for a given account using the provided CLI key.
// It decrypts the token key, master key, and secret key using the CLI key.
// The decrypted keys and the decoded public key are stored in the AccountSecrets map using the account key as the map key.
// It returns the account secret information or an error if the decryption fails.
func (k *KeyHolder) LoadSecrets(account model.Account) (*model.AccSecretInfo, error) {
tokenKey := account.Token.MustDecrypt(k.DeviceKey)
masterKey := account.MasterKey.MustDecrypt(k.DeviceKey)
secretKey := account.SecretKey.MustDecrypt(k.DeviceKey)
k.AccountSecrets[account.AccountKey()] = &model.AccSecretInfo{
Token: tokenKey,
MasterKey: masterKey,
SecretKey: secretKey,
PublicKey: encoding.DecodeBase64(account.PublicKey),
}
return k.AccountSecrets[account.AccountKey()], nil
}
func (k *KeyHolder) GetAccountSecretInfo(ctx context.Context) *model.AccSecretInfo {
accountKey := ctx.Value("account_key").(string)
return k.AccountSecrets[accountKey]
}
// GetCollectionKey retrieves the key for a given collection.
// It first fetches the account secret information from the context.
// If the collection owner's ID matches the user ID from the context, it decrypts the collection key using the master key.
// If the collection is shared (i.e., the owner's ID does not match the user ID), it decrypts the collection key using the public and secret keys.
// It returns the decrypted collection key or an error if the decryption fails.
func (k *KeyHolder) GetCollectionKey(ctx context.Context, collection api.Collection) ([]byte, error) {
accSecretInfo := k.GetAccountSecretInfo(ctx)
userID := ctx.Value("user_id").(int64)
if collection.Owner.ID == userID {
collKey, err := eCrypto.SecretBoxOpen(
encoding.DecodeBase64(collection.EncryptedKey),
encoding.DecodeBase64(collection.KeyDecryptionNonce),
accSecretInfo.MasterKey)
if err != nil {
return nil, fmt.Errorf("collection %d key drive failed %s", collection.ID, err)
}
return collKey, nil
} else {
collKey, err := eCrypto.SealedBoxOpen(encoding.DecodeBase64(collection.EncryptedKey),
accSecretInfo.PublicKey, accSecretInfo.SecretKey)
if err != nil {
return nil, fmt.Errorf("shared collection %d key drive failed %s", collection.ID, err)
}
return collKey, nil
}
}