Skip to content

Commit

Permalink
Rework the FIPS mode detection
Browse files Browse the repository at this point in the history
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 <justin.cormack@docker.com>
  • Loading branch information
justincormack committed Mar 26, 2018
1 parent a079b57 commit 9c40686
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 54 deletions.
16 changes: 8 additions & 8 deletions fips.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package notary

import "os"
import (
"crypto"
_ "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()
}
17 changes: 12 additions & 5 deletions tuf/utils/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -146,13 +142,20 @@ 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")
}

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 == "" {
Expand Down Expand Up @@ -433,14 +436,18 @@ 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")
}

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":
Expand Down
57 changes: 16 additions & 41 deletions tuf/utils/x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -289,89 +287,72 @@ 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", "")
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)

// 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)
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit 9c40686

Please sign in to comment.