From 171017817df53355b95d8bc1638f6785e9fc3efd Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Wed, 21 Mar 2018 17:50:00 -0700 Subject: [PATCH] Rework the FIPS mode detection The best test we currently have for whether Notary is running in a FIPS compliant environment is whether the MD5 hash function is registered when crypto/md5 is linked in to the program. This function is not available in FIPS mode as it is not an allowed hash function. Fix the tests to not use environment variables but private functions instead. This allows parallel testing and is cleaner. Signed-off-by: Justin Cormack --- fips.go | 17 +++++++------ tuf/utils/x509.go | 17 +++++++++---- tuf/utils/x509_test.go | 57 ++++++++++++------------------------------ 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/fips.go b/fips.go index 01ed2fb57..3f3bc68ef 100644 --- a/fips.go +++ b/fips.go @@ -1,13 +1,14 @@ package notary -import "os" +import ( + "crypto" + // Need to import md5 so can test availability. + _ "crypto/md5" +) -// FIPSEnvVar is the name of the environment variable that is being used to switch -// between FIPS and non-FIPS mode -const FIPSEnvVar = "GOFIPS" - -// FIPSEnabled returns true if environment variable `GOFIPS` has been set to enable -// FIPS mode +// FIPSEnabled returns true if running in FIPS mode. +// If compiled in FIPS mode the md5 hash function is never available +// even when imported. This seems to be the best test we have for it. func FIPSEnabled() bool { - return os.Getenv(FIPSEnvVar) != "" + return !crypto.MD5.Available() } diff --git a/tuf/utils/x509.go b/tuf/utils/x509.go index 7738418ac..4ebf83c2e 100644 --- a/tuf/utils/x509.go +++ b/tuf/utils/x509.go @@ -86,10 +86,6 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) { } func parseLegacyPrivateKey(block *pem.Block, passphrase string) (data.PrivateKey, error) { - if notary.FIPSEnabled() { - return nil, fmt.Errorf("%s not supported in FIPS mode", block.Type) - } - var privKeyBytes []byte var err error if x509.IsEncryptedPEMBlock(block) { @@ -146,6 +142,10 @@ func parseLegacyPrivateKey(block *pem.Block, passphrase string) (data.PrivateKey // supports PKCS#8 as well as RSA/ECDSA (PKCS#1) only in non-FIPS mode and // attempts to decrypt using the passphrase, if encrypted. func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) { + return parsePEMPrivateKey(pemBytes, passphrase, notary.FIPSEnabled()) +} + +func parsePEMPrivateKey(pemBytes []byte, passphrase string, fips bool) (data.PrivateKey, error) { block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("no valid private key found") @@ -153,6 +153,9 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er switch block.Type { case "RSA PRIVATE KEY", "EC PRIVATE KEY", "ED25519 PRIVATE KEY": + if fips { + return nil, fmt.Errorf("%s not supported in FIPS mode", block.Type) + } return parseLegacyPrivateKey(block, passphrase) case "ENCRYPTED PRIVATE KEY", "PRIVATE KEY": if passphrase == "" { @@ -433,6 +436,10 @@ func ED25519ToPrivateKey(privKeyBytes []byte) (data.PrivateKey, error) { // ExtractPrivateKeyAttributes extracts role and gun values from private key bytes func ExtractPrivateKeyAttributes(pemBytes []byte) (data.RoleName, data.GUN, error) { + return extractPrivateKeyAttributes(pemBytes, notary.FIPSEnabled()) +} + +func extractPrivateKeyAttributes(pemBytes []byte, fips bool) (data.RoleName, data.GUN, error) { block, _ := pem.Decode(pemBytes) if block == nil { return "", "", errors.New("PEM block is empty") @@ -440,7 +447,7 @@ func ExtractPrivateKeyAttributes(pemBytes []byte) (data.RoleName, data.GUN, erro switch block.Type { case "RSA PRIVATE KEY", "EC PRIVATE KEY", "ED25519 PRIVATE KEY": - if notary.FIPSEnabled() { + if fips { return "", "", fmt.Errorf("%s not supported in FIPS mode", block.Type) } case "PRIVATE KEY", "ENCRYPTED PRIVATE KEY": diff --git a/tuf/utils/x509_test.go b/tuf/utils/x509_test.go index c8c5351b2..07fe36600 100644 --- a/tuf/utils/x509_test.go +++ b/tuf/utils/x509_test.go @@ -9,13 +9,11 @@ import ( "encoding/pem" "fmt" "io/ioutil" - "os" "strings" "testing" "time" "github.com/stretchr/testify/require" - "github.com/theupdateframework/notary" "github.com/theupdateframework/notary/tuf/data" ) @@ -289,27 +287,13 @@ func TestECDSAX509PublickeyID(t *testing.T) { require.Equal(t, tufPrivKey.ID(), tufID) } -func preserveEnv(name string) func() { - if env, has := os.LookupEnv(name); has { - os.Unsetenv(name) - return func() { - os.Setenv(name, env) - } - } - - return func() {} -} - func TestExtractPrivateKeyAttributes(t *testing.T) { testExtractPrivateKeyAttributes(t) testExtractPrivateKeyAttributesWithFIPS(t) } func testExtractPrivateKeyAttributes(t *testing.T) { - defer preserveEnv(notary.FIPSEnvVar)() - - err := os.Unsetenv(notary.FIPSEnvVar) - require.NoError(t, err) + fips := false testPKCS1PEM1 := getPKCS1KeyWithRole(t, "unicorn", "rainbow") testPKCS1PEM2 := getPKCS1KeyWithRole(t, "docker", "") @@ -317,61 +301,58 @@ func testExtractPrivateKeyAttributes(t *testing.T) { testPKCS8PEM2 := getPKCS8KeyWithRole(t, "dagger", "") // Try garbage bytes - _, _, err = ExtractPrivateKeyAttributes([]byte("Knock knock; it's Bob.")) + _, _, err := extractPrivateKeyAttributes([]byte("Knock knock; it's Bob."), fips) require.Error(t, err) // PKCS#8 - role, gun, err := ExtractPrivateKeyAttributes(testPKCS8PEM1) + role, gun, err := extractPrivateKeyAttributes(testPKCS8PEM1, fips) require.NoError(t, err) require.EqualValues(t, data.RoleName("fat"), role) require.EqualValues(t, data.GUN("panda"), gun) - role, gun, err = ExtractPrivateKeyAttributes(testPKCS8PEM2) + role, gun, err = extractPrivateKeyAttributes(testPKCS8PEM2, fips) require.NoError(t, err) require.EqualValues(t, data.RoleName("dagger"), role) require.EqualValues(t, data.GUN(""), gun) // PKCS#1 - role, gun, err = ExtractPrivateKeyAttributes(testPKCS1PEM1) + role, gun, err = extractPrivateKeyAttributes(testPKCS1PEM1, fips) require.NoError(t, err) require.EqualValues(t, data.RoleName("unicorn"), role) require.EqualValues(t, data.GUN("rainbow"), gun) - role, gun, err = ExtractPrivateKeyAttributes(testPKCS1PEM2) + role, gun, err = extractPrivateKeyAttributes(testPKCS1PEM2, fips) require.NoError(t, err) require.EqualValues(t, data.RoleName("docker"), role) require.EqualValues(t, data.GUN(""), gun) } func testExtractPrivateKeyAttributesWithFIPS(t *testing.T) { - defer preserveEnv(notary.FIPSEnvVar)() - - err := os.Setenv(notary.FIPSEnvVar, "1") - require.NoError(t, err) + fips := true testPKCS1PEM1 := getPKCS1KeyWithRole(t, "unicorn", "rainbow") testPKCS1PEM2 := getPKCS1KeyWithRole(t, "docker", "") // PKCS#1 - _, _, err = ExtractPrivateKeyAttributes(testPKCS1PEM1) + _, _, err := extractPrivateKeyAttributes(testPKCS1PEM1, fips) require.Error(t, err) - _, _, err = ExtractPrivateKeyAttributes(testPKCS1PEM2) + _, _, err = extractPrivateKeyAttributes(testPKCS1PEM2, fips) require.Error(t, err) testPKCS8PEM1 := getPKCS8KeyWithRole(t, "fat", "panda") testPKCS8PEM2 := getPKCS8KeyWithRole(t, "dagger", "") // Try garbage bytes - _, _, err = ExtractPrivateKeyAttributes([]byte("Knock knock; it's Bob.")) + _, _, err = extractPrivateKeyAttributes([]byte("Knock knock; it's Bob."), fips) require.Error(t, err) // PKCS#8 - role, gun, err := ExtractPrivateKeyAttributes(testPKCS8PEM1) + role, gun, err := extractPrivateKeyAttributes(testPKCS8PEM1, fips) require.NoError(t, err) require.EqualValues(t, data.RoleName("fat"), role) require.EqualValues(t, data.GUN("panda"), gun) - role, gun, err = ExtractPrivateKeyAttributes(testPKCS8PEM2) + role, gun, err = extractPrivateKeyAttributes(testPKCS8PEM2, fips) require.NoError(t, err) require.EqualValues(t, data.RoleName("dagger"), role) require.EqualValues(t, data.GUN(""), gun) @@ -433,25 +414,19 @@ PBV11bfmoHzDVeeuz1ztFUb3WjR7xlQe09izY3o3N6yZlTFIsqawIg== } func testParsePEMPrivateKeyLegacy(t *testing.T, raw []byte) { - defer preserveEnv(notary.FIPSEnvVar)() - - err := os.Unsetenv(notary.FIPSEnvVar) - require.NoError(t, err) + fips := false - key, err := ParsePEMPrivateKey(raw, "") + key, err := parsePEMPrivateKey(raw, "", fips) require.NoError(t, err) require.NotNil(t, key.Public()) require.NotNil(t, key.Private()) } func testParsePEMPrivateKeyLegacyWithFIPS(t *testing.T, raw []byte) { - defer preserveEnv(notary.FIPSEnvVar)() - - err := os.Setenv(notary.FIPSEnvVar, "1") - require.NoError(t, err) + fips := true // No legacy key must be accepted in FIPS mode - _, err = ParsePEMPrivateKey(raw, "") + _, err := parsePEMPrivateKey(raw, "", fips) require.Error(t, err) }