kustomize-controller/internal/sops/age/keysource.go

179 lines
5.0 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package age
import (
"bytes"
"fmt"
"io"
"strings"
"filippo.io/age"
"filippo.io/age/armor"
)
// MasterKey is an age key used to encrypt and decrypt sops' data key.
//
// Adapted from https://github.com/mozilla/sops/blob/v3.7.0/age/keysource.go
// to be able to have fine-grain control over the used decryption keys
// without relying on the existence of file(path)s.
type MasterKey struct {
Identities []string // a slice of Bech32-encoded private keys
Recipient string // a Bech32-encoded public key
EncryptedKey string // a sops data key encrypted with age
parsedIdentities []age.Identity // a slice of parsed age private keys
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 {
return err
}
key.parsedRecipient = parsedRecipient
}
aw := armor.NewWriter(buffer)
w, err := age.Encrypt(aw, key.parsedRecipient)
if err != nil {
return fmt.Errorf("failed to open file for encrypting sops data key with age: %w", err)
}
if _, err := w.Write(datakey); err != nil {
return fmt.Errorf("failed to encrypt sops data key with age: %w", err)
}
if err := w.Close(); err != nil {
return fmt.Errorf("failed to close file for encrypting sops data key with age: %w", err)
}
if err := aw.Close(); err != nil {
return fmt.Errorf("failed to close armored writer: %w", err)
}
key.EncryptedKey = buffer.String()
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) {
if len(key.parsedIdentities) <= 0 && len(key.Identities) > 0 {
var identities []age.Identity
for _, i := range key.Identities {
i, err := age.ParseIdentities(strings.NewReader(i))
if err != nil {
return nil, err
}
identities = append(identities, i...)
}
key.parsedIdentities = identities
}
src := bytes.NewReader([]byte(key.EncryptedKey))
ar := armor.NewReader(src)
r, err := age.Decrypt(ar, key.parsedIdentities...)
if err != nil {
return nil, fmt.Errorf("no age identity found that could decrypt the data")
}
var b bytes.Buffer
if _, err := io.Copy(&b, r); err != nil {
return nil, fmt.Errorf("failed to copy decrypted data into bytes.Buffer: %w", err)
}
return b.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: %w", err)
}
return parsedRecipient, nil
}