Abstract
The FIPS140 code, especially its RNG, sits unused and bloaty on the overwhelming majority of Go binaries. All but the most specific users actually jump through the hoops of turning on this mostly useless (from a technical perspective) code. So it just sits there, wasting memory (the init functions still trigger) and wasting disk space and filling caches and just being a very unwelcome weight to a formerly clean set of crypto libraries. This proposal is to add a new GOFIPS140=disabled default value to not compile in unused FIPS code.
Motivation
The new fips140 code adds lots of indirection and gravely bloats the code size of resultant binaries, even when disabled or not in use. Its use is gated by default-off GODEBUG flag. Yet it is always compiled into Go binaries.
In addition to indirecting large amounts of crypto through its framework (to then bail out early when it's not been enabled with a GODEBUG flag), it also imports large amounts of additional crypto and complexity.
As far as I can tell, in addition to causing lots of indirection through its framework, it adds branchy code paths to:
- TLS
- RSA/PKCS1
- The random number generator wrapper functions
- AES/GCM
I think adding the additional unused code is a shame in all of these cases, and all of these ought to instead be gated on a compile time constant (the Enabled symbol could go from var to const). But it's the RNG situation that I find especially egregious, so that's what I'll focus on here to illustrate what I mean.
I made two test programs:
tester1:
package main func main() { println("hello") }
tester2:
package main import "crypto/rand" func main() { var b [1]byte rand.Read(b[:]) println("hello", b[0]) }
Let's see which crypto packages tester2 adds. We'd expect that it'd pull in crypto/rand, of course, plus some internal crypto things for dispatching this to the various syscalls or whatever else involved in getting the good random bytes. That's not exactly what happens:
$ comm -13 <(go tool objdump tester1 | grep ^TEXT\ crypto | sort) <(go tool objdump tester2 | grep ^TEXT\ crypto | sort)
TEXT crypto/fips140.init(SB) <autogenerated>
TEXT crypto.init(SB) <autogenerated>
TEXT crypto/internal/entropy/v1%2e0%2e0.digestBytes(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/sha384.go
TEXT crypto/internal/entropy/v1%2e0%2e0.newSource(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/entropy.go
TEXT crypto/internal/entropy/v1%2e0%2e0.Samples(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/entropy.go
TEXT crypto/internal/entropy/v1%2e0%2e0.Seed(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/entropy.go
TEXT crypto/internal/entropy/v1%2e0%2e0.sha384Block(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/sha384.go
TEXT crypto/internal/entropy/v1%2e0%2e0.SHA384(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/sha384.go
TEXT crypto/internal/entropy/v1%2e0%2e0.(*source).Sample(SB) /usr/lib/go/src/crypto/internal/entropy/v1.0.0/entropy.go
TEXT crypto/internal/fips140/aes.(*Block).BlockSize(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes.go
TEXT crypto/internal/fips140/aes.(*CBCDecrypter).CryptBlocks(SB) /usr/lib/go/src/crypto/internal/fips140/aes/cbc.go
TEXT crypto/internal/fips140/aes.(*CBCEncrypter).CryptBlocks(SB) /usr/lib/go/src/crypto/internal/fips140/aes/cbc.go
TEXT crypto/internal/fips140/aes.cryptBlocksDecGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/aes/cbc.go
TEXT crypto/internal/fips140/aes.cryptBlocksEncGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/aes/cbc.go
TEXT crypto/internal/fips140/aes.ctrBlocks1Asm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_amd64.s
TEXT crypto/internal/fips140/aes.ctrBlocks1(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_asm.go
TEXT crypto/internal/fips140/aes.ctrBlocks2Asm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_amd64.s
TEXT crypto/internal/fips140/aes.ctrBlocks2(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_asm.go
TEXT crypto/internal/fips140/aes.ctrBlocks4Asm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_amd64.s
TEXT crypto/internal/fips140/aes.ctrBlocks4(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_asm.go
TEXT crypto/internal/fips140/aes.ctrBlocks8Asm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_amd64.s
TEXT crypto/internal/fips140/aes.ctrBlocks8(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr_asm.go
TEXT crypto/internal/fips140/aes.ctrBlocks(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr.go
TEXT crypto/internal/fips140/aes.(*CTR).XORKeyStreamAt(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr.go
TEXT crypto/internal/fips140/aes.(*CTR).XORKeyStream(SB) /usr/lib/go/src/crypto/internal/fips140/aes/ctr.go
TEXT crypto/internal/fips140/aes.decryptBlockAsm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_amd64.s
TEXT crypto/internal/fips140/aes.decryptBlockGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_generic.go
TEXT crypto/internal/fips140/aes.decryptBlock(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_asm.go
TEXT crypto/internal/fips140/aes.encryptBlockAsm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_amd64.s
TEXT crypto/internal/fips140/aes.encryptBlockGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_generic.go
TEXT crypto/internal/fips140/aes.encryptBlock(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_asm.go
TEXT crypto/internal/fips140/aes.expandKeyAsm.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_amd64.s
TEXT crypto/internal/fips140/aes.expandKeyGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_generic.go
TEXT crypto/internal/fips140/aes/gcm.(*CMAC).deriveSubkeys(SB) /usr/lib/go/src/crypto/internal/fips140/aes/gcm/cmac.go
TEXT crypto/internal/fips140/aes/gcm.(*CMAC).MAC(SB) /usr/lib/go/src/crypto/internal/fips140/aes/gcm/cmac.go
TEXT crypto/internal/fips140/aes/gcm.(*CounterKDF).DeriveKey(SB) /usr/lib/go/src/crypto/internal/fips140/aes/gcm/ctrkdf.go
TEXT crypto/internal/fips140/aes/gcm.init.0.func1(SB) /usr/lib/go/src/crypto/internal/fips140/aes/gcm/cast.go
TEXT crypto/internal/fips140/aes/gcm.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/gcm/cast.go
TEXT crypto/internal/fips140/aes/gcm.init.1(SB) /usr/lib/go/src/crypto/internal/fips140/aes/gcm/gcm_asm.go
TEXT crypto/internal/fips140/aes/gcm.init(SB) <autogenerated>
TEXT crypto/internal/fips140/aes.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_asm.go
TEXT crypto/internal/fips140/aes.init.1.func1(SB) /usr/lib/go/src/crypto/internal/fips140/aes/cast.go
TEXT crypto/internal/fips140/aes.init.1(SB) /usr/lib/go/src/crypto/internal/fips140/aes/cast.go
TEXT crypto/internal/fips140/aes.init(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_asm.go
TEXT crypto/internal/fips140/aes.(*KeySizeError).Error(SB) <autogenerated>
TEXT crypto/internal/fips140/aes.KeySizeError.Error(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes.go
TEXT crypto/internal/fips140/aes.newBlock(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes_asm.go
TEXT crypto/internal/fips140/aes.newOutlined(SB) /usr/lib/go/src/crypto/internal/fips140/aes/aes.go
TEXT crypto/internal/fips140.CAST(SB) /usr/lib/go/src/crypto/internal/fips140/cast.go
TEXT crypto/internal/fips140/check.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/check/check.go
TEXT crypto/internal/fips140deps/cpu.init(SB) /usr/lib/go/src/crypto/internal/fips140deps/cpu/cpu.go
TEXT crypto/internal/fips140deps/time.monoTime(SB) /usr/lib/go/src/runtime/time.go
TEXT crypto/internal/fips140/drbg.(*Counter).Generate(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/ctrdrbg.go
TEXT crypto/internal/fips140/drbg.(*Counter).Reseed(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/ctrdrbg.go
TEXT crypto/internal/fips140/drbg.(*Counter).update(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/ctrdrbg.go
TEXT crypto/internal/fips140/drbg.getEntropy(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/entropy_fips140.go
TEXT crypto/internal/fips140/drbg.init.0.func1(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/cast.go
TEXT crypto/internal/fips140/drbg.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/cast.go
TEXT crypto/internal/fips140/drbg.init.func1(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/rand.go
TEXT crypto/internal/fips140/drbg.init(SB) <autogenerated>
TEXT crypto/internal/fips140/drbg.NewCounter(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/ctrdrbg.go
TEXT crypto/internal/fips140/drbg.Read.func1(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/rand.go
TEXT crypto/internal/fips140/drbg.Read(SB) /usr/lib/go/src/crypto/internal/fips140/drbg/rand.go
TEXT crypto/internal/fips140.fatal(SB) /usr/lib/go/src/runtime/panic.go
TEXT crypto/internal/fips140.getIndicator(SB) /usr/lib/go/src/runtime/runtime1.go
TEXT crypto/internal/fips140/hmac.(*HMAC).BlockSize(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140/hmac.(*HMAC).Reset(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140/hmac.(*HMAC).Sum(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140/hmac.(*HMAC).Write(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140/hmac.init.0.func1(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/cast.go
TEXT crypto/internal/fips140/hmac.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/cast.go
TEXT crypto/internal/fips140/hmac.New[go.shape.*uint8].func1.1(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140/hmac.New[go.shape.*uint8].func1(SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140/hmac.New[go.shape.*uint8](SB) /usr/lib/go/src/crypto/internal/fips140/hmac/hmac.go
TEXT crypto/internal/fips140.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/fips140.go
TEXT crypto/internal/fips140.init(SB) <autogenerated>
TEXT crypto/internal/fips140.RecordApproved(SB) /usr/lib/go/src/crypto/internal/fips140/indicator.go
TEXT crypto/internal/fips140.setIndicator(SB) /usr/lib/go/src/runtime/runtime1.go
TEXT crypto/internal/fips140/sha256.blockAVX2.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256block_amd64.s
TEXT crypto/internal/fips140/sha256.blockGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256block.go
TEXT crypto/internal/fips140/sha256.block(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256block_amd64.go
TEXT crypto/internal/fips140/sha256.blockSHANI.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256block_amd64.s
TEXT crypto/internal/fips140/sha256.(*Digest).AppendBinary(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).BlockSize(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).checkSum(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).MarshalBinary(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).Reset(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).Sum(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).UnmarshalBinary(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.(*Digest).Write(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha256.init.0.func1(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/cast.go
TEXT crypto/internal/fips140/sha256.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/cast.go
TEXT crypto/internal/fips140/sha256.init.1(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256block_amd64.go
TEXT crypto/internal/fips140/sha256.init(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256block_amd64.go
TEXT crypto/internal/fips140/sha256.New(SB) /usr/lib/go/src/crypto/internal/fips140/sha256/sha256.go
TEXT crypto/internal/fips140/sha3.bytepadWrite(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/shake.go
TEXT crypto/internal/fips140/sha3.(*Digest).padAndPermute(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3.go
TEXT crypto/internal/fips140/sha3.(*Digest).readGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3.go
TEXT crypto/internal/fips140/sha3.(*Digest).sumGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3.go
TEXT crypto/internal/fips140/sha3.(*Digest).Sum(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3.go
TEXT crypto/internal/fips140/sha3.(*Digest).writeGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3.go
TEXT crypto/internal/fips140/sha3.(*Digest).Write(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3.go
TEXT crypto/internal/fips140/sha3.init.0.func1(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/cast.go
TEXT crypto/internal/fips140/sha3.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/cast.go
TEXT crypto/internal/fips140/sha3.keccakF1600.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/sha3_amd64.s
TEXT crypto/internal/fips140/sha3.newCShake128(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/shake.go
TEXT crypto/internal/fips140/sha3.newCShake(SB) /usr/lib/go/src/crypto/internal/fips140/sha3/shake.go
TEXT crypto/internal/fips140/sha512.blockAVX2.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512block_amd64.s
TEXT crypto/internal/fips140/sha512.blockGeneric(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512block.go
TEXT crypto/internal/fips140/sha512.block(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512block_amd64.go
TEXT crypto/internal/fips140/sha512.(*Digest).checkSum(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512.go
TEXT crypto/internal/fips140/sha512.(*Digest).Reset(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512.go
TEXT crypto/internal/fips140/sha512.(*Digest).Sum(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512.go
TEXT crypto/internal/fips140/sha512.(*Digest).Write(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512.go
TEXT crypto/internal/fips140/sha512.init.0.func1(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/cast.go
TEXT crypto/internal/fips140/sha512.init.0(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/cast.go
TEXT crypto/internal/fips140/sha512.init.1(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512block_amd64.go
TEXT crypto/internal/fips140/sha512.init(SB) /usr/lib/go/src/crypto/internal/fips140/sha512/sha512block_amd64.go
TEXT crypto/internal/fips140/subtle.xorBytes.abi0(SB) /usr/lib/go/src/crypto/internal/fips140/subtle/xor_amd64.s
TEXT crypto/internal/fips140/subtle.XORBytes(SB) /usr/lib/go/src/crypto/internal/fips140/subtle/xor.go
TEXT crypto/internal/impl.Register(SB) /usr/lib/go/src/crypto/internal/impl/impl.go
TEXT crypto/internal/rand.(*reader).Read(SB) <autogenerated>
TEXT crypto/internal/rand.reader.Read(SB) /usr/lib/go/src/crypto/internal/rand/rand.go
TEXT crypto/internal/sysrand.fatal(SB) /usr/lib/go/src/runtime/panic.go
TEXT crypto/internal/sysrand.Read.deferwrap1(SB) /usr/lib/go/src/crypto/internal/sysrand/rand.go
TEXT crypto/internal/sysrand.read(SB) /usr/lib/go/src/crypto/internal/sysrand/rand_getrandom.go
TEXT crypto/internal/sysrand.Read(SB) /usr/lib/go/src/crypto/internal/sysrand/rand.go
TEXT crypto/internal/sysrand.urandomRead.func1(SB) /usr/lib/go/src/crypto/internal/sysrand/rand.go
TEXT crypto/internal/sysrand.urandomRead(SB) /usr/lib/go/src/crypto/internal/sysrand/rand.go
TEXT crypto/internal/sysrand.warnBlocked(SB) /usr/lib/go/src/crypto/internal/sysrand/rand.go
TEXT crypto/rand.fatal(SB) /usr/lib/go/src/runtime/panic.go
TEXT crypto/rand.init(SB) <autogenerated>
TEXT crypto/rand.Read(SB) /usr/lib/go/src/crypto/rand/rand.go
We get:
- A bizarre voodoo magic userspace entropy collector
- A specialized SHA384 implementation for said entropy collector
- An AES implementation
- An AES-NI accelerated AES implementation
- A CTR implementation for AES
- A GCM implementation for AES
- Some CPU detection code
- An implementation of some FIPS-specified DRBG algorithm
- An HMAC implementation
- A SHA256 implementation
- An AVX2-accelerated SHA256 implementation
- A SHA3 implementation (including cSHAKE)
- An AVX2-accelerated SHA3 implementation
- A SHA512 implementation
- Some generic XOR-bytes routines
- The actual syscall wrappers for this (crypto/internal/sysrand and crypto/rand)
This all seems a bit much to me. I mean, all of the FIPS RNG stuff seems a bit much to me, but I get that some people are forced to use it, so whatever. But that's the sort of thing that should be cordoned off into its own corner, and not compiled in by default to every Go binary that uses crypto/rand, and especially not compiled in if it's not being used. I mean, that's a lot of extra code.
Considerations
As mentioned above, this is all gated on the GODEBUG-controlled member fips140.Enabled. I would suggest simply changing fips140.Enabled from being a var into being a const, so that the compiler can then do its usual normal thing of stripping out the unused code. Then, certain complex chains of dependencies can be fixed by using the fips140 build tag on certain files. That would still mean that all calls get dispatched through fips140 trampolines, but at least all of the actual algorithms and excess unused code are stripped out. And the trampolines can mostly be inlined out too.
I spoke quasi-privately with @FiloSottile about this, and he expressed concern about doing this with regards to testing and CI. So part of this will involve reworking the tests so that they re-compile parts as needed. The current CL stack does this fairly cleanly.
So, specifically what this proposal does is proposes a new default GOFIPS140=disabled, which means that the binaries don't compile in any of the fips-only paths (the RNG, and the various extra branches and "silly" things). The various values are described in this comment below, but I'll here in the proposal lay out a before and after table for the GOFIPS140 variable:
| Value | Before | After |
|---|---|---|
| Unset | Defaults to off |
Defaults to disabled |
disabled |
Invalid | FIPS mode unavailable; fips140.Enabled is const false |
off |
FIPS support included, disabled at startup | Same |
latest |
Current module, enabled by default | Same |
certified |
Certified snapshot, enabled by default | Same |
inprocess |
Snapshot undergoing certification, enabled by default | Same |
| Supported version | Specified snapshot, enabled by default | Same |
Implementation
There's a stack of CLs available at https://go-review.googlesource.com/c/go/+/836445 and beyond that implement this, and get the testing all automated to reduce future maintenance burden.
CC @FiloSottile @bradfitz @cherrymui @ianlancetaylor @rsc @aclements @rolandshoemaker