forked from hashicorp/memberlist
/
security.go
142 lines (121 loc) · 3.43 KB
/
security.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package memberlist
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
const (
encryptionVersion = 0
versionSize = 1
nonceSize = 12
tagSize = 16
maxPadOverhead = 15
blockSize = aes.BlockSize
)
// pkcs7encode is used to pad a byte buffer to a specific block size using
// the PKCS7 algorithm. "Ignores" some bytes to compensate for IV
func pkcs7encode(buf *bytes.Buffer, ignore, blockSize int) {
n := buf.Len() - ignore
more := blockSize - (n % blockSize)
if more == blockSize {
return
}
for i := 0; i < more; i++ {
buf.WriteByte(byte(more))
}
}
// pkcs7decode is used to decode a buffer that has been padded
func pkcs7decode(buf []byte, blockSize int) []byte {
if len(buf) == 0 {
panic("Cannot decode a PKCS7 buffer of zero length")
}
n := len(buf)
last := int(buf[n-1])
if last == 0 || last >= blockSize {
return buf
}
n -= (last % blockSize)
return buf[:n]
}
// encryptedLength is used to compute the buffer size needed
// for a message of given length
func encryptedLength(inp int) int {
// Determine the padding size
padding := blockSize - (inp % blockSize)
if padding == blockSize {
padding = 0
}
// Sum the extra parts to get total size
return versionSize + nonceSize + inp + padding + tagSize
}
// encryptPayload is used to encrypt a message with a given key.
// We make use of AES-128 in GCM mode. New byte buffer is the version,
// nonce, ciphertext and tag
func encryptPayload(key []byte, msg []byte, data []byte, dst *bytes.Buffer) error {
// Grow the buffer to make room for everything
offset := dst.Len()
dst.Grow(encryptedLength(len(msg)))
// Write the encryption version
dst.WriteByte(byte(encryptionVersion))
// Add a random nonce
io.CopyN(dst, rand.Reader, nonceSize)
afterNonce := dst.Len()
// Copy the message
io.Copy(dst, bytes.NewReader(msg))
// Ensure we are correctly padded
pkcs7encode(dst, offset+versionSize+nonceSize, aes.BlockSize)
// Get the AES block cipher
aesBlock, err := aes.NewCipher(key)
if err != nil {
return err
}
// Get the GCM cipher mode
gcm, err := cipher.NewGCM(aesBlock)
if err != nil {
return err
}
// Encrypt message using GCM
slice := dst.Bytes()[offset:]
nonce := slice[versionSize : versionSize+nonceSize]
src := slice[versionSize+nonceSize:]
out := gcm.Seal(nil, nonce, src, data)
// Truncate the plaintext, and write the cipher text
dst.Truncate(afterNonce)
dst.Write(out)
return nil
}
// decryptPayload is used to decrypt a message with a given key,
// and verify it's contents. Any padding will be removed, and a
// slice to the plaintext is returned. Decryption is done IN PLACE!
func decryptPayload(key []byte, msg []byte, data []byte) ([]byte, error) {
// Ensure the length is sane
if len(msg) <= encryptedLength(0) {
return nil, fmt.Errorf("Payload is too small to decrypt")
}
// Verify the version
if msg[0] != encryptionVersion {
return nil, fmt.Errorf("Unsupported encryption version %d", msg[0])
}
// Get the AES block cipher
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Get the GCM cipher mode
gcm, err := cipher.NewGCM(aesBlock)
if err != nil {
return nil, err
}
// Decrypt the message
nonce := msg[versionSize : versionSize+nonceSize]
ciphertext := msg[versionSize+nonceSize:]
plain, err := gcm.Open(nil, nonce, ciphertext, data)
if err != nil {
return nil, err
}
// Remove the PKCS7 padding
return pkcs7decode(plain, aes.BlockSize), nil
}