1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00
Files
sops/age/keysource.go
2020-09-21 13:00:36 -07:00

196 lines
5.1 KiB
Go

package age
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"filippo.io/age"
"github.com/sirupsen/logrus"
"go.mozilla.org/sops/v3/logging"
)
var log *logrus.Logger
func init() {
log = logging.NewLogger("AGE")
}
const privateKeySizeLimit = 1 << 24 // 16 MiB
// MasterKey is an age key used to encrypt and decrypt sops' data key.
type MasterKey struct {
Identity string // a Bech32-encoded private key
Recipient string // a Bech32-encoded public key
EncryptedKey string // a sops data key encrypted with age
parsedIdentity *age.X25519Identity // a parsed age private key
parsedRecipient *age.X25519Recipient // a parsed age public key
}
// Encrypt takes a sops data key, encrypts it with age and stores the result in the EncryptedKey field.
func (key *MasterKey) Encrypt(datakey []byte) error {
buffer := &bytes.Buffer{}
if key.parsedRecipient == nil {
parsedRecipient, err := parseRecipient(key.Recipient)
if err != nil {
log.WithField("recipient", key.parsedRecipient).Error("Encryption failed")
return err
}
key.parsedRecipient = parsedRecipient
}
w, err := age.Encrypt(buffer, key.parsedRecipient)
if err != nil {
return fmt.Errorf("failed to open file for encrypting sops data key with age: %v", err)
}
if _, err := w.Write(datakey); err != nil {
log.WithField("recipient", key.parsedRecipient).Error("Encryption failed")
return fmt.Errorf("failed to encrypt sops data key with age: %v", err)
}
if err := w.Close(); err != nil {
log.WithField("recipient", key.parsedRecipient).Error("Encryption failed")
return fmt.Errorf("failed to close file for encrypting sops data key with age: %v", err)
}
key.EncryptedKey = buffer.String()
log.WithField("recipient", key.parsedRecipient).Info("Encryption succeeded")
return nil
}
// EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet.
func (key *MasterKey) EncryptIfNeeded(datakey []byte) error {
if key.EncryptedKey == "" {
return key.Encrypt(datakey)
}
return nil
}
// EncryptedDataKey returns the encrypted data key this master key holds.
func (key *MasterKey) EncryptedDataKey() []byte {
return []byte(key.EncryptedKey)
}
// SetEncryptedDataKey sets the encrypted data key for this master key.
func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
key.EncryptedKey = string(enc)
}
// Decrypt decrypts the EncryptedKey field with the age identity and returns the result.
func (key *MasterKey) Decrypt() ([]byte, error) {
ageKeyFilePath, ok := os.LookupEnv("SOPS_AGE_KEY_FILE")
if !ok {
userConfigDir, err := os.UserConfigDir()
if err != nil {
return nil, fmt.Errorf("user config directory could not be determined: %v", err)
}
ageKeyFilePath = filepath.Join(userConfigDir, "sops", "age", "keys.txt")
}
ageKeyFile, err := os.Open(ageKeyFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer ageKeyFile.Close()
identities, err := age.ParseIdentities(ageKeyFile)
if err != nil {
return nil, err
}
buffer := &bytes.Buffer{}
reader := bytes.NewReader([]byte(key.EncryptedKey))
r, err := age.Decrypt(reader, identities...)
if err != nil {
return nil, fmt.Errorf("no age identity found in %q that could decrypt the data", ageKeyFilePath)
}
if _, err := io.Copy(buffer, r); err != nil {
return nil, fmt.Errorf("failed to copy decrypted data into bytes.Buffer")
}
return buffer.Bytes(), nil
}
// NeedsRotation returns whether the data key needs to be rotated or not.
func (key *MasterKey) NeedsRotation() bool {
return false
}
// ToString converts the key to a string representation.
func (key *MasterKey) ToString() string {
return key.Recipient
}
// ToMap converts the MasterKey to a map for serialization purposes.
func (key *MasterKey) ToMap() map[string]interface{} {
return map[string]interface{}{"recipient": key.Recipient, "enc": key.EncryptedKey}
}
// MasterKeysFromRecipients takes a comma-separated list of Bech32-encoded public keys and returns a
// slice of new MasterKeys.
func MasterKeysFromRecipients(commaSeparatedRecipients string) ([]*MasterKey, error) {
if commaSeparatedRecipients == "" {
// otherwise Split returns [""] and MasterKeyFromRecipient is unhappy
return make([]*MasterKey, 0), nil
}
recipients := strings.Split(commaSeparatedRecipients, ",")
var keys []*MasterKey
for _, recipient := range recipients {
key, err := MasterKeyFromRecipient(recipient)
if err != nil {
return nil, err
}
keys = append(keys, key)
}
return keys, nil
}
// MasterKeyFromRecipient takes a Bech32-encoded public key and returns a new MasterKey.
func MasterKeyFromRecipient(recipient string) (*MasterKey, error) {
parsedRecipient, err := parseRecipient(recipient)
if err != nil {
return nil, err
}
return &MasterKey{
Recipient: recipient,
parsedRecipient: parsedRecipient,
}, nil
}
// parseRecipient attempts to parse a string containing an encoded age public key
func parseRecipient(recipient string) (*age.X25519Recipient, error) {
parsedRecipient, err := age.ParseX25519Recipient(recipient)
if err != nil {
return nil, fmt.Errorf("failed to parse input as Bech32-encoded age public key: %v", err)
}
return parsedRecipient, nil
}