Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions ca/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ import (
"github.com/cloudflare/cfssl/signer/local"
"github.com/docker/go-events"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/ca/keyutils"
"github.com/docker/swarmkit/ca/pkcs8"
"github.com/docker/swarmkit/connectionbroker"
"github.com/docker/swarmkit/fips"
"github.com/docker/swarmkit/ioutils"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
Expand Down Expand Up @@ -636,7 +634,7 @@ func newLocalSigner(keyBytes, certBytes []byte, certExpiry time.Duration, rootPo
}

// The key should not be encrypted, but it could be in PKCS8 format rather than PKCS1
priv, err := keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, nil)
priv, err := helpers.ParsePrivateKeyPEM(keyBytes)
if err != nil {
return nil, errors.Wrap(err, "malformed private key")
}
Expand Down Expand Up @@ -782,14 +780,6 @@ func CreateRootCA(rootCN string) (RootCA, error) {
return RootCA{}, err
}

// Convert key to PKCS#8 in FIPS mode
if fips.Enabled() {
key, err = pkcs8.ConvertECPrivateKeyPEM(key)
if err != nil {
return RootCA{}, err
}
}

rootCA, err := NewRootCA(cert, cert, key, DefaultNodeCertExpiration, nil)
if err != nil {
return RootCA{}, err
Expand Down
26 changes: 0 additions & 26 deletions ca/certificates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"github.com/docker/swarmkit/ca"
cautils "github.com/docker/swarmkit/ca/testutils"
"github.com/docker/swarmkit/connectionbroker"
"github.com/docker/swarmkit/fips"
"github.com/docker/swarmkit/identity"
"github.com/docker/swarmkit/manager/state"
"github.com/docker/swarmkit/manager/state/store"
Expand Down Expand Up @@ -78,31 +77,6 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func TestCreateRootCAKeyFormat(t *testing.T) {
// Check if the CA key generated is PKCS#1 when FIPS-mode is off
rootCA, err := ca.CreateRootCA("rootCA")
require.NoError(t, err)

s, err := rootCA.Signer()
require.NoError(t, err)
block, _ := pem.Decode(s.Key)
require.NotNil(t, block)
require.Equal(t, "EC PRIVATE KEY", block.Type)

// Check if the CA key generated is PKCS#8 when FIPS-mode is on
os.Setenv(fips.EnvVar, "1")
defer os.Unsetenv(fips.EnvVar)

rootCA, err = ca.CreateRootCA("rootCA")
require.NoError(t, err)

s, err = rootCA.Signer()
require.NoError(t, err)
block, _ = pem.Decode(s.Key)
require.NotNil(t, block)
require.Equal(t, "PRIVATE KEY", block.Type)
}

func TestCreateRootCASaveRootCA(t *testing.T) {
tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-")
assert.NoError(t, err)
Expand Down
33 changes: 22 additions & 11 deletions ca/keyreadwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,30 @@ func (e ErrInvalidKEK) Error() string {
// KeyReadWriter is an object that knows how to read and write TLS keys and certs to disk,
// optionally encrypted and optionally updating PEM headers.
type KeyReadWriter struct {
mu sync.Mutex
kekData KEKData
paths CertPaths
headersObj PEMKeyHeaders
mu sync.Mutex
kekData KEKData
paths CertPaths
headersObj PEMKeyHeaders
keyFormatter keyutils.Formatter
}

// NewKeyReadWriter creates a new KeyReadWriter
func NewKeyReadWriter(paths CertPaths, kek []byte, headersObj PEMKeyHeaders) *KeyReadWriter {
return &KeyReadWriter{
kekData: KEKData{KEK: kek},
paths: paths,
headersObj: headersObj,
kekData: KEKData{KEK: kek},
paths: paths,
headersObj: headersObj,
keyFormatter: keyutils.Default,
}
}

// SetKeyFormatter sets the keyformatter with which to encrypt and decrypt keys
func (k *KeyReadWriter) SetKeyFormatter(kf keyutils.Formatter) {
k.mu.Lock()
defer k.mu.Unlock()
k.keyFormatter = kf
}

// Migrate checks to see if a temporary key file exists. Older versions of
// swarmkit wrote temporary keys instead of temporary certificates, so
// migrate that temporary key if it exists. We want to write temporary certificates,
Expand Down Expand Up @@ -324,8 +333,10 @@ func (k *KeyReadWriter) readKey() (*pem.Block, error) {
return nil, ErrInvalidKEK{Wrapped: x509.IncorrectPasswordError}
}

derBytes, err := keyutils.DecryptPEMBlock(keyBlock, k.kekData.KEK)
if err != nil {
derBytes, err := k.keyFormatter.DecryptPEMBlock(keyBlock, k.kekData.KEK)
if err == keyutils.ErrFIPSUnsupportedKeyFormat {
return nil, err
} else if err != nil {
return nil, ErrInvalidKEK{Wrapped: err}
}

Expand All @@ -349,7 +360,7 @@ func (k *KeyReadWriter) readKey() (*pem.Block, error) {
// writing it to disk. If the kek is nil, writes it to disk unencrypted.
func (k *KeyReadWriter) writeKey(keyBlock *pem.Block, kekData KEKData, pkh PEMKeyHeaders) error {
if kekData.KEK != nil {
encryptedPEMBlock, err := keyutils.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK)
encryptedPEMBlock, err := k.keyFormatter.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK)
if err != nil {
return err
}
Expand Down Expand Up @@ -404,7 +415,7 @@ func (k *KeyReadWriter) DowngradeKey() error {
}

if k.kekData.KEK != nil {
newBlock, err = keyutils.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK)
newBlock, err = k.keyFormatter.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK)
if err != nil {
return err
}
Expand Down
46 changes: 45 additions & 1 deletion ca/keyreadwriter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ func testKeyReadWriterDowngradeKeyCase(t *testing.T, tc downgradeTestCase) error
require.NotNil(t, block)

kek = []byte("kek")
block, err = keyutils.EncryptPEMBlock(block.Bytes, kek)
block, err = keyutils.Default.EncryptPEMBlock(block.Bytes, kek)
require.NoError(t, err)

key = pem.EncodeToMemory(block)
Expand Down Expand Up @@ -517,3 +517,47 @@ func TestKeyReadWriterDowngradeKey(t *testing.T) {
require.NoError(t, err)
}
}

// In FIPS mode, when reading a PKCS1 encrypted key, a PKCS1 error is returned as opposed
// to any other type of invalid KEK error
func TestKeyReadWriterReadNonFIPS(t *testing.T) {
t.Parallel()
cert, key, err := testutils.CreateRootCertAndKey("cn")
require.NoError(t, err)

key, err = pkcs8.ConvertToECPrivateKeyPEM(key)
require.NoError(t, err)

tempdir, err := ioutil.TempDir("", "KeyReadWriter")
require.NoError(t, err)
defer os.RemoveAll(tempdir)

path := ca.NewConfigPaths(filepath.Join(tempdir, "subdir")) // to make sure subdirectories are created

k := ca.NewKeyReadWriter(path.Node, nil, nil)
k.SetKeyFormatter(keyutils.FIPS)

// can write an unencrypted PKCS1 key with no issues
require.NoError(t, k.Write(cert, key, nil))
// can read the unencrypted key with no issues
readCert, readKey, err := k.Read()
require.NoError(t, err)
require.Equal(t, cert, readCert)
require.Equal(t, key, readKey)

// cannot write an encrypted PKCS1 key
passphrase := []byte("passphrase")
require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, k.Write(cert, key, &ca.KEKData{KEK: passphrase}))

k.SetKeyFormatter(keyutils.Default)
require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: passphrase}))

// cannot read an encrypted PKCS1 key
k.SetKeyFormatter(keyutils.FIPS)
_, _, err = k.Read()
require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, err)

k.SetKeyFormatter(keyutils.Default)
_, _, err = k.Read()
require.NoError(t, err)
}
51 changes: 34 additions & 17 deletions ca/keyutils/keyutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,28 @@ import (

"github.com/cloudflare/cfssl/helpers"
"github.com/docker/swarmkit/ca/pkcs8"
"github.com/docker/swarmkit/fips"
)

var errFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance")
// Formatter provides an interface for converting keys to the right format, and encrypting and decrypting keys
type Formatter interface {
ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error)
DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error)
EncryptPEMBlock(data, password []byte) (*pem.Block, error)
}

// ErrFIPSUnsupportedKeyFormat is returned when encryption/decryption operations are attempted on a PKCS1 key
// when FIPS mode is enabled.
var ErrFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance")

// Default is the default key util, where FIPS is not required
var Default Formatter = &utils{fips: false}

// FIPS is the key utility which enforces FIPS compliance
var FIPS Formatter = &utils{fips: true}

type utils struct {
fips bool
}

// IsPKCS8 returns true if the provided der bytes is encrypted/unencrypted PKCS#8 key
func IsPKCS8(derBytes []byte) bool {
Expand All @@ -31,48 +49,47 @@ func IsPKCS8(derBytes []byte) bool {
})
}

// IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not
func IsEncryptedPEMBlock(block *pem.Block) bool {
return pkcs8.IsEncryptedPEMBlock(block) || x509.IsEncryptedPEMBlock(block)
}

// ParsePrivateKeyPEMWithPassword parses an encrypted or a decrypted PKCS#1 or PKCS#8 PEM to crypto.Signer.
// It returns an error in FIPS mode if PKCS#1 PEM bytes are passed.
func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) {
func (u *utils) ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("Could not parse PEM")
}

if IsPKCS8(block.Bytes) {
return pkcs8.ParsePrivateKeyPEMWithPassword(pemBytes, password)
} else if fips.Enabled() {
return nil, errFIPSUnsupportedKeyFormat
} else if u.fips {
return nil, ErrFIPSUnsupportedKeyFormat
}

return helpers.ParsePrivateKeyPEMWithPassword(pemBytes, password)
}

// IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not
// It returns false in FIPS mode even if PKCS#1 is encrypted
func IsEncryptedPEMBlock(block *pem.Block) bool {
return pkcs8.IsEncryptedPEMBlock(block) || (!fips.Enabled() && x509.IsEncryptedPEMBlock(block))
}

// DecryptPEMBlock requires PKCS#1 or PKCS#8 PEM Block and password to decrypt and return unencrypted der []byte
// It returns an error in FIPS mode when PKCS#1 PEM Block is passed.
func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) {
func (u *utils) DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) {
if IsPKCS8(block.Bytes) {
return pkcs8.DecryptPEMBlock(block, password)
} else if fips.Enabled() {
return nil, errFIPSUnsupportedKeyFormat
} else if u.fips {
return nil, ErrFIPSUnsupportedKeyFormat
}

return x509.DecryptPEMBlock(block, password)
}

// EncryptPEMBlock takes DER-format bytes and password to return an encrypted PKCS#1 or PKCS#8 PEM-block
// It returns an error in FIPS mode when PKCS#1 PEM bytes are passed.
func EncryptPEMBlock(data, password []byte) (*pem.Block, error) {
func (u *utils) EncryptPEMBlock(data, password []byte) (*pem.Block, error) {
if IsPKCS8(data) {
return pkcs8.EncryptPEMBlock(data, password)
} else if fips.Enabled() {
return nil, errFIPSUnsupportedKeyFormat
} else if u.fips {
return nil, ErrFIPSUnsupportedKeyFormat
}

cipherType := x509.PEMCipherAES256
Expand Down
Loading