mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
Refactored PGP and KMS into their own packages
This commit is contained in:
275
keysources.go
275
keysources.go
@@ -1,275 +0,0 @@
|
||||
package sops
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/kms"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
"github.com/howeyc/gopass"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KeySource provides a way to obtain the symmetric encryption key used by sops
|
||||
type KeySource interface {
|
||||
DecryptKeys() (string, error)
|
||||
EncryptKeys(plaintext string) error
|
||||
}
|
||||
|
||||
type KMS struct {
|
||||
Arn string
|
||||
Role string
|
||||
EncryptedKey string
|
||||
}
|
||||
|
||||
type KMSKeySource struct {
|
||||
KMS []KMS
|
||||
}
|
||||
|
||||
type GPG struct {
|
||||
Fingerprint string
|
||||
EncryptedKey string
|
||||
}
|
||||
|
||||
type GPGKeySource struct {
|
||||
GPG []GPG
|
||||
}
|
||||
|
||||
// NewKMSKeySourceFromString takes a comma-separated string with KMS ARNs and returns a KMSKeySource
|
||||
func NewKMSKeySourceFromString(arns string) KMSKeySource {
|
||||
var kmss []KMS
|
||||
arns = strings.Replace(arns, " ", "", -1)
|
||||
for _, s := range strings.Split(arns, ",") {
|
||||
roleIndex := strings.Index(s, "+arn:aws:iam::")
|
||||
k := KMS{}
|
||||
if roleIndex > 0 {
|
||||
k.Arn = s[:roleIndex]
|
||||
k.Role = s[roleIndex+1:]
|
||||
} else {
|
||||
k.Arn = s
|
||||
}
|
||||
kmss = append(kmss, k)
|
||||
}
|
||||
return KMSKeySource{KMS: kmss}
|
||||
}
|
||||
|
||||
func (k KMS) createStsSession(config aws.Config, sess *session.Session) (*session.Session, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stsService := sts.New(sess)
|
||||
name := "sops@" + hostname
|
||||
out, err := stsService.AssumeRole(&sts.AssumeRoleInput{
|
||||
RoleArn: &k.Role, RoleSessionName: &name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Credentials = credentials.NewStaticCredentials(*out.Credentials.AccessKeyId,
|
||||
*out.Credentials.SecretAccessKey, *out.Credentials.SessionToken)
|
||||
sess, err = session.NewSession(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (k KMS) createSession() (*session.Session, error) {
|
||||
re := regexp.MustCompile(`^arn:aws:kms:(.+):([0-9]+):key/(.+)$`)
|
||||
matches := re.FindStringSubmatch(k.Arn)
|
||||
if matches == nil {
|
||||
return nil, fmt.Errorf("No valid ARN found in %s", k.Arn)
|
||||
}
|
||||
config := aws.Config{Region: aws.String(matches[1])}
|
||||
sess, err := session.NewSession(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k.Role != "" {
|
||||
return k.createStsSession(config, sess)
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (k KMS) DecryptKey(encryptedKey string) (string, error) {
|
||||
sess, err := k.createSession()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error creating AWS session: %v", err)
|
||||
}
|
||||
|
||||
service := kms.New(sess)
|
||||
decrypted, err := service.Decrypt(&kms.DecryptInput{CiphertextBlob: []byte(encryptedKey)})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error decrypting key: %v", err)
|
||||
}
|
||||
return string(decrypted.Plaintext), nil
|
||||
}
|
||||
|
||||
func (ks KMSKeySource) DecryptKeys() (string, error) {
|
||||
errors := make([]error, 1)
|
||||
for _, kms := range ks.KMS {
|
||||
encKey, err := base64.StdEncoding.DecodeString(kms.EncryptedKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
key, err := kms.DecryptKey(string(encKey))
|
||||
if err == nil {
|
||||
return key, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return "", fmt.Errorf("The key could not be decrypted with any KMS entries", errors)
|
||||
}
|
||||
|
||||
func (ks KMSKeySource) EncryptKeys(plaintext string) error {
|
||||
for i, _ := range ks.KMS {
|
||||
sess, err := ks.KMS[i].createSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := kms.New(sess)
|
||||
out, err := service.Encrypt(&kms.EncryptInput{Plaintext: []byte(plaintext), KeyId: &ks.KMS[i].Arn})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks.KMS[i].EncryptedKey = base64.StdEncoding.EncodeToString(out.CiphertextBlob)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPGPKeySourceFromString created a new PGPKeySource from a comma-separated string of PGP fingerprints
|
||||
func NewPGPKeySourceFromString(fps string) GPGKeySource {
|
||||
var gpgs []GPG
|
||||
for _, s := range strings.Split(fps, ",") {
|
||||
gpgs = append(gpgs, GPG{Fingerprint: strings.Replace(s, " ", "", -1)})
|
||||
}
|
||||
return GPGKeySource{GPG: gpgs}
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) gpgHome() string {
|
||||
dir := os.Getenv("GNUPGHOME")
|
||||
if dir == "" {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "~/.gnupg"
|
||||
}
|
||||
return path.Join(usr.HomeDir, ".gnupg")
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) loadRing(path string) (openpgp.EntityList, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return openpgp.EntityList{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
keyring, err := openpgp.ReadKeyRing(f)
|
||||
if err != nil {
|
||||
return keyring, err
|
||||
}
|
||||
return keyring, nil
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) secRing() (openpgp.EntityList, error) {
|
||||
return gpg.loadRing(gpg.gpgHome() + "/secring.gpg")
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) pubRing() (openpgp.EntityList, error) {
|
||||
return gpg.loadRing(gpg.gpgHome() + "/pubring.gpg")
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) fingerprintMap(ring openpgp.EntityList) map[string]openpgp.Entity {
|
||||
fps := make(map[string]openpgp.Entity)
|
||||
for _, entity := range ring {
|
||||
fp := strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]))
|
||||
if entity != nil {
|
||||
fps[fp] = *entity
|
||||
}
|
||||
}
|
||||
return fps
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) passphrasePrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||
fmt.Print("Enter PGP key passphrase: ")
|
||||
psswd, err := gopass.GetPasswd()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return psswd, err
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) DecryptKeys() (string, error) {
|
||||
ring, err := gpg.secRing()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not load secring: %s", err)
|
||||
}
|
||||
for _, g := range gpg.GPG {
|
||||
block, err := armor.Decode(strings.NewReader(g.EncryptedKey))
|
||||
if err != nil {
|
||||
fmt.Println("Decode failed", err)
|
||||
continue
|
||||
}
|
||||
md, err := openpgp.ReadMessage(block.Body, ring, gpg.passphrasePrompt, nil)
|
||||
if err != nil {
|
||||
fmt.Println("ReadMessage failed", err)
|
||||
continue
|
||||
}
|
||||
if b, err := ioutil.ReadAll(md.UnverifiedBody); err == nil {
|
||||
return string(b), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("The key could not be decrypted with any of the GPG entries")
|
||||
}
|
||||
|
||||
func (gpg GPGKeySource) EncryptKeys(plaintext string) error {
|
||||
ring, err := gpg.pubRing()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fingerprints := gpg.fingerprintMap(ring)
|
||||
for i, _ := range gpg.GPG {
|
||||
entity, ok := fingerprints[gpg.GPG[i].Fingerprint]
|
||||
if !ok {
|
||||
return fmt.Errorf("Key with fingerprint %s is not available in keyring.", gpg.GPG[i].Fingerprint)
|
||||
}
|
||||
encbuf := new(bytes.Buffer)
|
||||
armorbuf, err := armor.Encode(encbuf, "PGP MESSAGE", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plaintextbuf, err := openpgp.Encrypt(armorbuf, []*openpgp.Entity{&entity}, nil, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = plaintextbuf.Write([]byte(plaintext))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = plaintextbuf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = armorbuf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(encbuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gpg.GPG[i].EncryptedKey = string(bytes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,18 +1,25 @@
|
||||
package sops
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.mozilla.org/sops/kms"
|
||||
"go.mozilla.org/sops/pgp"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
func TestKMS(t *testing.T) {
|
||||
// TODO: make this not terrible and mock KMS with a reverseable operation on the key, or something. Good luck running the tests on a machine that's not mine!
|
||||
ks := KMSKeySource{KMS: []KMS{
|
||||
KMS{Arn: "arn:aws:kms:us-east-1:927034868273:key/e9fc75db-05e9-44c1-9c35-633922bac347", Role: "", EncryptedKey: ""},
|
||||
}}
|
||||
k := kms.KMSMasterKey{Arn: "arn:aws:kms:us-east-1:927034868273:key/e9fc75db-05e9-44c1-9c35-633922bac347", Role: "", EncryptedKey: ""}
|
||||
f := func(x string) bool {
|
||||
ks.EncryptKeys(x)
|
||||
v, _ := ks.DecryptKeys()
|
||||
err := k.Encrypt(x)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
v, err := k.Decrypt()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if x == "" {
|
||||
return true // we can't encrypt an empty string
|
||||
}
|
||||
@@ -28,13 +35,10 @@ func TestKMS(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGPG(t *testing.T) {
|
||||
ks := GPGKeySource{GPG: []GPG{
|
||||
GPG{Fingerprint: "64FEF099B0544CF975BCD408A014A073E0848B51"},
|
||||
},
|
||||
}
|
||||
key := pgp.NewGPGMasterKeyFromFingerprint("64FEF099B0544CF975BCD408A014A073E0848B51")
|
||||
f := func(x string) bool {
|
||||
ks.EncryptKeys(x)
|
||||
k, _ := ks.DecryptKeys()
|
||||
key.Encrypt(x)
|
||||
k, _ := key.Decrypt()
|
||||
return x == k
|
||||
}
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
@@ -44,18 +48,22 @@ func TestGPG(t *testing.T) {
|
||||
|
||||
func TestGPGKeySourceFromString(t *testing.T) {
|
||||
s := "C8C5 2C0A B2A4 8174 01E8 12C8 F3CC 3233 3FAD 9F1E, C8C5 2C0A B2A4 8174 01E8 12C8 F3CC 3233 3FAD 9F1E"
|
||||
ks := NewPGPKeySourceFromString(s)
|
||||
ks := pgp.GPGMasterKeysFromFingerprintString(s)
|
||||
expected := "C8C52C0AB2A4817401E812C8F3CC32333FAD9F1E"
|
||||
if ks.GPG[0].Fingerprint != expected || ks.GPG[1].Fingerprint != expected {
|
||||
if ks[0].Fingerprint != expected {
|
||||
t.Errorf("Fingerprint does not match. Got %s, expected %s", ks[0].Fingerprint, expected)
|
||||
}
|
||||
|
||||
if ks[1].Fingerprint != expected {
|
||||
t.Error("Fingerprint does not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKMSKeySourceFromString(t *testing.T) {
|
||||
s := "arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e+arn:aws:iam::927034868273:role/sops-dev, arn:aws:kms:ap-southeast-1:656532927350:key/9006a8aa-0fa6-4c14-930e-a2dfb916de1d"
|
||||
ks := NewKMSKeySourceFromString(s)
|
||||
k1 := ks.KMS[0]
|
||||
k2 := ks.KMS[1]
|
||||
ks := kms.KMSMasterKeysFromArnString(s)
|
||||
k1 := ks[0]
|
||||
k2 := ks[1]
|
||||
expectedArn1 := "arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e"
|
||||
expectedRole1 := "arn:aws:iam::927034868273:role/sops-dev"
|
||||
if k1.Arn != expectedArn1 {
|
||||
|
||||
128
kms/keysource.go
Normal file
128
kms/keysource.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package kms
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/kms"
|
||||
"github.com/aws/aws-sdk-go/service/sts"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KMSMasterKey struct {
|
||||
Arn string
|
||||
Role string
|
||||
EncryptedKey string
|
||||
CreationDate time.Time
|
||||
}
|
||||
|
||||
func (key *KMSMasterKey) Encrypt(dataKey string) error {
|
||||
sess, err := key.createSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := kms.New(sess)
|
||||
out, err := service.Encrypt(&kms.EncryptInput{Plaintext: []byte(dataKey), KeyId: &key.Arn})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.EncryptedKey = base64.StdEncoding.EncodeToString(out.CiphertextBlob)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (key *KMSMasterKey) EncryptIfNeeded(dataKey string) error {
|
||||
if key.EncryptedKey == "" {
|
||||
return key.Encrypt(dataKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (key *KMSMasterKey) Decrypt() (string, error) {
|
||||
k, err := base64.StdEncoding.DecodeString(key.EncryptedKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error base64-decoding encrypted data key: %s", err)
|
||||
}
|
||||
sess, err := key.createSession()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error creating AWS session: %v", err)
|
||||
}
|
||||
|
||||
service := kms.New(sess)
|
||||
decrypted, err := service.Decrypt(&kms.DecryptInput{CiphertextBlob: k})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error decrypting key: %v", err)
|
||||
}
|
||||
return string(decrypted.Plaintext), nil
|
||||
}
|
||||
|
||||
func (key *KMSMasterKey) NeedsRotation() bool {
|
||||
return time.Since(key.CreationDate).Hours() > float64(24*30*6)
|
||||
}
|
||||
|
||||
func (key *KMSMasterKey) ToString() string {
|
||||
return key.Arn
|
||||
}
|
||||
|
||||
func NewKMSMasterKeyFromArn(arn string) KMSMasterKey {
|
||||
k := KMSMasterKey{}
|
||||
arn = strings.Replace(arn, " ", "", -1)
|
||||
roleIndex := strings.Index(arn, "+arn:aws:iam::")
|
||||
if roleIndex > 0 {
|
||||
k.Arn = arn[:roleIndex]
|
||||
k.Role = arn[roleIndex+1:]
|
||||
} else {
|
||||
k.Arn = arn
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func KMSMasterKeysFromArnString(arn string) []KMSMasterKey {
|
||||
var keys []KMSMasterKey
|
||||
for _, s := range strings.Split(arn, ",") {
|
||||
keys = append(keys, NewKMSMasterKeyFromArn(s))
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (k KMSMasterKey) createStsSession(config aws.Config, sess *session.Session) (*session.Session, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stsService := sts.New(sess)
|
||||
name := "sops@" + hostname
|
||||
out, err := stsService.AssumeRole(&sts.AssumeRoleInput{
|
||||
RoleArn: &k.Role, RoleSessionName: &name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Credentials = credentials.NewStaticCredentials(*out.Credentials.AccessKeyId,
|
||||
*out.Credentials.SecretAccessKey, *out.Credentials.SessionToken)
|
||||
sess, err = session.NewSession(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (k KMSMasterKey) createSession() (*session.Session, error) {
|
||||
re := regexp.MustCompile(`^arn:aws:kms:(.+):([0-9]+):key/(.+)$`)
|
||||
matches := re.FindStringSubmatch(k.Arn)
|
||||
if matches == nil {
|
||||
return nil, fmt.Errorf("No valid ARN found in %s", k.Arn)
|
||||
}
|
||||
config := aws.Config{Region: aws.String(matches[1])}
|
||||
sess, err := session.NewSession(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k.Role != "" {
|
||||
return k.createStsSession(config, sess)
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
162
pgp/keysource.go
Normal file
162
pgp/keysource.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package pgp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/howeyc/gopass"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GPGMasterKey struct {
|
||||
Fingerprint string
|
||||
EncryptedKey string
|
||||
CreationDate time.Time
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) Encrypt(dataKey string) error {
|
||||
ring, err := key.pubRing()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fingerprints := key.fingerprintMap(ring)
|
||||
entity, ok := fingerprints[key.Fingerprint]
|
||||
if !ok {
|
||||
return fmt.Errorf("Key with fingerprint %s is not available in keyring.", key.Fingerprint)
|
||||
}
|
||||
encbuf := new(bytes.Buffer)
|
||||
armorbuf, err := armor.Encode(encbuf, "PGP MESSAGE", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plaintextbuf, err := openpgp.Encrypt(armorbuf, []*openpgp.Entity{&entity}, nil, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = plaintextbuf.Write([]byte(dataKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = plaintextbuf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = armorbuf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(encbuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.EncryptedKey = string(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) EncryptIfNeeded(dataKey string) error {
|
||||
if key.EncryptedKey == "" {
|
||||
return key.Encrypt(dataKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) Decrypt() (string, error) {
|
||||
ring, err := key.secRing()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Could not load secring: %s", err)
|
||||
}
|
||||
block, err := armor.Decode(strings.NewReader(key.EncryptedKey))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Armor decoding failed: %s", err)
|
||||
}
|
||||
md, err := openpgp.ReadMessage(block.Body, ring, key.passphrasePrompt, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Reading PGP message failed: %s", err)
|
||||
}
|
||||
if b, err := ioutil.ReadAll(md.UnverifiedBody); err == nil {
|
||||
return string(b), nil
|
||||
}
|
||||
return "", fmt.Errorf("The key could not be decrypted with any of the GPG entries")
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) NeedsRotation() bool {
|
||||
return time.Since(key.CreationDate).Hours() > 24*30*6
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) ToString() string {
|
||||
return key.Fingerprint
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) gpgHome() string {
|
||||
dir := os.Getenv("GNUPGHOME")
|
||||
if dir == "" {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "~/.gnupg"
|
||||
}
|
||||
return path.Join(usr.HomeDir, ".gnupg")
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func NewGPGMasterKeyFromFingerprint(fingerprint string) GPGMasterKey {
|
||||
return GPGMasterKey{
|
||||
Fingerprint: strings.Replace(fingerprint, " ", "", -1),
|
||||
}
|
||||
}
|
||||
|
||||
func GPGMasterKeysFromFingerprintString(fingerprint string) []GPGMasterKey {
|
||||
var keys []GPGMasterKey
|
||||
for _, s := range strings.Split(fingerprint, ",") {
|
||||
keys = append(keys, NewGPGMasterKeyFromFingerprint(s))
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) loadRing(path string) (openpgp.EntityList, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return openpgp.EntityList{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
keyring, err := openpgp.ReadKeyRing(f)
|
||||
if err != nil {
|
||||
return keyring, err
|
||||
}
|
||||
return keyring, nil
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) secRing() (openpgp.EntityList, error) {
|
||||
return key.loadRing(key.gpgHome() + "/secring.gpg")
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) pubRing() (openpgp.EntityList, error) {
|
||||
return key.loadRing(key.gpgHome() + "/pubring.gpg")
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) fingerprintMap(ring openpgp.EntityList) map[string]openpgp.Entity {
|
||||
fps := make(map[string]openpgp.Entity)
|
||||
for _, entity := range ring {
|
||||
fp := strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]))
|
||||
if entity != nil {
|
||||
fps[fp] = *entity
|
||||
}
|
||||
}
|
||||
return fps
|
||||
}
|
||||
|
||||
func (key *GPGMasterKey) passphrasePrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||
fmt.Print("Enter PGP key passphrase: ")
|
||||
psswd, err := gopass.GetPasswd()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return psswd, err
|
||||
}
|
||||
58
sops.go
Normal file
58
sops.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package sops
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
LastModified time.Time
|
||||
UnencryptedSuffix string
|
||||
MessageAuthenticationCode string
|
||||
Version string
|
||||
KeySources []KeySource
|
||||
}
|
||||
|
||||
type KeySource struct {
|
||||
Name string
|
||||
Keys []*MasterKey
|
||||
}
|
||||
|
||||
type MasterKey interface {
|
||||
Encrypt(dataKey string) error
|
||||
EncryptIfNeeded(dataKey string) error
|
||||
Decrypt() (string, error)
|
||||
NeedsRotation() bool
|
||||
ToString() string
|
||||
}
|
||||
|
||||
func (m *Metadata) MasterKeyCount() int {
|
||||
count := 0
|
||||
for _, ks := range m.KeySources {
|
||||
count += len(ks.Keys)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (m *Metadata) RemoveMasterKeys(keys []MasterKey) {
|
||||
for _, ks := range m.KeySources {
|
||||
for i, k := range ks.Keys {
|
||||
for _, k2 := range keys {
|
||||
if (*k).ToString() == k2.ToString() {
|
||||
ks.Keys = append(ks.Keys[:i], ks.Keys[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metadata) UpdateMasterKeys(dataKey string) {
|
||||
for _, ks := range m.KeySources {
|
||||
for _, k := range ks.Keys {
|
||||
err := (*k).EncryptIfNeeded(dataKey)
|
||||
if err != nil {
|
||||
fmt.Println("[WARNING]: could not encrypt data key with master key ", (*k).ToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user