Skip to content

Commit 61234a9

Browse files
authored
Merge pull request #111 from corhere/fix-aes-gcm-102fips
Fix AES-GCM decryption on OpenSSL 1.0.2-fips
2 parents 08f07a7 + 9b5d63e commit 61234a9

File tree

14 files changed

+935
-50
lines changed

14 files changed

+935
-50
lines changed

aes_test.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -81,25 +81,52 @@ func TestNewGCMNonce(t *testing.T) {
8181
}
8282

8383
func TestSealAndOpen(t *testing.T) {
84-
key := []byte("D249BF6DEC97B1EBD69BC4D6B3A3C49D")
85-
ci, err := openssl.NewAESCipher(key)
86-
if err != nil {
87-
t.Fatal(err)
88-
}
89-
gcm, err := cipher.NewGCM(ci)
90-
if err != nil {
91-
t.Fatal(err)
92-
}
93-
nonce := []byte{0x91, 0xc7, 0xa7, 0x54, 0x52, 0xef, 0x10, 0xdb, 0x91, 0xa8, 0x6c, 0xf9}
94-
plainText := []byte{0x01, 0x02, 0x03}
95-
additionalData := []byte{0x05, 0x05, 0x07}
96-
sealed := gcm.Seal(nil, nonce, plainText, additionalData)
97-
decrypted, err := gcm.Open(nil, nonce, sealed, additionalData)
98-
if err != nil {
99-
t.Error(err)
100-
}
101-
if !bytes.Equal(decrypted, plainText) {
102-
t.Errorf("unexpected decrypted result\ngot: %#v\nexp: %#v", decrypted, plainText)
84+
for _, tt := range aesGCMTests {
85+
t.Run(tt.description, func(t *testing.T) {
86+
ci, err := openssl.NewAESCipher(tt.key)
87+
if err != nil {
88+
t.Fatalf("NewAESCipher() err = %v", err)
89+
}
90+
gcm, err := cipher.NewGCM(ci)
91+
if err != nil {
92+
t.Fatalf("cipher.NewGCM() err = %v", err)
93+
}
94+
95+
sealed := gcm.Seal(nil, tt.nonce, tt.plaintext, tt.aad)
96+
if !bytes.Equal(sealed, tt.ciphertext) {
97+
t.Errorf("unexpected sealed result\ngot: %#v\nexp: %#v", sealed, tt.ciphertext)
98+
}
99+
100+
decrypted, err := gcm.Open(nil, tt.nonce, tt.ciphertext, tt.aad)
101+
if err != nil {
102+
t.Errorf("gcm.Open() err = %v", err)
103+
}
104+
if !bytes.Equal(decrypted, tt.plaintext) {
105+
t.Errorf("unexpected decrypted result\ngot: %#v\nexp: %#v", decrypted, tt.plaintext)
106+
}
107+
108+
// Test that open fails if the ciphertext is modified.
109+
tt.ciphertext[0] ^= 0x80
110+
_, err = gcm.Open(nil, tt.nonce, tt.ciphertext, tt.aad)
111+
if err != openssl.ErrOpen {
112+
t.Errorf("expected authentication error for tampered message\ngot: %#v", err)
113+
}
114+
tt.ciphertext[0] ^= 0x80
115+
116+
// Test that the ciphertext can be opened using a fresh context
117+
// which was not previously used to seal the same message.
118+
gcm, err = cipher.NewGCM(ci)
119+
if err != nil {
120+
t.Fatalf("cipher.NewGCM() err = %v", err)
121+
}
122+
decrypted, err = gcm.Open(nil, tt.nonce, tt.ciphertext, tt.aad)
123+
if err != nil {
124+
t.Errorf("fresh GCM instance: gcm.Open() err = %v", err)
125+
}
126+
if !bytes.Equal(decrypted, tt.plaintext) {
127+
t.Errorf("fresh GCM instance: unexpected decrypted result\ngot: %#v\nexp: %#v", decrypted, tt.plaintext)
128+
}
129+
})
103130
}
104131
}
105132

cipher.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ func newCipherCtx(kind cipherKind, mode cipherMode, encrypt cipherOp, key, iv []
513513
cipher = nil
514514
}
515515
if C.go_openssl_EVP_CipherInit_ex(ctx, cipher, nil, base(key), base(iv), C.int(encrypt)) != 1 {
516-
return nil, fail("unable to initialize EVP cipher ctx")
516+
return nil, newOpenSSLError("unable to initialize EVP cipher ctx")
517517
}
518518
return ctx, nil
519519
}

cmd/gentestvectors/main.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// gentestvectors emits cryptographic test vectors using the Go standard library
2+
// cryptographic routines to test the OpenSSL bindings.
3+
package main
4+
5+
import (
6+
"bytes"
7+
"crypto/aes"
8+
"crypto/cipher"
9+
"flag"
10+
"fmt"
11+
"go/format"
12+
"io"
13+
"log"
14+
"math/rand"
15+
"os"
16+
"path/filepath"
17+
)
18+
19+
var outputPath = flag.String("out", "", "output path (default stdout)")
20+
21+
func init() {
22+
log.SetFlags(log.Llongfile)
23+
log.SetOutput(os.Stderr)
24+
}
25+
26+
func main() {
27+
flag.Parse()
28+
29+
var b bytes.Buffer
30+
fmt.Fprint(&b, "// Code generated by cmd/gentestvectors. DO NOT EDIT.\n\n")
31+
if *outputPath != "" {
32+
fmt.Fprintf(&b, "//go"+":generate go run github.com/golang-fips/openssl/v2/cmd/gentestvectors -out %s\n\n", filepath.Base(*outputPath))
33+
}
34+
35+
pkg := "openssl_test"
36+
if gopackage := os.Getenv("GOPACKAGE"); gopackage != "" {
37+
pkg = gopackage + "_test"
38+
}
39+
fmt.Fprintf(&b, "package %s\n\n", pkg)
40+
41+
aesGCM(&b)
42+
43+
generated, err := format.Source(b.Bytes())
44+
if err != nil {
45+
log.Fatalf("failed to format generated code: %v", err)
46+
}
47+
48+
if *outputPath != "" {
49+
err := os.WriteFile(*outputPath, generated, 0o644)
50+
if err != nil {
51+
log.Fatalf("failed to write output file: %v\n", err)
52+
}
53+
} else {
54+
_, _ = os.Stdout.Write(generated)
55+
}
56+
}
57+
58+
func aesGCM(w io.Writer) {
59+
r := rand.New(rand.NewSource(0))
60+
61+
fmt.Fprintln(w, `var aesGCMTests = []struct {
62+
description string
63+
key, nonce, plaintext, aad, ciphertext []byte
64+
}{`)
65+
66+
for _, keyLen := range []int{16, 24, 32} {
67+
for _, aadLen := range []int{0, 1, 3, 13, 30} {
68+
for _, plaintextLen := range []int{0, 1, 3, 13, 16, 51} {
69+
if aadLen == 0 && plaintextLen == 0 {
70+
continue
71+
}
72+
73+
key := randbytes(r, keyLen)
74+
nonce := randbytes(r, 12)
75+
plaintext := randbytes(r, plaintextLen)
76+
aad := randbytes(r, aadLen)
77+
78+
c, err := aes.NewCipher(key)
79+
if err != nil {
80+
panic(err)
81+
}
82+
aead, err := cipher.NewGCM(c)
83+
if err != nil {
84+
panic(err)
85+
}
86+
ciphertext := aead.Seal(nil, nonce, plaintext, aad)
87+
88+
fmt.Fprint(w, "\t{\n")
89+
fmt.Fprintf(w, "\t\tdescription: \"AES-%d/AAD=%d/Plaintext=%d\",\n", keyLen*8, aadLen, plaintextLen)
90+
printBytesField(w, "key", key)
91+
printBytesField(w, "nonce", nonce)
92+
printBytesField(w, "plaintext", plaintext)
93+
printBytesField(w, "aad", aad)
94+
printBytesField(w, "ciphertext", ciphertext)
95+
fmt.Fprint(w, "\t},\n")
96+
}
97+
}
98+
}
99+
fmt.Fprintln(w, "}")
100+
}
101+
102+
func randbytes(r *rand.Rand, n int) []byte {
103+
if n == 0 {
104+
return nil
105+
}
106+
b := make([]byte, n)
107+
r.Read(b)
108+
return b
109+
}
110+
111+
func printBytesField(w io.Writer, name string, b []byte) {
112+
if len(b) == 0 {
113+
return
114+
}
115+
fmt.Fprintf(w, "\t\t%s: %#v,\n", name, b)
116+
}

des.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414
// If CBC is also supported, then the returned cipher.Block
1515
// will also implement NewCBCEncrypter and NewCBCDecrypter.
1616
func SupportsDESCipher() bool {
17-
// True for stock OpenSSL 1.
17+
// True for stock OpenSSL 1 w/o FIPS.
1818
// False for stock OpenSSL 3 unless the legacy provider is available.
19-
return loadCipher(cipherDES, cipherModeECB) != nil
19+
return (versionAtOrAbove(1, 1, 0) || !FIPS()) && loadCipher(cipherDES, cipherModeECB) != nil
2020
}
2121

2222
// SupportsTripleDESCipher returns true if NewTripleDESCipher is supported,

ed25519.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func SupportsEd25519() bool {
3737
onceSupportsEd25519.Do(func() {
3838
switch vMajor {
3939
case 1:
40-
supportsEd25519 = version1_1_1_or_above()
40+
supportsEd25519 = versionAtOrAbove(1, 1, 1)
4141
case 3:
4242
name := C.CString("ED25519")
4343
defer C.free(unsafe.Pointer(name))

evp.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,13 @@ func cryptoHashToMD(ch crypto.Hash) (md C.GO_EVP_MD_PTR) {
7272
}
7373
switch ch {
7474
case crypto.MD4:
75-
return C.go_openssl_EVP_md4()
75+
if versionAtOrAbove(1, 1, 0) || !FIPS() {
76+
return C.go_openssl_EVP_md4()
77+
}
7678
case crypto.MD5:
77-
return C.go_openssl_EVP_md5()
79+
if versionAtOrAbove(1, 1, 0) || !FIPS() {
80+
return C.go_openssl_EVP_md5()
81+
}
7882
case crypto.SHA1:
7983
return C.go_openssl_EVP_sha1()
8084
case crypto.SHA224:
@@ -86,19 +90,19 @@ func cryptoHashToMD(ch crypto.Hash) (md C.GO_EVP_MD_PTR) {
8690
case crypto.SHA512:
8791
return C.go_openssl_EVP_sha512()
8892
case crypto.SHA3_224:
89-
if version1_1_1_or_above() {
93+
if versionAtOrAbove(1, 1, 1) {
9094
return C.go_openssl_EVP_sha3_224()
9195
}
9296
case crypto.SHA3_256:
93-
if version1_1_1_or_above() {
97+
if versionAtOrAbove(1, 1, 1) {
9498
return C.go_openssl_EVP_sha3_256()
9599
}
96100
case crypto.SHA3_384:
97-
if version1_1_1_or_above() {
101+
if versionAtOrAbove(1, 1, 1) {
98102
return C.go_openssl_EVP_sha3_384()
99103
}
100104
case crypto.SHA3_512:
101-
if version1_1_1_or_above() {
105+
if versionAtOrAbove(1, 1, 1) {
102106
return C.go_openssl_EVP_sha3_512()
103107
}
104108
}

goopenssl.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ go_openssl_fips_enabled(void* handle)
6060
// and assign them to their corresponding function pointer
6161
// defined in goopenssl.h.
6262
void
63-
go_openssl_load_functions(void* handle, int major, int minor, int patch)
63+
go_openssl_load_functions(void* handle, unsigned int major, unsigned int minor, unsigned int patch)
6464
{
6565
#define DEFINEFUNC_INTERNAL(name, func) \
6666
_g_##name = dlsym(handle, func); \
6767
if (_g_##name == NULL) { \
68-
fprintf(stderr, "Cannot get required symbol " #func " from libcrypto version %d.%d\n", major, minor); \
68+
fprintf(stderr, "Cannot get required symbol " #func " from libcrypto version %u.%u\n", major, minor); \
6969
abort(); \
7070
}
7171
#define DEFINEFUNC(ret, func, args, argscall) \

goopenssl.h

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ int go_openssl_version_major(void* handle);
2424
int go_openssl_version_minor(void* handle);
2525
int go_openssl_version_patch(void* handle);
2626
int go_openssl_thread_setup(void);
27-
void go_openssl_load_functions(void* handle, int major, int minor, int patch);
27+
void go_openssl_load_functions(void* handle, unsigned int major, unsigned int minor, unsigned int patch);
2828
const GO_EVP_MD_PTR go_openssl_EVP_md5_sha1_backport(void);
2929

3030
// Define pointers to all the used OpenSSL functions.
@@ -144,22 +144,40 @@ go_openssl_EVP_CIPHER_CTX_open_wrapper(const GO_EVP_CIPHER_CTX_PTR ctx,
144144
const unsigned char *aad, int aad_len,
145145
const unsigned char *tag)
146146
{
147-
if (in_len == 0) in = (const unsigned char *)"";
147+
if (in_len == 0) {
148+
in = (const unsigned char *)"";
149+
// OpenSSL 1.0.2 in FIPS mode contains a bug: it will fail to verify
150+
// unless EVP_DecryptUpdate is called at least once with a non-NULL
151+
// output buffer. OpenSSL will not dereference the output buffer when
152+
// the input length is zero, so set it to an arbitrary non-NULL pointer
153+
// to satisfy OpenSSL when the caller only has authenticated additional
154+
// data (AAD) to verify. While a stack-allocated buffer could be used,
155+
// that would risk a stack-corrupting buffer overflow if OpenSSL
156+
// unexpectedly dereferenced it. Instead pass a value which would
157+
// segfault if dereferenced on any modern platform where a NULL-pointer
158+
// dereference would also segfault.
159+
if (out == NULL) out = (unsigned char *)1;
160+
}
148161
if (aad_len == 0) aad = (const unsigned char *)"";
149162

150163
if (go_openssl_EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, nonce) != 1)
151164
return 0;
152165

166+
// OpenSSL 1.0.x FIPS Object Module 2.0 versions below 2.0.5 require that
167+
// the tag be set before the ciphertext, otherwise EVP_DecryptUpdate returns
168+
// an error. At least one extant commercially-supported, FIPS validated
169+
// build of OpenSSL 1.0.2 uses FIPS module version 2.0.1. Set the tag first
170+
// to maximize compatibility with all OpenSSL version combinations.
171+
if (go_openssl_EVP_CIPHER_CTX_ctrl(ctx, GO_EVP_CTRL_GCM_SET_TAG, 16, (unsigned char *)(tag)) != 1)
172+
return 0;
173+
153174
int discard_len, out_len;
154175
if (go_openssl_EVP_DecryptUpdate(ctx, NULL, &discard_len, aad, aad_len) != 1
155176
|| go_openssl_EVP_DecryptUpdate(ctx, out, &out_len, in, in_len) != 1)
156177
{
157178
return 0;
158179
}
159180

160-
if (go_openssl_EVP_CIPHER_CTX_ctrl(ctx, GO_EVP_CTRL_GCM_SET_TAG, 16, (unsigned char *)(tag)) != 1)
161-
return 0;
162-
163181
if (go_openssl_EVP_DecryptFinal_ex(ctx, out + out_len, &discard_len) != 1)
164182
return 0;
165183

hkdf.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
func SupportsHKDF() bool {
16-
return version1_1_1_or_above()
16+
return versionAtOrAbove(1, 1, 1)
1717
}
1818

1919
func newHKDF(h func() hash.Hash, mode C.int) (*hkdf, error) {

init.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
// as reported by the OpenSSL API.
1414
//
1515
// See Init() for details about file.
16-
func opensslInit(file string) (major, minor, patch int, err error) {
16+
func opensslInit(file string) (major, minor, patch uint, err error) {
1717
// Load the OpenSSL shared library using dlopen.
1818
handle, err := dlopen(file)
1919
if err != nil {
@@ -24,12 +24,13 @@ func opensslInit(file string) (major, minor, patch int, err error) {
2424
// Notice that major and minor could not match with the version parameter
2525
// in case the name of the shared library file differs from the OpenSSL
2626
// version it contains.
27-
major = int(C.go_openssl_version_major(handle))
28-
minor = int(C.go_openssl_version_minor(handle))
29-
patch = int(C.go_openssl_version_patch(handle))
30-
if major == -1 || minor == -1 || patch == -1 {
27+
imajor := int(C.go_openssl_version_major(handle))
28+
iminor := int(C.go_openssl_version_minor(handle))
29+
ipatch := int(C.go_openssl_version_patch(handle))
30+
if imajor < 0 || iminor < 0 || ipatch < 0 {
3131
return 0, 0, 0, errors.New("openssl: can't retrieve OpenSSL version")
3232
}
33+
major, minor, patch = uint(imajor), uint(iminor), uint(ipatch)
3334
var supported bool
3435
if major == 1 {
3536
supported = minor == 0 || minor == 1
@@ -43,7 +44,7 @@ func opensslInit(file string) (major, minor, patch int, err error) {
4344

4445
// Load the OpenSSL functions.
4546
// See shims.go for the complete list of supported functions.
46-
C.go_openssl_load_functions(handle, C.int(major), C.int(minor), C.int(patch))
47+
C.go_openssl_load_functions(handle, C.uint(major), C.uint(minor), C.uint(patch))
4748

4849
// Initialize OpenSSL.
4950
C.go_openssl_OPENSSL_init()

0 commit comments

Comments
 (0)