Updated: 2022-09-29. To reflect that BoringCrypto is now part of the main branch.
Updated: 2022-11-20. To add a section on TLS 1.3 and FIPS.
Updated: 2023-01-07. To add a section on automatic configuration at compile time: Force FIPS-compliant crypto at compile time.
If you work with US government entities or corporations in regulated markets the subject of FIPS compliance may come up. FIPS 140-2 is a set of cryptographic standards that your application may need to adhere to.
FIPS in go #
It takes a lot to be FIPS verified or FIPS compliant (learn more), but for us the bottom line is that our app must use FIPS verified crypto libraries. This is a challenge with go, where the native crypto is not FIPS friendly.
There are no plans to change it either:
Go’s crypto is not FIPS 140 validated and I’m afraid that there is no possibility of that happening in the future either. I think Ian’s suggestion of using cgo to call out to an existing, certified library is probably your best bet. However, we would not be interested in patches to add hook points all over the Go library, so you would need to carry that work yourself.
Reimplementing crypto with cgo call outs doesn’t seem like a fun activity. Are we out of luck? Rewrite in Java? Luckily there are two options for us:
- BoringSSL based crypto.
- RedHat go toolchain.
Let’s discuss them.
Boring gets interesting #
BoringSSL is a fork of OpenSSL that is designed to meet Google’s needs.
Critically, it has a FIPS 140-2 verified version.
dev.boringcrypto is a go repo branch. It is maintained alongside the mainline go (quote from its readme):
We have been working inside Google on a fork of Go that uses BoringCrypto (the core of BoringSSL) for various crypto primitives, in furtherance of some work related to FIPS 140-2. We have heard that some external users of Go would be interested in this code as well, so I intend to create a new branch dev.boringcrypto that will hold patches to make Go use BoringCrypto.
Unlike typical dev branches, we do not intend any eventual merge of this code into the master branch. Instead we intend to maintain in that branch the latest release plus BoringCrypto patches. In this sense it is a bit like dev.typealias holding go1.8+type alias patches.
This sounds good, but in an OSS fashion there are no guarantees:
To be clear, we are not making any statements or representations about the suitability of this code in relation to the FIPS 140-2 standard. Interested users will have to evaluate for themselves whether the code is useful for their own purposes.
Since go 1.19 BoringSSL based crypto is part of the main branch: golang/go#51940.
The dev.boringcrypto branch started out as a bit of an experiment, back in the Go 1.8 time frame. It is clearly here to stay as something that we maintain alongside the main distribution.
Maintaining a whole separate branch is cumbersome, requiring frequent conflict resolution during merges and being just generally painful.
It would be far less upkeep if we kept the boringcrypto code in the main branch behind a GOEXPERIMENT, same as we do for GOEXPERIMENT=fieldtrack. We should do that.
It means that the build process is different for go 1.19+.
Updated the Build process and Boring Example sections to highlight this.
Build process #
GOEXPERIMENT=boringcrypto to the go tool during build time. As simple as that.
Code changes #
No changes to the code are needed (maybe with an exception of filtering out unsupported ciphers, etc. but this is a configuration concern).
Performance impact #
The foreign function interface between golang and c is not free. The library performs worse than the build-in crypto. See golang/go#21525.
In general there is about a 200ns overhead to calling into BoringCrypto via cgo for a particular call. So for example aes.BenchmarkEncrypt (testing encryption of a single 16-byte block) went from 13ns to 209ns, or +1500%. That we can’t do much about except hope that bulk operations call into cgo once instead of once per 16 bytes.
Note that the benchmarks were done in 2017, so the things might’ve improved (but then vanilla tls/crypto improved as well). Also note the detailed benchmark results, not all ciphers performed as poorly as the highlight. Some where on the same level or faster. If you don’t care about every nanosecond of performance or your code is not on the critical path then this is good enough. Otherwise benchmark your usecase to see if the performance hit is acceptable.
Usage Example #
I’ve created a simple echo server in go to show what changes are needed to switch to boringcrypto.
It is as simple as passing the extra flag
GOEXPERIMENT=boringcrypto to the build:
# 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
See the full diff: https://github.com/igor-kupczynski/fips-echo-server/compare/main...boringcrypto.
Note on verification: Use
go tool nm fips-echo-server > tags.txt and
grep '_Cfunc__goboringcrypto_' tags.txt to verify if the symbol
_Cfunc__goboringcrypto_ is present in the binary. As per the comment above this verifies that the binary uses BoringCrypto.
There are some other changes in the branch, e.g. to add new option
-fipsMode, which resets the ciphersuite to a FIPS compliant one. You may want to do something else, like still allow user configured ciphers as long as they are a subset of FIPS compliant ones, etc. No “core” app changes required.
testssl reports this:
... Testing server's cipher preferences Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC) ----------------------------------------------------------------------------------------------------------------------------- SSLv2 - SSLv3 - TLSv1 - TLSv1.1 - TLSv1.2 (server order) xc02f ECDHE-RSA-AES128-GCM-SHA256 ECDH 521 AESGCM 128 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 xc030 ECDHE-RSA-AES256-GCM-SHA384 ECDH 521 AESGCM 256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 x9c AES128-GCM-SHA256 RSA AESGCM 128 TLS_RSA_WITH_AES_128_GCM_SHA256 x9d AES256-GCM-SHA384 RSA AESGCM 256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLSv1.3 - Has server cipher order? yes (OK) Negotiated protocol TLSv1.2 Negotiated cipher ECDHE-RSA-AES128-GCM-SHA256, 521 bit ECDH (P-521) ...
Force FIPS-compliant crypto at compile time #
Instead of manually setting FIPS-complaint ciphers and TLS versions you add a following file to your project:
//go:build boringcrypto // +build boringcrypto package main import _ "crypto/tls/fipsonly"
This forces TLSv1.2 and FIPS ciphers regardless of the runtime settings. If you don’t need FIPS/non-FIPS selection at runtime this is simpler and more convinient.
Boringcrypto, FIPS, and TLS 1.3 #
FIPS requires at least TLS 1.2, and encourages TLS 1.3. [src]. Starting from 2024, TLS 1.3 will be required.
NIST Special Publication (SP) 800-52, Rev. 2, provides guidance for the selection and configuration of TLS protocol implementations while making effective use of Federal Information Processing Standards (FIPS) and NIST-recommended cryptographic algorithms. It requires that TLS 1.2 configured with FIPS-based cipher suites be supported by all government TLS servers and clients. This Special Publication also provides guidance on certificates and TLS extensions that impact security. Support for TLS 1.3 is strongly recommended.
Agencies shall support TLS 1.3 by January 1, 2024. After this date, servers shall support TLS 1.3 for both government-only and citizen or business-facing applications.
The situation around FIPS 140 and TLS 1.3 is a little complex. However, Google is not in a position to give opinions on the interpretation of NIST rules and we recommend that you retain the services of an NVLAP lab for that.
I suspect this is because goboring internally uses boringcrypto. Boringcrypto TLS 1.3 cipher list is not configurable, and contains non-FIPS cipher
[Jul 27, 2022] @\visweshn92: Could you confirm the possibility of removing TLS_CHACHA20_POLY1305_SHA256 in the FIPS build? Or allowing a way to configure the TLS 1.3 ciphers?
[Jul 27, 2022] @\PiotrSikora: BoringSSL recently added
SSL_CTX_set_compliance_policy(ssl_compliance_policy_fips_202205), which does exactly that, but it won’t be available in FIPS builds until BoringCrypto
fips-20220613is validated, which might take up to a year.
go 1.18 and earlier #
Build Process #
Google maintains docker images with patched go toolchain, similar to
A Dockerfile that starts with
FROM golang:1.17.2can switch to
FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.17.2b7and should need no other modifications. (…)
BoringCrypto is used for a given build only in limited circumstances:
- The build must be GOOS=linux, GOARCH=amd64.
- The build must have cgo enabled.
- The android build tag must not be specified.
- The cmd_go_bootstrap build tag must not be specified.
- The version string reported by runtime.Version does not indicate that BoringCrypto was actually used for the build. For example, linux/386 and non-cgo linux/amd64 binaries will report a version of go1.8.3b2 but not be using BoringCrypto.
To check whether a given binary is using BoringCrypto, run
go tool nmon it and check that it has symbols named
Usage example #
The boringssl based toolchain is basically a drop in replacement. All we need to do is to use different go image toolchain:
--- a/Dockerfile (revision 3209b023ad9173f1deebcc5ff91b4c315510299e) +++ b/Dockerfile (date 1664401645292) @@ -1,15 +1,16 @@ -FROM golang:1.18.6 +FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.18.6b7
Upgrading from 1.18 to 1.19 #
To see the difference in boringcrypto usage between go 1.18 and go 1.19 check this diff: https://github.com/igor-kupczynski/fips-echo-server/compare/boringcrypto-1.18...boringcrypto
diff --git a/Dockerfile b/Dockerfile index ccb2648..5209f6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Start with an official image -FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:1.18.6b7 +FROM golang:1.19.1 # Bundle the project source in the container RUN mkdir -p /app @@ -7,7 +7,7 @@ ADD . /app WORKDIR /app # Build a binary and assert that it uses boringcrypto instead of the native golang crypto -RUN go build . && \ +RUN GOEXPERIMENT=boringcrypto go build . && \ go tool nm fips-echo-server > tags.txt && \ grep '_Cfunc__goboringcrypto_' tags.txt 1> /dev/null
The differences are:
- Switch to the standard image.
- Pass the
RedHat go toolchain #
Update: This section was written in 2019 and can be outdated at this point.
We are excited to announce that we plan to ship go-toolset with a new feature that allows Go to bypass the standard library cryptographic routines and instead call into a FIPS 140-2 validated cryptographic library.
I think they leverage some of the
dev.boringcrypto patches internally:
This new feature builds on top of pre-existing upstream work (which instead calls into BoringSSL) …
The major differences seem to be:
- RedHat offers support contracts and FIPS mode for RHEL, so they are committed to supporting their FIPS complaint go toolchain.
- FIPS mode can be enabled at runtime.
- Older go version — 1.11 at the time of writing, they promise to upgrade to 1.12 in 2019.
dev.boringcryptois being kept up-to-date against master (but there are no guarantees it will be like that in the future).
The native go crypto is not FIPS compliant, nor it will be in a foreseeable future. Luckily, both Google and RedHat provide go toolchains backed by FIPS validated SSL libraries. We can leverage them to make our go app FIPS compliant.>> Home