-
Notifications
You must be signed in to change notification settings - Fork 34
/
envelope.go
128 lines (105 loc) · 2.93 KB
/
envelope.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package sneaker
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/kms"
)
// An Envelope encrypts and decrypts secrets with single-use KMS data keys using
// AES-256-GCM.
type Envelope struct {
KMS KeyManagement
}
// Seal generates a 256-bit data key using KMS and encrypts the given plaintext
// with AES-256-GCM using a random nonce. The ciphertext is appended to the
// nonce, which is in turn appended to the KMS data key ciphertext and returned.
func (e *Envelope) Seal(keyID string, ctxt map[string]string, plaintext []byte) ([]byte, error) {
key, err := e.KMS.GenerateDataKey(&kms.GenerateDataKeyInput{
EncryptionContext: e.context(ctxt),
KeySpec: aws.String("AES_256"),
KeyId: &keyID,
})
if err != nil {
return nil, err
}
ciphertext, err := encrypt(key.Plaintext, plaintext, []byte(*key.KeyId))
if err != nil {
return nil, err
}
return join(key.CiphertextBlob, ciphertext), nil
}
// Open takes the output of Seal and decrypts it. If any part of the ciphertext
// or context is modified, Seal will return an error instead of the decrypted
// data.
func (e *Envelope) Open(ctxt map[string]string, ciphertext []byte) ([]byte, error) {
key, ciphertext := split(ciphertext)
d, err := e.KMS.Decrypt(&kms.DecryptInput{
CiphertextBlob: key,
EncryptionContext: e.context(ctxt),
})
if err != nil {
if apiErr, ok := err.(awserr.Error); ok {
if apiErr.Code() == "InvalidCiphertextException" {
return nil, fmt.Errorf("unable to decrypt data key")
}
}
return nil, err
}
return decrypt(d.Plaintext, ciphertext, []byte(*d.KeyId))
}
func (e *Envelope) context(c map[string]string) map[string]*string {
ctxt := make(map[string]*string)
for k, v := range c {
ctxt[k] = aws.String(v)
}
return ctxt
}
func decrypt(key, ciphertext, data []byte) ([]byte, error) {
defer zero(key)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce, ciphertext := ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():]
return gcm.Open(nil, nonce, ciphertext, data)
}
func encrypt(key, plaintext, data []byte) ([]byte, error) {
defer zero(key)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, plaintext, data), nil
}
func join(a, b []byte) []byte {
res := make([]byte, len(a)+len(b)+4)
binary.BigEndian.PutUint32(res, uint32(len(a)))
copy(res[4:], a)
copy(res[len(a)+4:], b)
return res
}
func split(v []byte) ([]byte, []byte) {
l := binary.BigEndian.Uint32(v)
return v[4 : 4+l], v[4+l:]
}
func zero(b []byte) {
for i := range b {
b[i] = 0
}
}