mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
Introduce EncryptContext and DecryptContext for AWS, Azure, GCP, PGP
and HashiCorp Vault Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
@@ -120,7 +120,15 @@ func (t TokenCredential) ApplyToMasterKey(key *MasterKey) {
|
||||
|
||||
// Encrypt takes a SOPS data key, encrypts it with Azure Key Vault, and stores
|
||||
// the result in the EncryptedKey field.
|
||||
//
|
||||
// Consider using EncryptContext instead.
|
||||
func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
return key.EncryptContext(context.Background(), dataKey)
|
||||
}
|
||||
|
||||
// EncryptContext takes a SOPS data key, encrypts it with Azure Key Vault, and stores
|
||||
// the result in the EncryptedKey field.
|
||||
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {
|
||||
token, err := key.getTokenCredential()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
|
||||
@@ -133,7 +141,7 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
return fmt.Errorf("failed to construct Azure Key Vault client to encrypt data: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.Encrypt(context.Background(), key.Name, key.Version, azkeys.KeyOperationParameters{
|
||||
resp, err := c.Encrypt(ctx, key.Name, key.Version, azkeys.KeyOperationParameters{
|
||||
Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256),
|
||||
Value: dataKey,
|
||||
}, nil)
|
||||
@@ -169,7 +177,15 @@ func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error {
|
||||
|
||||
// Decrypt decrypts the EncryptedKey field with Azure Key Vault and returns
|
||||
// the result.
|
||||
//
|
||||
// Consider using DecryptContext instead.
|
||||
func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
return key.DecryptContext(context.Background())
|
||||
}
|
||||
|
||||
// DecryptContext decrypts the EncryptedKey field with Azure Key Vault and returns
|
||||
// the result.
|
||||
func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) {
|
||||
token, err := key.getTokenCredential()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Decryption failed")
|
||||
@@ -188,7 +204,7 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
return nil, fmt.Errorf("failed to construct Azure Key Vault client to decrypt data: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.Decrypt(context.Background(), key.Name, key.Version, azkeys.KeyOperationParameters{
|
||||
resp, err := c.Decrypt(ctx, key.Name, key.Version, azkeys.KeyOperationParameters{
|
||||
Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256),
|
||||
Value: rawEncryptedKey,
|
||||
}, nil)
|
||||
|
||||
@@ -118,8 +118,16 @@ func (c CredentialJSON) ApplyToMasterKey(key *MasterKey) {
|
||||
|
||||
// Encrypt takes a SOPS data key, encrypts it with GCP KMS, and stores the
|
||||
// result in the EncryptedKey field.
|
||||
//
|
||||
// Consider using EncryptContext instead.
|
||||
func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
service, err := key.newKMSClient()
|
||||
return key.EncryptContext(context.Background(), dataKey)
|
||||
}
|
||||
|
||||
// EncryptContext takes a SOPS data key, encrypts it with GCP KMS, and stores the
|
||||
// result in the EncryptedKey field.
|
||||
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {
|
||||
service, err := key.newKMSClient(ctx)
|
||||
if err != nil {
|
||||
log.WithField("resourceID", key.ResourceID).Info("Encryption failed")
|
||||
return fmt.Errorf("cannot create GCP KMS service: %w", err)
|
||||
@@ -134,7 +142,6 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
Name: key.ResourceID,
|
||||
Plaintext: dataKey,
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := service.Encrypt(ctx, req)
|
||||
if err != nil {
|
||||
log.WithField("resourceID", key.ResourceID).Info("Encryption failed")
|
||||
@@ -169,8 +176,16 @@ func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error {
|
||||
|
||||
// Decrypt decrypts the EncryptedKey field with GCP KMS and returns
|
||||
// the result.
|
||||
//
|
||||
// Consider using DecryptContext instead.
|
||||
func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
service, err := key.newKMSClient()
|
||||
return key.DecryptContext(context.Background())
|
||||
}
|
||||
|
||||
// DecryptContext decrypts the EncryptedKey field with GCP KMS and returns
|
||||
// the result.
|
||||
func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) {
|
||||
service, err := key.newKMSClient(ctx)
|
||||
if err != nil {
|
||||
log.WithField("resourceID", key.ResourceID).Info("Decryption failed")
|
||||
return nil, fmt.Errorf("cannot create GCP KMS service: %w", err)
|
||||
@@ -193,7 +208,6 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
Name: key.ResourceID,
|
||||
Ciphertext: decodedCipher,
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := service.Decrypt(ctx, req)
|
||||
if err != nil {
|
||||
log.WithField("resourceID", key.ResourceID).Info("Decryption failed")
|
||||
@@ -232,7 +246,7 @@ func (key *MasterKey) TypeToIdentifier() string {
|
||||
// or credentialJSON, and/or grpcConn, falling back to environmental defaults.
|
||||
// It returns an error if the ResourceID is invalid, or if the setup of the
|
||||
// client fails.
|
||||
func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
|
||||
func (key *MasterKey) newKMSClient(ctx context.Context) (*kms.KeyManagementClient, error) {
|
||||
re := regexp.MustCompile(`^projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+$`)
|
||||
matches := re.FindStringSubmatch(key.ResourceID)
|
||||
if matches == nil {
|
||||
@@ -265,7 +279,6 @@ func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
|
||||
opts = append(opts, option.WithGRPCConn(key.grpcConn))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := kms.NewKeyManagementClient(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gcpkms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -155,7 +156,7 @@ func TestMasterKey_createCloudKMSService_withCredentialsFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, err := tt.key.newKMSClient()
|
||||
_, err := tt.key.newKMSClient(context.Background())
|
||||
if tt.errString != "" {
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, tt.errString)
|
||||
@@ -172,7 +173,7 @@ func TestMasterKey_createCloudKMSService_withOauthToken(t *testing.T) {
|
||||
ResourceID: testResourceID,
|
||||
}
|
||||
|
||||
_, err := masterKey.newKMSClient()
|
||||
_, err := masterKey.newKMSClient(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -182,7 +183,7 @@ func TestMasterKey_createCloudKMSService_withoutCredentials(t *testing.T) {
|
||||
ResourceID: testResourceID,
|
||||
}
|
||||
|
||||
_, err := masterKey.newKMSClient()
|
||||
_, err := masterKey.newKMSClient(context.Background())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "credentials: could not find default credentials")
|
||||
|
||||
@@ -2,6 +2,7 @@ package hcvault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -130,7 +131,15 @@ func NewMasterKey(address, enginePath, keyName string) *MasterKey {
|
||||
|
||||
// Encrypt takes a SOPS data key, encrypts it with Vault Transit, and stores
|
||||
// the result in the EncryptedKey field.
|
||||
//
|
||||
// Consider using EncryptContext instead.
|
||||
func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
return key.EncryptContext(context.Background(), dataKey)
|
||||
}
|
||||
|
||||
// EncryptContext takes a SOPS data key, encrypts it with Vault Transit, and stores
|
||||
// the result in the EncryptedKey field.
|
||||
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {
|
||||
fullPath := key.encryptPath()
|
||||
|
||||
client, err := vaultClient(key.VaultAddress, key.token)
|
||||
@@ -139,7 +148,7 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(fullPath, encryptPayload(dataKey))
|
||||
secret, err := client.Logical().WriteWithContext(ctx, fullPath, encryptPayload(dataKey))
|
||||
if err != nil {
|
||||
log.WithField("Path", fullPath).Info("Encryption failed")
|
||||
return fmt.Errorf("failed to encrypt sops data key to Vault transit backend '%s': %w", fullPath, err)
|
||||
@@ -175,7 +184,14 @@ func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
|
||||
}
|
||||
|
||||
// Decrypt decrypts the EncryptedKey field with Vault Transit and returns the result.
|
||||
//
|
||||
// Consider using DecryptContext instead.
|
||||
func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
return key.DecryptContext(context.Background())
|
||||
}
|
||||
|
||||
// DecryptContext decrypts the EncryptedKey field with Vault Transit and returns the result.
|
||||
func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) {
|
||||
fullPath := key.decryptPath()
|
||||
|
||||
client, err := vaultClient(key.VaultAddress, key.token)
|
||||
@@ -184,7 +200,7 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret, err := client.Logical().Write(fullPath, decryptPayload(key.EncryptedKey))
|
||||
secret, err := client.Logical().WriteWithContext(ctx, fullPath, decryptPayload(key.EncryptedKey))
|
||||
if err != nil {
|
||||
log.WithField("Path", fullPath).Info("Decryption failed")
|
||||
return nil, fmt.Errorf("failed to decrypt sops data key from Vault transit backend '%s': %w", fullPath, err)
|
||||
|
||||
@@ -235,8 +235,16 @@ func (c CredentialsProvider) ApplyToMasterKey(key *MasterKey) {
|
||||
|
||||
// Encrypt takes a SOPS data key, encrypts it with KMS and stores the result
|
||||
// in the EncryptedKey field.
|
||||
//
|
||||
// Consider using EncryptContext instead.
|
||||
func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
cfg, err := key.createKMSConfig()
|
||||
return key.EncryptContext(context.Background(), dataKey)
|
||||
}
|
||||
|
||||
// EncryptContext takes a SOPS data key, encrypts it with KMS and stores the result
|
||||
// in the EncryptedKey field.
|
||||
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {
|
||||
cfg, err := key.createKMSConfig(ctx)
|
||||
if err != nil {
|
||||
log.WithField("arn", key.Arn).Info("Encryption failed")
|
||||
return err
|
||||
@@ -247,7 +255,7 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
Plaintext: dataKey,
|
||||
EncryptionContext: stringPointerToStringMap(key.EncryptionContext),
|
||||
}
|
||||
out, err := client.Encrypt(context.TODO(), input)
|
||||
out, err := client.Encrypt(ctx, input)
|
||||
if err != nil {
|
||||
log.WithField("arn", key.Arn).Info("Encryption failed")
|
||||
return fmt.Errorf("failed to encrypt sops data key with AWS KMS: %w", err)
|
||||
@@ -278,13 +286,21 @@ func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
|
||||
|
||||
// Decrypt decrypts the EncryptedKey with a newly created AWS KMS config, and
|
||||
// returns the result.
|
||||
//
|
||||
// Consider using DecryptContext instead.
|
||||
func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
return key.DecryptContext(context.Background())
|
||||
}
|
||||
|
||||
// DecryptContext decrypts the EncryptedKey with a newly created AWS KMS config, and
|
||||
// returns the result.
|
||||
func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) {
|
||||
k, err := base64.StdEncoding.DecodeString(key.EncryptedKey)
|
||||
if err != nil {
|
||||
log.WithField("arn", key.Arn).Info("Decryption failed")
|
||||
return nil, fmt.Errorf("error base64-decoding encrypted data key: %s", err)
|
||||
}
|
||||
cfg, err := key.createKMSConfig()
|
||||
cfg, err := key.createKMSConfig(ctx)
|
||||
if err != nil {
|
||||
log.WithField("arn", key.Arn).Info("Decryption failed")
|
||||
return nil, err
|
||||
@@ -295,7 +311,7 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
CiphertextBlob: k,
|
||||
EncryptionContext: stringPointerToStringMap(key.EncryptionContext),
|
||||
}
|
||||
decrypted, err := client.Decrypt(context.TODO(), input)
|
||||
decrypted, err := client.Decrypt(ctx, input)
|
||||
if err != nil {
|
||||
log.WithField("arn", key.Arn).Info("Decryption failed")
|
||||
return nil, fmt.Errorf("failed to decrypt sops data key with AWS KMS: %w", err)
|
||||
@@ -351,7 +367,7 @@ func (key *MasterKey) TypeToIdentifier() string {
|
||||
|
||||
// createKMSConfig returns an AWS config with the credentialsProvider of the
|
||||
// MasterKey, or the default configuration sources.
|
||||
func (key MasterKey) createKMSConfig() (*aws.Config, error) {
|
||||
func (key MasterKey) createKMSConfig(ctx context.Context) (*aws.Config, error) {
|
||||
re := regexp.MustCompile(arnRegex)
|
||||
matches := re.FindStringSubmatch(key.Arn)
|
||||
if matches == nil {
|
||||
@@ -359,7 +375,7 @@ func (key MasterKey) createKMSConfig() (*aws.Config, error) {
|
||||
}
|
||||
region := matches[1]
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(), func(lo *config.LoadOptions) error {
|
||||
cfg, err := config.LoadDefaultConfig(ctx, func(lo *config.LoadOptions) error {
|
||||
// Use the credentialsProvider if present, otherwise default to reading credentials
|
||||
// from the environment.
|
||||
if key.credentialsProvider != nil {
|
||||
@@ -376,7 +392,7 @@ func (key MasterKey) createKMSConfig() (*aws.Config, error) {
|
||||
}
|
||||
|
||||
if key.Role != "" {
|
||||
return key.createSTSConfig(&cfg)
|
||||
return key.createSTSConfig(ctx, &cfg)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
@@ -393,7 +409,7 @@ func (key MasterKey) createClient(config *aws.Config) *kms.Client {
|
||||
// createSTSConfig uses AWS STS to assume a role and returns a config
|
||||
// configured with that role's credentials. It returns an error if
|
||||
// it fails to construct a session name, or assume the role.
|
||||
func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) {
|
||||
func (key MasterKey) createSTSConfig(ctx context.Context, config *aws.Config) (*aws.Config, error) {
|
||||
name, err := stsSessionName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -404,7 +420,7 @@ func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) {
|
||||
}
|
||||
|
||||
client := sts.NewFromConfig(*config)
|
||||
out, err := client.AssumeRole(context.TODO(), input)
|
||||
out, err := client.AssumeRole(ctx, input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to assume role '%s': %w", key.Role, err)
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@ aws_secret_access_key = test-secret`), 0600))
|
||||
if tt.envFunc != nil {
|
||||
tt.envFunc(t)
|
||||
}
|
||||
cfg, err := tt.key.createKMSConfig()
|
||||
cfg, err := tt.key.createKMSConfig(context.Background())
|
||||
tt.assertFunc(t, cfg, err)
|
||||
})
|
||||
}
|
||||
@@ -549,7 +549,7 @@ func TestMasterKey_createSTSConfig(t *testing.T) {
|
||||
return
|
||||
}
|
||||
key := NewMasterKeyFromArn(dummyARN, nil, "")
|
||||
cfg, err := key.createSTSConfig(nil)
|
||||
cfg, err := key.createSTSConfig(context.Background(), nil)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "failed to construct STS session name")
|
||||
assert.Nil(t, cfg)
|
||||
@@ -558,7 +558,7 @@ func TestMasterKey_createSTSConfig(t *testing.T) {
|
||||
t.Run("role assumption error", func(t *testing.T) {
|
||||
key := NewMasterKeyFromArn(dummyARN, nil, "")
|
||||
key.Role = "role"
|
||||
got, err := key.createSTSConfig(&aws.Config{})
|
||||
got, err := key.createSTSConfig(context.Background(), &aws.Config{})
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "failed to assume role 'role'")
|
||||
assert.Nil(t, got)
|
||||
@@ -629,7 +629,7 @@ func createTestMasterKey(arn string) MasterKey {
|
||||
// createTestKMSClient creates a new client with the
|
||||
// aws.EndpointResolverWithOptions set to epResolver.
|
||||
func createTestKMSClient(key MasterKey) (*kms.Client, error) {
|
||||
cfg, err := key.createKMSConfig()
|
||||
cfg, err := key.createKMSConfig(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package pgp // import "github.com/getsops/sops/v3/pgp"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -129,13 +130,22 @@ func NewGnuPGHome() (GnuPGHome, error) {
|
||||
// Import attempts to import the armored key bytes into the GnuPGHome keyring.
|
||||
// It returns an error if the GnuPGHome does not pass Validate, or if the
|
||||
// import failed.
|
||||
//
|
||||
// Consider using ImportContext instead.
|
||||
func (d GnuPGHome) Import(armoredKey []byte) error {
|
||||
return d.ImportContext(context.Background(), armoredKey)
|
||||
}
|
||||
|
||||
// ImportContext attempts to import the armored key bytes into the GnuPGHome keyring.
|
||||
// It returns an error if the GnuPGHome does not pass Validate, or if the
|
||||
// import failed.
|
||||
func (d GnuPGHome) ImportContext(ctx context.Context, armoredKey []byte) error {
|
||||
if err := d.Validate(); err != nil {
|
||||
return fmt.Errorf("cannot import armored key data into GnuPG keyring: %w", err)
|
||||
}
|
||||
|
||||
args := []string{"--batch", "--import"}
|
||||
_, stderr, err := gpgExec(d.String(), args, bytes.NewReader(armoredKey))
|
||||
_, stderr, err := gpgExec(ctx, d.String(), args, bytes.NewReader(armoredKey))
|
||||
if err != nil {
|
||||
stderrStr := strings.TrimSpace(stderr.String())
|
||||
errStr := err.Error()
|
||||
@@ -254,7 +264,15 @@ func (e errSet) Error() string {
|
||||
|
||||
// Encrypt encrypts the data key with the PGP key with the same
|
||||
// fingerprint as the MasterKey.
|
||||
//
|
||||
// Consider using EncryptContext instead.
|
||||
func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
return key.EncryptContext(context.Background(), dataKey)
|
||||
}
|
||||
|
||||
// EncryptContext encrypts the data key with the PGP key with the same
|
||||
// fingerprint as the MasterKey.
|
||||
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {
|
||||
var errs errSet
|
||||
|
||||
if !key.disableOpenPGP {
|
||||
@@ -266,7 +284,7 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
|
||||
errs = append(errs, fmt.Errorf("github.com/ProtonMail/go-crypto/openpgp error: %w", openpgpErr))
|
||||
}
|
||||
|
||||
binaryErr := key.encryptWithGnuPG(dataKey)
|
||||
binaryErr := key.encryptWithGnuPG(ctx, dataKey)
|
||||
if binaryErr == nil {
|
||||
log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded")
|
||||
return nil
|
||||
@@ -320,7 +338,7 @@ func (key *MasterKey) encryptWithOpenPGP(dataKey []byte) error {
|
||||
// encryptWithOpenPGP attempts to encrypt the data key using GnuPG with the
|
||||
// PGP key that belongs to Fingerprint. It sets EncryptedDataKey, or returns
|
||||
// an error.
|
||||
func (key *MasterKey) encryptWithGnuPG(dataKey []byte) error {
|
||||
func (key *MasterKey) encryptWithGnuPG(ctx context.Context, dataKey []byte) error {
|
||||
fingerprint := shortenFingerprint(key.Fingerprint)
|
||||
|
||||
args := []string{
|
||||
@@ -334,7 +352,7 @@ func (key *MasterKey) encryptWithGnuPG(dataKey []byte) error {
|
||||
fingerprint,
|
||||
"--no-encrypt-to",
|
||||
}
|
||||
stdout, stderr, err := gpgExec(key.gnuPGHomeDir, args, bytes.NewReader(dataKey))
|
||||
stdout, stderr, err := gpgExec(ctx, key.gnuPGHomeDir, args, bytes.NewReader(dataKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt sops data key with pgp: %s", strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
@@ -365,7 +383,16 @@ func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
|
||||
// Decrypt first attempts to obtain the data key from the EncryptedKey
|
||||
// stored in the MasterKey using OpenPGP, before falling back to GnuPG.
|
||||
// When both attempts fail, an error is returned.
|
||||
//
|
||||
// Consider using DecryptContext instead.
|
||||
func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
return key.DecryptContext(context.Background())
|
||||
}
|
||||
|
||||
// DecryptContext first attempts to obtain the data key from the EncryptedKey
|
||||
// stored in the MasterKey using OpenPGP, before falling back to GnuPG.
|
||||
// When both attempts fail, an error is returned.
|
||||
func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) {
|
||||
var errs errSet
|
||||
|
||||
if !key.disableOpenPGP {
|
||||
@@ -377,7 +404,7 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
|
||||
errs = append(errs, fmt.Errorf("github.com/ProtonMail/go-crypto/openpgp error: %w", openpgpErr))
|
||||
}
|
||||
|
||||
dataKey, binaryErr := key.decryptWithGnuPG()
|
||||
dataKey, binaryErr := key.decryptWithGnuPG(ctx)
|
||||
if binaryErr == nil {
|
||||
log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded")
|
||||
return dataKey, nil
|
||||
@@ -419,11 +446,11 @@ func (key *MasterKey) decryptWithOpenPGP() ([]byte, error) {
|
||||
// GnuPG and returns the result. If DisableAgent is configured on the MasterKey,
|
||||
// the GnuPG agent is not enabled. When the decryption command fails, it returns
|
||||
// the error from stdout.
|
||||
func (key *MasterKey) decryptWithGnuPG() ([]byte, error) {
|
||||
func (key *MasterKey) decryptWithGnuPG(ctx context.Context) ([]byte, error) {
|
||||
args := []string{
|
||||
"-d",
|
||||
}
|
||||
stdout, stderr, err := gpgExec(key.gnuPGHomeDir, args, strings.NewReader(key.EncryptedKey))
|
||||
stdout, stderr, err := gpgExec(ctx, key.gnuPGHomeDir, args, strings.NewReader(key.EncryptedKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt sops data key with pgp: %s",
|
||||
strings.TrimSpace(stderr.String()))
|
||||
@@ -595,12 +622,12 @@ func fingerprintIndex(ring openpgp.EntityList) map[string]openpgp.Entity {
|
||||
// gpgExec runs the provided args with the gpgBinary, while restricting it to
|
||||
// homeDir when provided. Stdout and stderr can be read from the returned
|
||||
// buffers. When the command fails, an error is returned.
|
||||
func gpgExec(homeDir string, args []string, stdin io.Reader) (stdout bytes.Buffer, stderr bytes.Buffer, err error) {
|
||||
func gpgExec(ctx context.Context, homeDir string, args []string, stdin io.Reader) (stdout bytes.Buffer, stderr bytes.Buffer, err error) {
|
||||
if homeDir != "" {
|
||||
args = append([]string{"--homedir", homeDir}, args...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(gpgBinary(), args...)
|
||||
cmd := exec.CommandContext(ctx, gpgBinary(), args...)
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
@@ -2,6 +2,7 @@ package pgp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -56,14 +57,14 @@ func TestGnuPGHome_Import(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, gnuPGHome.Import(b))
|
||||
|
||||
_, stderr, err := gpgExec(gnuPGHome.String(), []string{"--list-keys", mockFingerprint}, nil)
|
||||
_, stderr, err := gpgExec(context.Background(), gnuPGHome.String(), []string{"--list-keys", mockFingerprint}, nil)
|
||||
assert.NoErrorf(t, err, stderr.String())
|
||||
|
||||
b, err = os.ReadFile(mockPrivateKey)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, gnuPGHome.Import(b))
|
||||
|
||||
_, stderr, err = gpgExec(gnuPGHome.String(), []string{"--list-secret-keys", mockFingerprint}, nil)
|
||||
_, stderr, err = gpgExec(context.Background(), gnuPGHome.String(), []string{"--list-secret-keys", mockFingerprint}, nil)
|
||||
assert.NoErrorf(t, err, stderr.String())
|
||||
|
||||
err = gnuPGHome.Import([]byte("invalid armored data"))
|
||||
@@ -281,7 +282,7 @@ func TestMasterKey_encryptWithGnuPG(t *testing.T) {
|
||||
key := NewMasterKeyFromFingerprint(mockFingerprint)
|
||||
gnuPGHome.ApplyToMasterKey(key)
|
||||
data := []byte("oh no, my darkest secret")
|
||||
assert.NoError(t, key.encryptWithGnuPG(data))
|
||||
assert.NoError(t, key.encryptWithGnuPG(context.Background(), data))
|
||||
|
||||
assert.NotEmpty(t, key.EncryptedKey)
|
||||
assert.NotEqual(t, data, key.EncryptedKey)
|
||||
@@ -291,14 +292,14 @@ func TestMasterKey_encryptWithGnuPG(t *testing.T) {
|
||||
args := []string{
|
||||
"-d",
|
||||
}
|
||||
stdout, stderr, err := gpgExec(key.gnuPGHomeDir, args, strings.NewReader(key.EncryptedKey))
|
||||
stdout, stderr, err := gpgExec(context.Background(), key.gnuPGHomeDir, args, strings.NewReader(key.EncryptedKey))
|
||||
assert.NoError(t, err, stderr.String())
|
||||
assert.Equal(t, data, stdout.Bytes())
|
||||
})
|
||||
|
||||
t.Run("invalid fingerprint error", func(t *testing.T) {
|
||||
key := NewMasterKeyFromFingerprint("invalid")
|
||||
err := key.encryptWithGnuPG([]byte("invalid"))
|
||||
err := key.encryptWithGnuPG(context.Background(), []byte("invalid"))
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "failed to encrypt sops data key with pgp: gpg: 'invalid' is not a valid long keyID")
|
||||
})
|
||||
@@ -341,7 +342,7 @@ func TestMasterKey_Decrypt(t *testing.T) {
|
||||
fingerprint := shortenFingerprint(mockFingerprint)
|
||||
|
||||
data := []byte("this data is absolutely top secret")
|
||||
stdout, stderr, err := gpgExec(gnuPGHome.String(), []string{
|
||||
stdout, stderr, err := gpgExec(context.Background(), gnuPGHome.String(), []string{
|
||||
"--no-default-recipient",
|
||||
"--yes",
|
||||
"--encrypt",
|
||||
@@ -424,7 +425,7 @@ func TestMasterKey_decryptWithOpenPGP(t *testing.T) {
|
||||
fingerprint := shortenFingerprint(mockFingerprint)
|
||||
|
||||
data := []byte("this data is absolutely top secret")
|
||||
stdout, stderr, err := gpgExec(gnuPGHome.String(), []string{
|
||||
stdout, stderr, err := gpgExec(context.Background(), gnuPGHome.String(), []string{
|
||||
"--no-default-recipient",
|
||||
"--yes",
|
||||
"--encrypt",
|
||||
@@ -473,7 +474,7 @@ func TestMasterKey_decryptWithGnuPG(t *testing.T) {
|
||||
fingerprint := shortenFingerprint(mockFingerprint)
|
||||
|
||||
data := []byte("this data is absolutely top secret")
|
||||
stdout, stderr, err := gpgExec(gnuPGHome.String(), []string{
|
||||
stdout, stderr, err := gpgExec(context.Background(), gnuPGHome.String(), []string{
|
||||
"--no-default-recipient",
|
||||
"--yes",
|
||||
"--encrypt",
|
||||
@@ -494,7 +495,7 @@ func TestMasterKey_decryptWithGnuPG(t *testing.T) {
|
||||
gnuPGHome.ApplyToMasterKey(key)
|
||||
key.EncryptedKey = encryptedData
|
||||
|
||||
got, err := key.decryptWithGnuPG()
|
||||
got, err := key.decryptWithGnuPG(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, data, got)
|
||||
})
|
||||
@@ -502,7 +503,7 @@ func TestMasterKey_decryptWithGnuPG(t *testing.T) {
|
||||
t.Run("invalid data error", func(t *testing.T) {
|
||||
key := NewMasterKeyFromFingerprint(mockFingerprint)
|
||||
key.EncryptedKey = "absolute invalid"
|
||||
got, err := key.decryptWithGnuPG()
|
||||
got, err := key.decryptWithGnuPG(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "gpg: no valid OpenPGP data found")
|
||||
assert.Nil(t, got)
|
||||
|
||||
Reference in New Issue
Block a user