Skip to content

boringcrypto: Not using FIPS compliant calls for SHA functions (also should we add md5? for easy proof of FIPS mode on) #66513

@evanskinner

Description

@evanskinner

Go version

go1.20.12

Output of go env in your module/workspace:

GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/tmp/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/tmp/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/golang"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/golang/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.20.12"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2938776592=/tmp/go-build -gno-record-gcc-switches"

What did you do?

After building a golang binary I wanted to prove that boringcrypto was using the openssl library in FIPS mode (my kernel was already in FIPS mode) and the way I had done that for other languages was to attempt an md5 hash and see that openssl threw an error. AFter ralising the the md5() function of golang is not implemented in boringcrypto I set about patching my copy by copying the way the sha1 functions work but for md5. After getting this to work I was surprised when the md5() function returned the hash, instead of returning an erorr. Using LD_DEBUG=symbols I could see that my binary was correctly calling into openssl for both an md5() call and a sha1() call:

		result := md5.Sum([]byte("hash me"))
		fmt.Printf("%x\n", result)
		result2 := sha1.Sum([]byte("hash me"))
		fmt.Printf("%x\n", result2)

when executrd with LD_DEBUG=symbols produced:

...
	  5752:	symbol=MD5_Final;  lookup in file=/lib64/libresolv.so.2 [0]
	  5752:	symbol=MD5_Final;  lookup in file=/lib64/libdl.so.2 [0]
	  5752:	symbol=MD5_Final;  lookup in file=/lib64/libpthread.so.0 [0]
	  5752:	symbol=MD5_Final;  lookup in file=/lib64/libc.so.6 [0]
	  5752:	symbol=MD5_Final;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
	  5752:	symbol=MD5_Final;  lookup in file=/lib64/libcrypto.so.1.1 [0]
17b31dce96b9d6c6d0a6ba95f47796fb
...
	  5752:	symbol=SHA1_Final;  lookup in file=/lib64/libresolv.so.2 [0]
	  5752:	symbol=SHA1_Final;  lookup in file=/lib64/libdl.so.2 [0]
	  5752:	symbol=SHA1_Final;  lookup in file=/lib64/libpthread.so.0 [0]
	  5752:	symbol=SHA1_Final;  lookup in file=/lib64/libc.so.6 [0]
	  5752:	symbol=SHA1_Final;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
	  5752:	symbol=SHA1_Final;  lookup in file=/lib64/libcrypto.so.1.1 [0]
43f932e4f7c6ecd136a695b7008694bb69d517bd

A bit of digging around and I found this section in the openssl fips page (https://www.openssl.org/docs/manmaster/man7/fips_module.html):

Applications written to use the OpenSSL 3.0 FIPS module should not use any legacy APIs or features that avoid the FIPS module. Specifically this includes:

Low level cryptographic APIs (use the high level APIs, such as EVP, instead)

It appears that the MD5_* and SHA1_* functions are considered low level APIs by openssl and so avoid the FIPS module. I tested this theory by creating a small C program that first uses the low level MD5 api and then the haigh level EVP_ MD5 api:

#include <stdio.h>
#include <string.h>
#include <openssl/md5.h>
#include <openssl/des.h>
#include <openssl/fips.h>
#include <openssl/ssl.h>
#include <openssl/evp.h>
#include <openssl/err.h>

void compute_md5(char *str, unsigned char digest[16]);

int main(int argc, char *argv[])
{
    if (argc == 2) {
	printf("Setting FIPS mode to %s\n",argv[1]);
#if OPENSSL_VERSION_MAJOR < 3
        FIPS_mode_set(atoi(argv[1]));
#else
        EVP_default_properties_enable_fips(NULL, atoi(argv[1]));
#endif
    }
    printf("FIPS Mode: %d\n",FIPS_mode());
    char data[] = "hash me";
    unsigned char md_value[EVP_MAX_MD_SIZE];
    unsigned int md_len, i;

    printf("Attempting md5 hash via MD5_ methods.\n");
    unsigned char digest[16];
    compute_md5("hash me", digest);
    for (int i = 0; i < 16; i++)
        printf("%02x", digest[i]);
    putchar ('\n');

    printf("Attempting md5 hash via EVP_ methods.\n");
    const EVP_MD *md = EVP_get_digestbyname("md5");
    if (md == NULL) {
        printf("Unknown message digest %s\n", "md5");
        exit(1);
    }
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!EVP_DigestInit_ex(ctx, md, NULL)) {
        printf("Message digest initialization failed.\n");
	ERR_print_errors_fp (stderr);
        EVP_MD_CTX_free(ctx);
        exit(1);
    }
    EVP_DigestUpdate(ctx, data, strlen(data));
    EVP_DigestFinal_ex(ctx, md_value, &md_len);
    for (int i = 0; i < md_len; i++)
        printf("%02x", md_value[i]);
    putchar ('\n');

    return 0;
}

void compute_md5(char *str, unsigned char digest[16]) {
    MD5_CTX ctx;
    MD5_Init(&ctx);
    MD5_Update(&ctx, str, strlen(str));
    MD5_Final(digest, &ctx);
}

Which when ran with FIPS enabled (either via the cmd line parameter, or on a kernel with fips=1) gives the output:

gcc md5_fips_test.c -o md5_fips_test -lssl -lcrypto
./md5_fips_test 1
Setting FIPS mode to 1
FIPS Mode: 1
Attempting md5 hash via MD5_ methods.
17b31dce96b9d6c6d0a6ba95f47796fb
Attempting md5 hash via EVP_ methods.
Message digest initialization failed.
140030451414848:error:060800C8:digital envelope routines:EVP_DigestInit_ex:disabled for FIPS:crypto/evp/digest.c:135:

Which seems to prove that the MD5_ methods, and so presumably all the SHA*_ methods do not go via the fips module. I wasn't able to find a way to configure the system to disallow a one of the SHA* hashes to test my theory with the SHA*_ functions though.

Note I was actually using go-toolset on Redhat, but I think the changes required are in the boringcrypto code in https://github.com/golang/go/blob/master/src/crypto/internal/boring/sha.go and other places.

I also think it would be handy to add an implementation of md5 (would be very simlar to the fixed sha implementation) in boringcrypto so that you can try an md5 hash and see it refused. It would also need to use the EVP_ methods.

What did you see happen?

Covered above, sorry.

What did you expect to see?

I expected to be able to prove that the binary was running openssl in fips mode, by seeing it deny a bad algorithmn, such as md5.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions