gocgofips

How can I check whether my golang app uses boringcrypto instead of the native golang crypto?


Context: I was reading multiple articles about making my golang app FIPS compliant (in other words, making my app use boringcrypto instead of the native golang crypto):

In short, they both say to run

# Build a binary and assert that it uses boringcrypto instead of the native golang crypto
RUN GOEXPERIMENT=boringcrypto go build . && \
    go tool nm fips-echo-server > tags.txt && \
    grep '_Cfunc__goboringcrypto_' tags.txt 1> /dev/null

and then expect the following output:

  e70fa0 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_AES_cbc_encrypt
  e70fc0 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_AES_ctr128_encrypt
  e70ff0 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_AES_decrypt
  e71000 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_AES_encrypt
  e71010 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_AES_set_decrypt_key
  e71050 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_AES_set_encrypt_key
  e71200 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_BN_bn2le_padded
  e71240 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_BN_free
  e71250 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_BN_le2bn
  e71300 T _cgo_d3bdb93f8e25_Cfunc__goboringcrypto_BN_new

that would contain _goboringcrypto_ (which would mean the app uses boringcrypto instead of the native golang crypto) or an empty output (which implies the app uses the native golang crypto instead of boringcrypto).

However my app's output when adding GOEXPERIMENT=boringcrypto is:

➜  GOEXPERIMENT=boringcrypto CGO_ENABLED=0 go build -o ./bin/app
➜  go tool nm bin/dapp | grep -i boring | cat 
 11230a0 T crypto/internal/boring.(*PrivateKeyECDH).PublicKey
 1123060 T crypto/internal/boring.(*PublicKeyECDH).Bytes
 243fba0 D crypto/internal/boring..inittask
 243eda0 D crypto/internal/boring/bbig..inittask
 1060bc0 T crypto/internal/boring/bcache.registerCache
 24efe14 B crypto/internal/boring/fipstls.required
 1123040 T crypto/internal/boring/sig.StandardCrypto.abi0
 1219c00 T crypto/x509.boringAllowCert
 24bfae0 B runtime.boringCaches
➜  go version bin/app                                                     
bin/app: go1.20.1 X:boringcrypto

which has 0 matches with _goboringcrypto_ but there's crypto/internal/boring instead.

When I opened crypto/internal/boring's docs I could see:

Package boring provides access to BoringCrypto implementation functions. Check the constant Enabled to find out whether BoringCrypto is available. If BoringCrypto is not available, the functions in this package all panic.

Based on that, I've got 2 quick questions:

  1. Is my output semantically equivalent even though there's no matches for __goboringcrypto_? In other words, does it confirm that my app uses boringcrypto instead of the native golang crypto?
  2. I was searching for other articles about FIPS compliance and some of them do mention using CGO_ENABLED=1 (however I'm using CGO_ENABLED=0). Is it still a requirement when using 1.20.1 version of golang?

Go 1.19 and higher: Starting with Go 1.19, you can simply add [BUILD_GOEXPERIMENT=boringcrypto][18] and some related arguments to enable integrating BoringCrypto for standard Go.

make container \
  BUILD_GOEXPERIMENT=boringcrypto \
  BUILD_CGO_ENABLED=1 \
  BUILD_EXTRA_GO_LDFLAGS="-linkmode=external -extldflags=-static"

That said, this article says

Since go 1.19 ... Pass GOEXPERIMENT=boringcrypto to the go tool during build time. As simple as that.

and there's no mention of CGO_ENABLED flag at all.

and I can only see the mention of cgo in the section for go 1.18 and earlier:

go 1.18 and earlier The build must have cgo enabled.

Updates:

  1. I found another article that implies having CGO_ENABLED=1 is necessary still even for golang version 1.19.

  2. The article referenced in the previous item, points to goversion:

Also, you can use the program rsc.io/goversion. It will report the crypto implementation used by a given binary when invoked with the -crypto flag.

that has this interesting PR that implies _Cfunc__goboringcrypto_ is equivalent to crypto/internal/boring/sig.BoringCrypto:

enter image description here

also see goversion/version/read.go file:

enter image description here enter image description here

That said, my output has crypto/internal/boring/sig.StandardCrypto and not crypto/internal/boring/sig.BoringCrypto.

My conclusions:

To sum up, it looks like if an app has any of

in its output for go tool nm command it means that an app uses boringcrypto and if there's

in its output it means that an app uses native golang crypto.


Solution

  • Overall,

    If an app has any of

    in its output for go tool nm command it means that an app uses boringcrypto and if there's

    in its output it means that an app uses native golang crypto.

    And to answer 2 questions specifically,

    Is my output semantically equivalent even though there's no matches for _goboringcrypto? In other words, does it confirm that my app uses boringcrypto instead of the native golang crypto?

    It isn't, since it contains crypto/internal/boring/sig.StandardCrypto instead of _Cfunc__goboringcrypto_ / crypto/internal/boring/sig.BoringCrypto.

    I was searching for other articles about FIPS compliance and some of them do mention using CGO_ENABLED=1 (however I'm using CGO_ENABLED=0). Is it still a requirement when using 1.20.1 version of golang?

    It is.

    Thanks everyone for the help!