-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #3317 The GCM mode is recommended by OWASP and other authorities for secret encryption. As per @jhrozek's suggestion, use the cryptopasta implementation of AES-256-GCM (see link/copyright in code). This PR does not actually make use of the new algorithm yet, this will be done in a future PR.
- Loading branch information
Showing
5 changed files
with
231 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright 2024 Stacklok, Inc | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Adapted from: https://github.com/gtank/cryptopasta/blob/bc3a108a5776376aa811eea34b93383837994340/encrypt.go | ||
// cryptopasta - basic cryptography examples | ||
// | ||
// Written in 2015 by George Tankersley <george.tankersley@gmail.com> | ||
// | ||
// To the extent possible under law, the author(s) have dedicated all copyright | ||
// and related and neighboring rights to this software to the public domain | ||
// worldwide. This software is distributed without any warranty. | ||
// | ||
// You should have received a copy of the CC0 Public Domain Dedication along | ||
// with this software. If not, see // <http://creativecommons.org/publicdomain/zero/1.0/>. | ||
|
||
package algorithms | ||
|
||
import ( | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/rand" | ||
"errors" | ||
"io" | ||
) | ||
|
||
// AES256GCMAlgorithm provides symmetric authenticated encryption using 256-bit AES-GCM with a random nonce. | ||
type AES256GCMAlgorithm struct{} | ||
|
||
// Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of | ||
// the data and provides a check that it hasn't been altered. Output takes the | ||
// form nonce|ciphertext|tag where '|' indicates concatenation. | ||
func (_ *AES256GCMAlgorithm) Encrypt(plaintext []byte, key []byte) ([]byte, error) { | ||
if len(plaintext) > maxPlaintextSize { | ||
return nil, ErrExceedsMaxSize | ||
} | ||
|
||
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()) | ||
_, err = io.ReadFull(rand.Reader, nonce) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return gcm.Seal(nonce, nonce, plaintext, nil), nil | ||
} | ||
|
||
// Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of | ||
// the data and provides a check that it hasn't been altered. Expects input | ||
// form nonce|ciphertext|tag where '|' indicates concatenation. | ||
func (_ *AES256GCMAlgorithm) Decrypt(ciphertext []byte, key []byte) ([]byte, error) { | ||
block, err := aes.NewCipher(key[:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
gcm, err := cipher.NewGCM(block) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(ciphertext) < gcm.NonceSize() { | ||
return nil, errors.New("malformed ciphertext") | ||
} | ||
|
||
return gcm.Open(nil, | ||
ciphertext[:gcm.NonceSize()], | ||
ciphertext[gcm.NonceSize():], | ||
nil, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright 2024 Stacklok, Inc | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package algorithms_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/stacklok/minder/internal/crypto/algorithms" | ||
) | ||
|
||
func TestGCMEncrypt(t *testing.T) { | ||
t.Parallel() | ||
|
||
scenarios := []struct { | ||
Name string | ||
Key []byte | ||
Plaintext []byte | ||
ExpectedError string | ||
}{ | ||
{ | ||
Name: "GCM Encrypt rejects short key", | ||
Key: []byte{0xFF}, | ||
Plaintext: []byte(plaintext), | ||
ExpectedError: "invalid key size", | ||
}, | ||
{ | ||
Name: "GCM Encrypt rejects oversized plaintext", | ||
Key: key, | ||
Plaintext: make([]byte, 33*1024*1024), // 33MiB | ||
ExpectedError: algorithms.ErrExceedsMaxSize.Error(), | ||
}, | ||
{ | ||
Name: "GCM encrypts plaintext", | ||
Key: key, | ||
Plaintext: []byte(plaintext), | ||
}, | ||
} | ||
|
||
for _, scenario := range scenarios { | ||
t.Run(scenario.Name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
result, err := gcm.Encrypt(scenario.Plaintext, scenario.Key) | ||
if scenario.ExpectedError == "" { | ||
require.NoError(t, err) | ||
// validate by decrypting | ||
decrypted, err := gcm.Decrypt(result, key) | ||
require.NoError(t, err) | ||
require.Equal(t, scenario.Plaintext, decrypted) | ||
} else { | ||
require.ErrorContains(t, err, scenario.ExpectedError) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// This doesn't test decryption - that is tested in the happy path of the encrypt test | ||
func TestGCMDecrypt(t *testing.T) { | ||
t.Parallel() | ||
|
||
scenarios := []struct { | ||
Name string | ||
Key []byte | ||
Ciphertext []byte | ||
ExpectedError string | ||
}{ | ||
{ | ||
Name: "GCM Decrypt rejects short key", | ||
Key: []byte{0xFF}, | ||
Ciphertext: []byte(plaintext), | ||
ExpectedError: "invalid key size", | ||
}, | ||
{ | ||
Name: "GCM Decrypt rejects malformed ciphertext", | ||
Key: key, | ||
Ciphertext: make([]byte, 32), // 33MiB | ||
ExpectedError: "message authentication failed", | ||
}, | ||
{ | ||
Name: "GCM Decrypt rejects undersized key", | ||
Key: key, | ||
Ciphertext: []byte{0xFF}, | ||
ExpectedError: "malformed ciphertext", | ||
}, | ||
} | ||
|
||
for _, scenario := range scenarios { | ||
t.Run(scenario.Name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
_, err := gcm.Decrypt(scenario.Ciphertext, scenario.Key) | ||
require.ErrorContains(t, err, scenario.ExpectedError) | ||
}) | ||
} | ||
} | ||
|
||
var ( | ||
key = []byte{ | ||
0x5, 0x94, 0x74, 0xfd, 0xb7, 0xf9, 0x85, 0x9, 0x67, 0x8, 0x2D, 0xe8, 0x46, 0x8c, 0x76, 0xe2, | ||
0x7a, 0x85, 0x7f, 0xed, 0x67, 0xd4, 0xd5, 0x2c, 0x46, 0x00, 0xba, 0x44, 0x8d, 0x54, 0x20, 0xf1, | ||
} | ||
gcm = algorithms.AES256GCMAlgorithm{} | ||
) | ||
|
||
const plaintext = "Hello world" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters