/usr/share/gocode/src/github.com/hashicorp/memberlist/security.go is in golang-github-hashicorp-memberlist-dev 0.1.0-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | package memberlist
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
/*
Encrypted messages are prefixed with an encryptionVersion byte
that is used for us to be able to properly encode/decode. We
currently support the following versions:
0 - AES-GCM 128, using PKCS7 padding
1 - AES-GCM 128, no padding. Padding not needed, caused bloat.
*/
type encryptionVersion uint8
const (
minEncryptionVersion encryptionVersion = 0
maxEncryptionVersion encryptionVersion = 1
)
const (
versionSize = 1
nonceSize = 12
tagSize = 16
maxPadOverhead = 16
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)
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 := buf[n-1]
n -= int(last)
return buf[:n]
}
// encryptOverhead returns the maximum possible overhead of encryption by version
func encryptOverhead(vsn encryptionVersion) int {
switch vsn {
case 0:
return 45 // Version: 1, IV: 12, Padding: 16, Tag: 16
case 1:
return 29 // Version: 1, IV: 12, Tag: 16
default:
panic("unsupported version")
}
}
// encryptedLength is used to compute the buffer size needed
// for a message of given length
func encryptedLength(vsn encryptionVersion, inp int) int {
// If we are on version 1, there is no padding
if vsn >= 1 {
return versionSize + nonceSize + inp + tagSize
}
// Determine the padding size
padding := blockSize - (inp % blockSize)
// 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(vsn encryptionVersion, key []byte, msg []byte, data []byte, dst *bytes.Buffer) error {
// 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
}
// Grow the buffer to make room for everything
offset := dst.Len()
dst.Grow(encryptedLength(vsn, len(msg)))
// Write the encryption version
dst.WriteByte(byte(vsn))
// Add a random nonce
io.CopyN(dst, rand.Reader, nonceSize)
afterNonce := dst.Len()
// Ensure we are correctly padded (only version 0)
if vsn == 0 {
io.Copy(dst, bytes.NewReader(msg))
pkcs7encode(dst, offset+versionSize+nonceSize, aes.BlockSize)
}
// Encrypt message using GCM
slice := dst.Bytes()[offset:]
nonce := slice[versionSize : versionSize+nonceSize]
// Message source depends on the encryption version.
// Version 0 uses padding, version 1 does not
var src []byte
if vsn == 0 {
src = slice[versionSize+nonceSize:]
} else {
src = msg
}
out := gcm.Seal(nil, nonce, src, data)
// Truncate the plaintext, and write the cipher text
dst.Truncate(afterNonce)
dst.Write(out)
return nil
}
// decryptMessage performs the actual decryption of ciphertext. This is in its
// own function to allow it to be called on all keys easily.
func decryptMessage(key, msg []byte, data []byte) ([]byte, error) {
// 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
}
// Success!
return plain, 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(keys [][]byte, msg []byte, data []byte) ([]byte, error) {
// Ensure we have at least one byte
if len(msg) == 0 {
return nil, fmt.Errorf("Cannot decrypt empty payload")
}
// Verify the version
vsn := encryptionVersion(msg[0])
if vsn > maxEncryptionVersion {
return nil, fmt.Errorf("Unsupported encryption version %d", msg[0])
}
// Ensure the length is sane
if len(msg) < encryptedLength(vsn, 0) {
return nil, fmt.Errorf("Payload is too small to decrypt: %d", len(msg))
}
for _, key := range keys {
plain, err := decryptMessage(key, msg, data)
if err == nil {
// Remove the PKCS7 padding for vsn 0
if vsn == 0 {
return pkcs7decode(plain, aes.BlockSize), nil
} else {
return plain, nil
}
}
}
return nil, fmt.Errorf("No installed keys could decrypt the message")
}
|