Skip to content

Commit f387107

Browse files
chore(endpoint): harden crypto
Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
1 parent 847af6a commit f387107

File tree

4 files changed

+36
-29
lines changed

4 files changed

+36
-29
lines changed

endpoint/crypto.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,26 @@ import (
2525
"encoding/base64"
2626
"fmt"
2727
"io"
28-
29-
log "github.com/sirupsen/logrus"
3028
)
3129

3230
const standardGcmNonceSize = 12
3331

34-
// GenerateNonce creates a random nonce of a fixed size
35-
func GenerateNonce() ([]byte, error) {
32+
// GenerateNonce creates a random base64-encoded nonce of a fixed size.
33+
func GenerateNonce() (string, error) {
3634
nonce := make([]byte, standardGcmNonceSize)
3735
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
38-
return nil, err
36+
return "", err
3937
}
40-
return []byte(base64.StdEncoding.EncodeToString(nonce)), nil
38+
return base64.StdEncoding.EncodeToString(nonce), nil
4139
}
4240

43-
// EncryptText gzip input data and encrypts it using the supplied AES key
44-
func EncryptText(text string, aesKey []byte, nonceEncoded []byte) (string, error) {
41+
// EncryptText gzips input data and encrypts it using the supplied AES key.
42+
// nonceEncoded must be a base64-encoded nonce of standardGcmNonceSize bytes.
43+
func EncryptText(text string, aesKey []byte, nonceEncoded string) (string, error) {
44+
if len(nonceEncoded) == 0 {
45+
return "", fmt.Errorf("nonce must be provided")
46+
}
47+
4548
block, err := aes.NewCipher(aesKey)
4649
if err != nil {
4750
return "", err
@@ -53,7 +56,7 @@ func EncryptText(text string, aesKey []byte, nonceEncoded []byte) (string, error
5356
}
5457

5558
nonce := make([]byte, standardGcmNonceSize)
56-
if _, err = base64.StdEncoding.Decode(nonce, nonceEncoded); err != nil {
59+
if _, err = base64.StdEncoding.Decode(nonce, []byte(nonceEncoded)); err != nil {
5760
return "", err
5861
}
5962

@@ -66,40 +69,38 @@ func EncryptText(text string, aesKey []byte, nonceEncoded []byte) (string, error
6669
return base64.StdEncoding.EncodeToString(cipherData), nil
6770
}
6871

69-
// DecryptText decrypt gziped data using a supplied AES encryption key ang ungzip it
70-
// in case of decryption failed, will return original input and decryption error
72+
// DecryptText decrypts data using the supplied AES encryption key and decompresses it.
73+
// Returns the plaintext, the base64-encoded nonce, and any error.
7174
func DecryptText(text string, aesKey []byte) (string, string, error) {
7275
block, err := aes.NewCipher(aesKey)
7376
if err != nil {
7477
return "", "", err
7578
}
76-
gcm, err := cipher.NewGCM(block)
79+
gcm, err := cipher.NewGCMWithNonceSize(block, standardGcmNonceSize)
7780
if err != nil {
7881
return "", "", err
7982
}
80-
nonceSize := gcm.NonceSize()
8183
data, err := base64.StdEncoding.DecodeString(text)
8284
if err != nil {
8385
return "", "", err
8486
}
85-
if len(data) <= nonceSize {
86-
return "", "", fmt.Errorf("the encoded data from text %#v is shorter than %#v bytes and can't be decoded", text, nonceSize)
87+
if len(data) <= standardGcmNonceSize {
88+
return "", "", fmt.Errorf("encrypted data too short: got %d bytes, need more than %d", len(data), standardGcmNonceSize)
8789
}
88-
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
90+
nonce, ciphertext := data[:standardGcmNonceSize], data[standardGcmNonceSize:]
8991
plaindata, err := gcm.Open(nil, nonce, ciphertext, nil)
9092
if err != nil {
9193
return "", "", err
9294
}
9395
plaindata, err = decompressData(plaindata)
9496
if err != nil {
95-
log.Debugf("Failed to decompress data based on the base64 encoded text %#v. Got error %#v.", text, err)
9697
return "", "", err
9798
}
9899

99100
return string(plaindata), base64.StdEncoding.EncodeToString(nonce), nil
100101
}
101102

102-
// decompressData gzip compressed data
103+
// decompressData decompresses gzip-compressed data.
103104
func decompressData(data []byte) ([]byte, error) {
104105
gz, err := gzip.NewReader(bytes.NewBuffer(data))
105106
if err != nil {
@@ -114,15 +115,14 @@ func decompressData(data []byte) ([]byte, error) {
114115
return b.Bytes(), nil
115116
}
116117

117-
// compressData by gzip, for minify data stored in registry
118+
// compressData compresses data using gzip to minimize storage in the registry.
118119
func compressData(data []byte) ([]byte, error) {
119120
var b bytes.Buffer
120121
gz, err := gzip.NewWriterLevel(&b, gzip.BestCompression)
121122
if err != nil {
122123
return nil, err
123124
}
124125

125-
defer gz.Close()
126126
if _, err = gz.Write(data); err != nil {
127127
return nil, err
128128
}

endpoint/crypto_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ import (
2727
)
2828

2929
func TestEncrypt(t *testing.T) {
30-
// Verify that text encryption and decryption works
30+
// Verify that nil nonce is rejected
3131
aesKey := []byte("s%zF`.*'5`9.AhI2!B,.~hmbs^.*TL?;")
3232
plaintext := "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
33-
encryptedtext, err := EncryptText(plaintext, aesKey, nil)
33+
_, err := EncryptText(plaintext, aesKey, "")
34+
require.EqualError(t, err, "nonce must be provided")
35+
36+
// Verify that text encryption and decryption works with a generated nonce
37+
nonce, err := GenerateNonce()
38+
require.NoError(t, err)
39+
encryptedtext, err := EncryptText(plaintext, aesKey, nonce)
3440
require.NoError(t, err)
3541
decryptedtext, _, err := DecryptText(encryptedtext, aesKey)
3642
require.NoError(t, err)
@@ -67,7 +73,7 @@ func TestGenerateNonceSuccess(t *testing.T) {
6773
require.NotEmpty(t, nonce)
6874

6975
// Test nonce length
70-
decodedNonce, err := base64.StdEncoding.DecodeString(string(nonce))
76+
decodedNonce, err := base64.StdEncoding.DecodeString(nonce)
7177
require.NoError(t, err)
7278
require.Len(t, decodedNonce, standardGcmNonceSize)
7379
}
@@ -82,7 +88,7 @@ func TestGenerateNonceError(t *testing.T) {
8288

8389
nonce, err := GenerateNonce()
8490
require.Error(t, err)
85-
require.Nil(t, nonce)
91+
require.Empty(t, nonce)
8692
}
8793

8894
type faultyReader struct{}

endpoint/labels.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,25 +133,26 @@ func (l Labels) Serialize(withQuotes bool, txtEncryptEnabled bool, aesKey []byte
133133
return l.SerializePlain(withQuotes)
134134
}
135135

136-
var encryptionNonce []byte
136+
var encryptionNonce string
137137
if extractedNonce, nonceExists := l[txtEncryptionNonce]; nonceExists {
138-
encryptionNonce = []byte(extractedNonce)
138+
encryptionNonce = extractedNonce
139139
} else {
140140
var err error
141141
encryptionNonce, err = GenerateNonce()
142142
if err != nil {
143143
log.Fatalf("Failed to generate cryptographic nonce %#v.", err)
144144
}
145-
l[txtEncryptionNonce] = string(encryptionNonce)
145+
l[txtEncryptionNonce] = encryptionNonce
146146
}
147147

148148
text := l.SerializePlain(false)
149149
log.Debugf("Encrypt the serialized text %#v before returning it.", text)
150150
var err error
151151
text, err = EncryptText(text, aesKey, encryptionNonce)
152152
if err != nil {
153+
// TODO: review if we could return error instead of crashing the external-dns
153154
// if encryption failed, the external-dns will crash
154-
log.Fatalf("Failed to encrypt the text %#v using the encryption key %#v. Got error %#v.", text, aesKey, err)
155+
log.Fatalf("Failed to encrypt the text: %v", err)
155156
}
156157

157158
if withQuotes {

endpoint/labels_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (suite *LabelsSuite) TestEncryptionFailed() {
105105
_ = foo.Serialize(false, true, []byte("wrong-key"))
106106

107107
suite.True(fatalCrash, "should fail if encryption key is wrong")
108-
suite.Contains(b.String(), "Failed to encrypt the text")
108+
suite.Contains(b.String(), "Failed to encrypt the text:")
109109
}
110110

111111
func (suite *LabelsSuite) TestEncryptionFailedFaultyReader() {

0 commit comments

Comments
 (0)