Skip to content

Commit

Permalink
Test compatibility with FIPS-enabled OpenSSL 1.0.2
Browse files Browse the repository at this point in the history
Some of the standard library crypto/cipher AES-GCM tests fail when using
OpenSSL 1.0.2 in FIPS mode, but pass with the same library when FIPS
mode is disabled. Extend the AES-GCM tests to cover all the failure
cases caught by the standard library tests and build the OpenSSL 1.0.2
library used in CI to be FIPS enabled.

Link the OpenSSL 1.0.2 CI build against FIPS Object Module 2.0.1 rather
than the latest version (2.0.16) as at least one commercially-supported
FIPS validated build of OpenSSL 1.0.2 is known to use that version of
the FIPS Object Module and some of the failures seen with 2.0.1 do not
reproduce with 2.0.16.

Signed-off-by: Cory Snider <csnider@mirantis.com>
  • Loading branch information
corhere committed Aug 25, 2023
1 parent 816703e commit 4189af1
Show file tree
Hide file tree
Showing 4 changed files with 876 additions and 21 deletions.
67 changes: 47 additions & 20 deletions aes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,53 @@ func TestNewGCMNonce(t *testing.T) {
}

func TestSealAndOpen(t *testing.T) {
key := []byte("D249BF6DEC97B1EBD69BC4D6B3A3C49D")
ci, err := NewAESCipher(key)
if err != nil {
t.Fatal(err)
}
c := ci.(*aesCipher)
gcm, err := c.NewGCM(gcmStandardNonceSize, gcmTagSize)
if err != nil {
t.Fatal(err)
}
nonce := []byte{0x91, 0xc7, 0xa7, 0x54, 0x52, 0xef, 0x10, 0xdb, 0x91, 0xa8, 0x6c, 0xf9}
plainText := []byte{0x01, 0x02, 0x03}
additionalData := []byte{0x05, 0x05, 0x07}
sealed := gcm.Seal(nil, nonce, plainText, additionalData)
decrypted, err := gcm.Open(nil, nonce, sealed, additionalData)
if err != nil {
t.Error(err)
}
if !bytes.Equal(decrypted, plainText) {
t.Errorf("unexpected decrypted result\ngot: %#v\nexp: %#v", decrypted, plainText)
for _, tt := range aesGCMTests {
t.Run(tt.description, func(t *testing.T) {
ci, err := NewAESCipher(tt.key)
if err != nil {
t.Fatalf("NewAESCipher() err = %v", err)
}
c := ci.(*aesCipher)
gcm, err := c.NewGCM(gcmStandardNonceSize, gcmTagSize)
if err != nil {
t.Fatalf("c.NewGCM() err = %v", err)
}

sealed := gcm.Seal(nil, tt.nonce, tt.plaintext, tt.aad)
if !bytes.Equal(sealed, tt.ciphertext) {
t.Errorf("unexpected sealed result\ngot: %#v\nexp: %#v", sealed, tt.ciphertext)
}

decrypted, err := gcm.Open(nil, tt.nonce, tt.ciphertext, tt.aad)
if err != nil {
t.Errorf("gcm.Open() err = %v", err)
}
if !bytes.Equal(decrypted, tt.plaintext) {
t.Errorf("unexpected decrypted result\ngot: %#v\nexp: %#v", decrypted, tt.plaintext)
}

// Test that open fails if the ciphertext is modified.
tt.ciphertext[0] ^= 0x80
_, err = gcm.Open(nil, tt.nonce, tt.ciphertext, tt.aad)
if err != errOpen {
t.Errorf("expected authentication error for tampered message\ngot: %#v", err)
}
tt.ciphertext[0] ^= 0x80

// Test that the ciphertext can be opened using a fresh context
// which was not previously used to seal the same message.
gcm, err = c.NewGCM(gcmStandardNonceSize, gcmTagSize)
if err != nil {
t.Fatalf("c.NewGCM() err = %v", err)
}
decrypted, err = gcm.Open(nil, tt.nonce, tt.ciphertext, tt.aad)
if err != nil {
t.Errorf("fresh GCM instance: gcm.Open() err = %v", err)
}
if !bytes.Equal(decrypted, tt.plaintext) {
t.Errorf("fresh GCM instance: unexpected decrypted result\ngot: %#v\nexp: %#v", decrypted, tt.plaintext)
}
})
}
}

Expand Down
116 changes: 116 additions & 0 deletions cmd/gentestvectors/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// gentestvectors emits cryptographic test vectors using the Go standard library
// cryptographic routines to test the OpenSSL bindings.
package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"flag"
"fmt"
"go/format"
"io"
"log"
"math/rand"
"os"
"path/filepath"
)

var outputPath = flag.String("out", "", "output path (default stdout)")

func init() {
log.SetFlags(log.Llongfile)
log.SetOutput(os.Stderr)
}

func main() {
flag.Parse()

var b bytes.Buffer
fmt.Fprint(&b, "// Code generated by cmd/gentestvectors. DO NOT EDIT.\n\n")
if *outputPath != "" {
fmt.Fprintf(&b, "//go"+":generate go run github.com/golang-fips/openssl/v2/cmd/gentestvectors -out %s\n\n", filepath.Base(*outputPath))
}

pkg := "openssl"
if gopackage := os.Getenv("GOPACKAGE"); gopackage != "" {
pkg = gopackage
}
fmt.Fprintf(&b, "package %s\n\n", pkg)

aesGCM(&b)

generated, err := format.Source(b.Bytes())
if err != nil {
log.Fatalf("failed to format generated code: %v", err)
}

if *outputPath != "" {
err := os.WriteFile(*outputPath, generated, 0o644)
if err != nil {
log.Fatalf("failed to write output file: %v\n", err)
}
} else {
_, _ = os.Stdout.Write(generated)
}
}

func aesGCM(w io.Writer) {
r := rand.New(rand.NewSource(0))

fmt.Fprintln(w, `var aesGCMTests = []struct {
description string
key, nonce, plaintext, aad, ciphertext []byte
}{`)

for _, keyLen := range []int{16, 24, 32} {
for _, aadLen := range []int{0, 1, 3, 13, 30} {
for _, plaintextLen := range []int{0, 1, 3, 13, 16, 51} {
if aadLen == 0 && plaintextLen == 0 {
continue
}

key := randbytes(r, keyLen)
nonce := randbytes(r, 12)
plaintext := randbytes(r, plaintextLen)
aad := randbytes(r, aadLen)

c, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aead, err := cipher.NewGCM(c)
if err != nil {
panic(err)
}
ciphertext := aead.Seal(nil, nonce, plaintext, aad)

fmt.Fprint(w, "\t{\n")
fmt.Fprintf(w, "\t\tdescription: \"AES-%d/AAD=%d/Plaintext=%d\",\n", keyLen*8, aadLen, plaintextLen)
printBytesField(w, "key", key)
printBytesField(w, "nonce", nonce)
printBytesField(w, "plaintext", plaintext)
printBytesField(w, "aad", aad)
printBytesField(w, "ciphertext", ciphertext)
fmt.Fprint(w, "\t},\n")
}
}
}
fmt.Fprintln(w, "}")
}

func randbytes(r *rand.Rand, n int) []byte {
if n == 0 {
return nil
}
b := make([]byte, n)
r.Read(b)
return b
}

func printBytesField(w io.Writer, name string, b []byte) {
if len(b) == 0 {
return
}
fmt.Fprintf(w, "\t\t%s: %#v,\n", name, b)
}
34 changes: 33 additions & 1 deletion scripts/openssl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,41 @@ case "$version" in
"1.0.2")
tag="OpenSSL_1_0_2u"
sha256="82fa58e3f273c53128c6fe7e3635ec8cda1319a10ce1ad50a987c3df0deeef05"
config="shared"
fipsmodule_version="2.0.1"
fipsmodule_tag="OpenSSL-fips-2_0_1"
fipsmodule_sha256="6645895f43a0229dd4b89d27874fdd91fee70d9671fff954d3da448d5fc1d331"
config="shared fips --with-fipsdir=/usr/local/src/openssl-fips-$fipsmodule_version/dist"
make="build_libs"
install=""
;;
"1.1.0")
tag="OpenSSL_1_1_0l"
sha256="e2acf0cf58d9bff2b42f2dc0aee79340c8ffe2c5e45d3ca4533dd5d4f5775b1d"
fipsmodule_version=""
config="shared"
make="build_libs"
install=""
;;
"1.1.1")
tag="OpenSSL_1_1_1m"
sha256="36ae24ad7cf0a824d0b76ac08861262e47ec541e5d0f20e6d94bab90b2dab360"
fipsmodule_version=""
config="shared"
make="build_libs"
install=""
;;
"3.0.1")
tag="openssl-3.0.1";
sha256="2a9dcf05531e8be96c296259e817edc41619017a4bf3e229b4618a70103251d5"
fipsmodule_version=""
config="enable-fips"
make="build_libs"
install="install_fips"
;;
"3.0.9")
tag="openssl-3.0.9";
sha256="2eec31f2ac0e126ff68d8107891ef534159c4fcfb095365d4cd4dc57d82616ee"
fipsmodule_version=""
config="enable-fips"
make="build_libs"
install="install_fips"
Expand All @@ -58,10 +65,35 @@ tar -xzf "$tag.tar.gz"
rm -rf "openssl-$version"
mv "openssl-$tag" "openssl-$version"

if [ -n "$fipsmodule_version" ]; then
wget -O "$fipsmodule_tag.tar.gz" "https://github.com/openssl/openssl/archive/refs/tags/$fipsmodule_tag.tar.gz"
echo "$fipsmodule_sha256 $fipsmodule_tag.tar.gz" | sha256sum -c -
rm -rf "openssl-$fipsmodule_tag"
tar -xzf "$fipsmodule_tag.tar.gz"

rm -rf "openssl-fips-$fipsmodule_version"
mv "openssl-$fipsmodule_tag" "openssl-fips-$fipsmodule_version"
(
cd "openssl-fips-$fipsmodule_version"
mkdir dist
./config -d shared fipscanisteronly --prefix=$(pwd)/dist
make
make install
)
fi

cd "openssl-$version"
# -d makes a debug build which helps with debugging memory issues and
# other problems. It's not necessary for normal use.
./config -d $config

# OpenSSL 1.0.2 ./config prompts the user to run `make depend` before `make`
# when configuring in debug mode. OpenSSL 1.1.0 and above handle this
# automatically.
if [ "$version" == "1.0.2" ]; then
make depend
fi

make -j$(nproc) $make
if [ -n "$install" ]; then
make $install
Expand Down
Loading

0 comments on commit 4189af1

Please sign in to comment.