Skip to content

Commit

Permalink
[Go 1.18] Restrict key generation and add test script (#109)
Browse files Browse the repository at this point in the history
* Add strict fips mode

* fixup whitespace

* Add crypto test script

This commit adds crypto-test.sh, which runs the Go crypto and tls tests
in both default fips and strict fips modes.
  • Loading branch information
dbenoit17 authored Aug 11, 2023
1 parent 025cf52 commit 7e08b6e
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 8 deletions.
2 changes: 2 additions & 0 deletions api/go1.16.txt
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,5 @@ pkg unicode, var Chorasmian *RangeTable
pkg unicode, var Dives_Akuru *RangeTable
pkg unicode, var Khitan_Small_Script *RangeTable
pkg unicode, var Yezidi *RangeTable
pkg crypto/rsa, func GenerateKeyNotBoring(io.Reader, int) (*PrivateKey, error)
pkg crypto/rsa, func GenerateMultiPrimeKeyNotBoring(io.Reader, int, int) (*PrivateKey, error)
100 changes: 100 additions & 0 deletions scripts/crypto-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/bin/bash

set -eE

quiet () {
2>&1>/dev/null $@
}

# Find the GOROOT.
# If using a release branch, expect the GOROOT
# in the go submodule directory.
GOROOT=$(readlink -f $(dirname $0)/..)
quiet pushd $GOROOT
if 2>/dev/null cat .gitmodules | grep -q "url = https://github.com/golang/go.git"; then
GOROOT=${GOROOT}/go
fi
quiet popd

export GOCACHE=/tmp/go-cache
export GO=${GOROOT}/bin/go

# Test suites to run
SUITES="crypto,tls"
# Verbosity flags to pass to Go
VERBOSE=""

# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--suites)
SUITES=$2
shift;shift
;;
-v)
VERBOSE="$VERBOSE -v"
set -x
shift
;;
*)
>&2 echo "unsupported option $1"
exit 1
;;
esac
done

notify_running() {
local mode=$1
local suite=$2
echo -e "\n##### ${suite} (${mode})"
}

run_crypto_test_suite () {
local mode=$1
local tags=$2
local suite="crypto-fips"
notify_running ${mode} ${suite}
quiet pushd ${GOROOT}/src/crypto
GOLANG_FIPS=1 OPENSSL_FORCE_FIPS_MODE=1 \
$GO test $tags -count=1 $($GO list ./... | grep -v tls) $VERBOSE

local suite="crypto-fips-parity-nocgo"
notify_running ${mode} ${suite}
GOLANG_FIPS=1 OPENSSL_FORCE_FIPS_MODE=1 \
CGO_ENABLED=0 $GO test $tags -count=1 $($GO list ./... | grep -v tls) $VERBOSE
quiet popd
}

run_tls_test_suite () {
local mode=$1
local tags=$2
local suite="tls-fips"
notify_running ${mode} ${suite}
quiet pushd ${GOROOT}/src
GOLANG_FIPS=1 OPENSSL_FORCE_FIPS_MODE=1 \
$GO test $tags -count=1 crypto/tls -run "^TestBoring" $VERBOSE
quiet popd
}


run_full_test_suite () {
local mode=$1
local tags=$2
for suite in ${SUITES//,/ }; do
if [[ "$suite" == "crypto" ]]; then
run_crypto_test_suite ${mode} ${tags}
elif [[ "$suite" == "tls" ]]; then
run_tls_test_suite ${mode} ${tags}
fi
done
}

# Run in default mode
run_full_test_suite default ""

# Run in strict fips mode
export GOEXPERIMENT=strictfipsruntime
run_full_test_suite strictfips "-tags=strictfipsruntime"

echo ALL TESTS PASSED

8 changes: 8 additions & 0 deletions src/crypto/internal/boring/boring.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ const (
OPENSSL_VERSION_3_0_0 = uint64(C.ulong(0x30000000))
)

func init() {
strictFIPSOpenSSLRuntimeCheck()
}

// Enabled controls whether FIPS crypto is enabled.
var enabled = false

func IsStrictFIPSMode() bool {
return isStrictFIPS
}

// When this variable is true, the go crypto API will panic when a caller
// tries to use the API in a non-compliant manner. When this is false, the
// go crytpo API will allow existing go crypto APIs to be used even
Expand Down
21 changes: 21 additions & 0 deletions src/crypto/internal/boring/hostfips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package boring

import (
"fmt"
"os"
)

func hostFIPSModeEnabled() bool {
// Look at /proc/sys/crypto/fips_enabled to see if FIPS mode is enabled.
// If it is, log an error and exit.
// If we run into an error reading that file because it doesn't exist, assume FIPS mode is not enabled.
data, err := os.ReadFile("/proc/sys/crypto/fips_enabled")
if err != nil {
if os.IsNotExist(err) {
return false
}
fmt.Fprintf(os.Stderr, "error reading /proc/sys/crypto/fips_enabled: %v\n", err)
os.Exit(1)
}
return len(data) > 0 && data[0] == '1'
}
12 changes: 12 additions & 0 deletions src/crypto/internal/boring/not_strict_fips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !goexperiment.strictfipsruntime
// +build !goexperiment.strictfipsruntime

package boring

var isStrictFIPS bool = false

func strictFIPSOpenSSLRuntimeCheck() {
}

func strictFIPSNonCompliantBinaryCheck() {
}
8 changes: 8 additions & 0 deletions src/crypto/internal/boring/notboring.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@ import (
"math/big"
)

func init() {
strictFIPSNonCompliantBinaryCheck()
}

var enabled = false

func IsStrictFIPSMode() bool {
return false
}

// Unreachable marks code that should be unreachable
// when BoringCrypto is in use. It is a no-op without BoringCrypto.
func Unreachable() {
Expand Down
25 changes: 25 additions & 0 deletions src/crypto/internal/boring/strict_fips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build goexperiment.strictfipsruntime
// +build goexperiment.strictfipsruntime

package boring

import (
"fmt"
"os"
)

var isStrictFIPS bool = true

func strictFIPSOpenSSLRuntimeCheck() {
if hostFIPSModeEnabled() && !Enabled() {
fmt.Fprintln(os.Stderr, "FIPS mode is enabled, but the required OpenSSL backend is unavailable")
os.Exit(1)
}
}

func strictFIPSNonCompliantBinaryCheck() {
if hostFIPSModeEnabled() {
fmt.Fprintln(os.Stderr, "FIPS mode is enabled, but this binary is not compiled with FIPS compliant mode enabled")
os.Exit(1)
}
}
4 changes: 2 additions & 2 deletions src/crypto/rsa/boring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
)

func TestBoringASN1Marshal(t *testing.T) {
k, err := GenerateKey(rand.Reader, 128)
k, err := GenerateKey(rand.Reader, 3072)
if err != nil {
t.Fatal(err)
}
Expand All @@ -34,7 +34,7 @@ func TestBoringASN1Marshal(t *testing.T) {
}

func TestBoringDeepEqual(t *testing.T) {
k, err := GenerateKey(rand.Reader, 128)
k, err := GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/rsa/equal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func TestEqual(t *testing.T) {
private, _ := rsa.GenerateKey(rand.Reader, 512)
private, _ := rsa.GenerateKey(rand.Reader, 2048)
public := &private.PublicKey

if !public.Equal(public) {
Expand Down Expand Up @@ -41,7 +41,7 @@ func TestEqual(t *testing.T) {
t.Errorf("private key is not equal to itself after decoding: %v", private)
}

other, _ := rsa.GenerateKey(rand.Reader, 512)
other, _ := rsa.GenerateKey(rand.Reader, 2048)
if public.Equal(other.Public()) {
t.Errorf("different public keys are Equal")
}
Expand Down
18 changes: 18 additions & 0 deletions src/crypto/rsa/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,24 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
// [1] US patent 4405829 (1972, expired)
// [2] http://www.cacr.math.uwaterloo.ca/techreports/2006/cacr2006-16.pdf
func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey, error) {
if boring.Enabled() && boring.IsStrictFIPSMode() && !(random == boring.RandReader && nprimes == 2 &&
(bits == 2048 || bits == 3072 || bits == 4096)) {
return nil, errors.New("crypto/rsa: invalid primes or bits for boring")
}
return generateMultiPrimeKeyInternal(random, nprimes, bits)
}

func GenerateKeyNotBoring(random io.Reader, bits int) (*PrivateKey, error) {
boring.UnreachableExceptTests()
return generateMultiPrimeKeyInternal(random, 2, bits)
}

func GenerateMultiPrimeKeyNotBoring(random io.Reader, nprimes int, bits int) (*PrivateKey, error) {
boring.UnreachableExceptTests()
return generateMultiPrimeKeyInternal(random, nprimes, bits)
}

func generateMultiPrimeKeyInternal(random io.Reader, nprimes int, bits int) (*PrivateKey, error) {
randutil.MaybeReadByte(random)

if boring.Enabled() && nprimes == 2 && (bits == 2048 || bits == 3072) {
Expand Down
30 changes: 28 additions & 2 deletions src/crypto/rsa/rsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@ import (
import "crypto/internal/boring"

func TestKeyGeneration(t *testing.T) {
for _, size := range []int{128, 1024, 2048, 3072} {
testKeys := []int{128, 1024}
if boring.Enabled() {
for _, size := range testKeys {
_, err := GenerateKey(rand.Reader, size)
if err == nil && boring.IsStrictFIPSMode() {
t.Errorf("Gener(%d): boring: bad accept", size)
}
}
testKeys = []int{2048, 3072}
}
for _, size := range testKeys {
priv, err := GenerateKey(rand.Reader, size)
if err != nil {
t.Errorf("GenerateKey(%d): %v", size, err)
Expand All @@ -35,14 +45,20 @@ func TestKeyGeneration(t *testing.T) {
}

func Test3PrimeKeyGeneration(t *testing.T) {
if boring.IsStrictFIPSMode() {
_, err := GenerateMultiPrimeKey(rand.Reader, 3, 768)
if err == nil {
t.Errorf("bad accept in strictfipsmode")
}
return
}
var sizes []int
if testing.Short() {
sizes = []int{256}
} else {
sizes = []int{128, 768, 1024, 2048, 3072}
}
for _, size := range sizes {

priv, err := GenerateMultiPrimeKey(rand.Reader, 3, size)
if err != nil {
t.Errorf("failed to generate key")
Expand All @@ -52,6 +68,13 @@ func Test3PrimeKeyGeneration(t *testing.T) {
}

func Test4PrimeKeyGeneration(t *testing.T) {
if boring.IsStrictFIPSMode() {
_, err := GenerateMultiPrimeKey(rand.Reader, 3, 768)
if err == nil {
t.Errorf("bad accept in strictfipsmode")
}
return
}
var sizes []int
if testing.Short() {
sizes = []int{256}
Expand All @@ -69,6 +92,9 @@ func Test4PrimeKeyGeneration(t *testing.T) {
}

func TestNPrimeKeyGeneration(t *testing.T) {
if boring.IsStrictFIPSMode() {
t.Skip("not supported in strict fips mode")
}
primeSize := 64
maxN := 32
if testing.Short() {
Expand Down
9 changes: 9 additions & 0 deletions src/crypto/tls/boring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,15 @@ const (
boringCertFIPSOK = 0x80
)

func NotBoringRSAKey(t *testing.T, size int) *rsa.PrivateKey {
k, err := rsa.GenerateKeyNotBoring(rand.Reader, size)
if err != nil {
t.Fatal(err)
}
return k
}


func boringRSAKey(t *testing.T, size int) *rsa.PrivateKey {
k, err := rsa.GenerateKey(rand.Reader, size)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/x509/x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2764,7 +2764,7 @@ func TestUnknownExtKey(t *testing.T) {
DNSNames: []string{"foo"},
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsage(-1)},
}
signer, err := rsa.GenerateKey(rand.Reader, 1024)
signer, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Errorf("failed to generate key for TestUnknownExtKey")
}
Expand Down Expand Up @@ -2927,7 +2927,7 @@ func TestCreateCertificateBrokenSigner(t *testing.T) {
SerialNumber: big.NewInt(10),
DNSNames: []string{"example.com"},
}
k, err := rsa.GenerateKey(rand.Reader, 1024)
k, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("failed to generate test key: %s", err)
}
Expand Down
9 changes: 9 additions & 0 deletions src/internal/goexperiment/exp_strictfipsruntime_off.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/internal/goexperiment/exp_strictfipsruntime_on.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/internal/goexperiment/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,6 @@ type Flags struct {
// has been broken out to its own experiment that is disabled
// by default.
HeapMinimum512KiB bool

StrictFIPSRuntime bool
}

0 comments on commit 7e08b6e

Please sign in to comment.