From a8e341aab4457e0f5e6263fd66076027d92757db Mon Sep 17 00:00:00 2001 From: cyli Date: Wed, 1 Mar 2017 22:57:40 -0800 Subject: [PATCH] Add utility function to verify certificate chains. Update all the TLS verification and loading to use the verify cert chain function so that intermediates in TLS certs can be supported. Signed-off-by: cyli --- ca/certificates.go | 142 +++++++++++++++++++++++-------- ca/certificates_test.go | 146 ++++++++++++++++++++++++++++++++ ca/config.go | 20 +---- ca/config_test.go | 89 ++++++++++--------- ca/testutils/cautils.go | 23 +++++ ca/testutils/staticcerts.go | 70 +++++++++++++++ integration/integration_test.go | 23 +---- 7 files changed, 400 insertions(+), 113 deletions(-) diff --git a/ca/certificates.go b/ca/certificates.go index fb781b6e41..f43dbedbbb 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -105,11 +105,15 @@ type RootCA struct { // Key will only be used by the original manager to put the private // key-material in raft, no signing operations depend on it. Key []byte - // Cert includes the PEM encoded Certificate for the Root CA + + // Cert contains a bundle of PEM encoded Certificate for the Root CA, the first one of which + // must correspond to the key, if provided Cert []byte Pool *x509.CertPool - // Digest of the serialized bytes of the certificate + + // Digest of the serialized bytes of the certificate(s) Digest digest.Digest + // This signer will be nil if the node doesn't have the appropriate key material Signer cfsigner.Signer } @@ -154,25 +158,6 @@ func (rca *RootCA) IssueAndSaveNewCertificates(kw KeyWriter, cn, ou, org string) return &tlsKeyPair, nil } -// Normally we can just call cert.Verify(opts), but since we actually want more information about -// whether a certificate is not yet valid or expired, we also need to perform the expiry checks ourselves. -func verifyCertificate(cert *x509.Certificate, opts x509.VerifyOptions, allowExpired bool) error { - _, err := cert.Verify(opts) - if invalidErr, ok := err.(x509.CertificateInvalidError); ok && invalidErr.Reason == x509.Expired { - now := time.Now().UTC() - if now.Before(cert.NotBefore) { - return errors.Wrapf(err, "certificate not valid before %s, and it is currently %s", - cert.NotBefore.UTC().Format(time.RFC1123), now.Format(time.RFC1123)) - } - if allowExpired { - return nil - } - return errors.Wrapf(err, "certificate expires at %s, and it is currently %s", - cert.NotAfter.UTC().Format(time.RFC1123), now.Format(time.RFC1123)) - } - return err -} - // RequestAndSaveNewCertificates gets new certificates issued, either by signing them locally if a signer is // available, or by requesting them from the remote server at remoteAddr. func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWriter, config CertificateRequestConfig) (*tls.Certificate, error) { @@ -208,20 +193,9 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit // Доверяй, но проверяй. // Before we overwrite our local key + certificate, let's make sure the server gave us one that is valid // Create an X509Cert so we can .Verify() - certBlock, _ := pem.Decode(signedCert) - if certBlock == nil { - return nil, errors.New("failed to parse certificate PEM") - } - X509Cert, err := x509.ParseCertificate(certBlock.Bytes) - if err != nil { - return nil, err - } - // Include our current root pool - opts := x509.VerifyOptions{ - Roots: rca.Pool, - } // Check to see if this certificate was signed by our CA, and isn't expired - if err := verifyCertificate(X509Cert, opts, false); err != nil { + parsedCerts, err := ValidateCertChain(rca.Pool, signedCert, false) + if err != nil { return nil, err } @@ -233,7 +207,8 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit var kekUpdate *KEKData for i := 0; i < 5; i++ { - kekUpdate, err = rca.getKEKUpdate(ctx, X509Cert, tlsKeyPair, config.ConnBroker) + // ValidateCertChain will always return at least 1 cert, so indexing at 0 is safe + kekUpdate, err = rca.getKEKUpdate(ctx, parsedCerts[0], tlsKeyPair, config.ConnBroker) if err == nil { break } @@ -415,6 +390,103 @@ func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, er return RootCA{Signer: signer, Key: keyBytes, Digest: digest, Cert: certBytes, Pool: pool}, nil } +// ValidateCertChain checks checks that the certificates provided chain up to the root pool provided. In addition +// it also enforces that every cert in the bundle certificates form a chain, each one certifying the one above, +// as per RFC5246 section 7.4.2, and that every certificate (whether or not it is necessary to form a chain to the root +// pool) is currently valid and not yet expired (unless allowExpiry is set to true). +// This is additional validation not required by go's Certificate.Verify (which allows invalid certs in the +// intermediate pool), because this function is intended to be used when reading certs from untrusted locations such as +// from disk or over a network when a CSR is signed, so it is extra pedantic. +// This function always returns all the parsed certificates in the bundle in order, which means there will always be +// at least 1 certificate if there is no error. +func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) ([]*x509.Certificate, error) { + // Parse all the certificates in the cert bundle + parsedCerts, err := helpers.ParseCertificatesPEM(certs) + if err != nil { + return nil, err + } + if len(parsedCerts) == 0 { + return nil, errors.New("no certificates to validate") + } + now := time.Now() + // ensure that they form a chain, each one being signed by the one after it + var intermediatePool *x509.CertPool + for i, cert := range parsedCerts { + // Manual expiry validation because we want more information on which certificate in the chain is expired, and + // because this is an easier way to allow expired certs. + if now.Before(cert.NotBefore) { + return nil, errors.Wrapf( + x509.CertificateInvalidError{ + Cert: cert, + Reason: x509.Expired, + }, + "certificate (%d - %s) not valid before %s, and it is currently %s", + i+1, cert.Subject.CommonName, cert.NotBefore.UTC().Format(time.RFC1123), now.Format(time.RFC1123)) + } + if !allowExpired && now.After(cert.NotAfter) { + return nil, errors.Wrapf( + x509.CertificateInvalidError{ + Cert: cert, + Reason: x509.Expired, + }, + "certificate (%d - %s) not valid after %s, and it is currently %s", + i+1, cert.Subject.CommonName, cert.NotAfter.UTC().Format(time.RFC1123), now.Format(time.RFC1123)) + } + + if i > 0 { + // check that the previous cert was signed by this cert + prevCert := parsedCerts[i-1] + if err := prevCert.CheckSignatureFrom(cert); err != nil { + return nil, errors.Wrapf(err, "certificates do not form a chain: (%d - %s) is not signed by (%d - %s)", + i, prevCert.Subject.CommonName, i+1, cert.Subject.CommonName) + } + + if intermediatePool == nil { + intermediatePool = x509.NewCertPool() + } + intermediatePool.AddCert(cert) + + } + } + + verifyOpts := x509.VerifyOptions{ + Roots: rootPool, + Intermediates: intermediatePool, + CurrentTime: now, + } + + // If we accept expired certs, try to build a valid cert chain using some subset of the certs. We start off using the + // first certificate's NotAfter as the current time, thus ensuring that the first cert is not expired. If the chain + // still fails to validate due to expiry issues, continue iterating over the rest of the certs. + // If any of the other certs has an earlier NotAfter time, use that time as the current time instead. This insures that + // particular cert, and any that came before it, are not expired. Note that the root that the certs chain up to + // should also not be expired at that "current" time. + if allowExpired { + verifyOpts.CurrentTime = parsedCerts[0].NotAfter.Add(time.Hour) + for _, cert := range parsedCerts { + if !cert.NotAfter.Before(verifyOpts.CurrentTime) { + continue + } + verifyOpts.CurrentTime = cert.NotAfter + + _, err = parsedCerts[0].Verify(verifyOpts) + if err == nil { + return parsedCerts, nil + } + } + if invalid, ok := err.(x509.CertificateInvalidError); ok && invalid.Reason == x509.Expired { + return nil, errors.New("there is no time span for which all of the certificates, including a root, are valid") + } + return nil, err + } + + _, err = parsedCerts[0].Verify(verifyOpts) + if err != nil { + return nil, err + } + return parsedCerts, nil +} + func ensureCertKeyMatch(cert *x509.Certificate, key crypto.PublicKey) error { switch certPub := cert.PublicKey.(type) { case *rsa.PublicKey: diff --git a/ca/certificates_test.go b/ca/certificates_test.go index a4a5d714c3..36f0729978 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -405,6 +405,9 @@ func TestRequestAndSaveNewCertificates(t *testing.T) { require.NoError(t, err) } +// TODO(cyli): add test for RequestAndSaveNewCertificates but with intermediates - this involves adding +// support for appending intermediates on the CA server first + func TestIssueAndSaveNewCertificates(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() @@ -741,3 +744,146 @@ func TestNewRootCAWithPassphrase(t *testing.T) { assert.Contains(t, string(anotherNewRootCA.Key), "Proc-Type: 4,ENCRYPTED") } + +type certTestCase struct { + cert []byte + errorStr string + root []byte + allowExpiry bool +} + +func TestValidateCertificateChain(t *testing.T) { + leaf, intermediate, root := testutils.ECDSACertChain[0], testutils.ECDSACertChain[1], testutils.ECDSACertChain[2] + intermediateKey, rootKey := testutils.ECDSACertChainKeys[1], testutils.ECDSACertChainKeys[2] // we don't care about the leaf key + + chain := func(certs ...[]byte) []byte { + var all []byte + for _, cert := range certs { + all = append(all, cert...) + } + return all + } + + now := time.Now() + expiredLeaf := testutils.ReDateCert(t, leaf, intermediate, intermediateKey, now.Add(-10*time.Hour), now.Add(-1*time.Minute)) + expiredIntermediate := testutils.ReDateCert(t, intermediate, root, rootKey, now.Add(-10*time.Hour), now.Add(-1*time.Minute)) + notYetValidLeaf := testutils.ReDateCert(t, leaf, intermediate, intermediateKey, now.Add(time.Hour), now.Add(2*time.Hour)) + notYetValidIntermediate := testutils.ReDateCert(t, intermediate, root, rootKey, now.Add(time.Hour), now.Add(2*time.Hour)) + + rootPool := x509.NewCertPool() + rootPool.AppendCertsFromPEM(root) + + invalids := []certTestCase{ + { + cert: nil, + root: root, + errorStr: "no certificates to validate", + }, + { + cert: []byte("malformed"), + root: root, + errorStr: "Failed to decode certificate", + }, + { + cert: chain(leaf, intermediate, leaf), + root: root, + errorStr: "certificates do not form a chain", + }, + { + cert: chain(leaf, intermediate), + root: testutils.ECDSA256SHA256Cert, + errorStr: "unknown authority", + }, + { + cert: chain(expiredLeaf, intermediate), + root: root, + errorStr: "not valid after", + }, + { + cert: chain(leaf, expiredIntermediate), + root: root, + errorStr: "not valid after", + }, + { + cert: chain(notYetValidLeaf, intermediate), + root: root, + errorStr: "not valid before", + }, + { + cert: chain(leaf, notYetValidIntermediate), + root: root, + errorStr: "not valid before", + }, + + // if we allow expiry, we still don't allow not yet valid certs or expired certs that don't chain up to the root + { + cert: chain(notYetValidLeaf, intermediate), + root: root, + allowExpiry: true, + errorStr: "not valid before", + }, + { + cert: chain(leaf, notYetValidIntermediate), + root: root, + allowExpiry: true, + errorStr: "not valid before", + }, + { + cert: chain(expiredLeaf, intermediate), + root: testutils.ECDSA256SHA256Cert, + allowExpiry: true, + errorStr: "unknown authority", + }, + + // construct a weird cases where one cert is expired, we allow expiry, but the other cert is not yet valid at the first cert's expiry + // (this is not something that can happen unless we allow expiry, because if the cert periods don't overlap, one or the other will + // be either not yet valid or already expired) + { + cert: chain( + testutils.ReDateCert(t, leaf, intermediate, intermediateKey, now.Add(-3*helpers.OneDay), now.Add(-2*helpers.OneDay)), + testutils.ReDateCert(t, intermediate, root, rootKey, now.Add(-1*helpers.OneDay), now.Add(helpers.OneDay))), + root: root, + allowExpiry: true, + errorStr: "there is no time span", + }, + // similarly, but for root pool + { + cert: chain(expiredLeaf, expiredIntermediate), + root: testutils.ReDateCert(t, root, root, rootKey, now.Add(-3*helpers.OneYear), now.Add(-2*helpers.OneYear)), + allowExpiry: true, + errorStr: "there is no time span", + }, + } + + for _, invalid := range invalids { + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(invalid.root) + _, err := ca.ValidateCertChain(pool, invalid.cert, invalid.allowExpiry) + require.Error(t, err, invalid.errorStr) + require.Contains(t, err.Error(), invalid.errorStr) + } + + // these will default to using the root pool, so we don't have to specify the root pool + valids := []certTestCase{ + {cert: chain(leaf, intermediate, root)}, + {cert: chain(leaf, intermediate)}, + {cert: intermediate}, + { + cert: chain(expiredLeaf, intermediate), + allowExpiry: true, + }, + { + cert: chain(leaf, expiredIntermediate), + allowExpiry: true, + }, + { + cert: chain(expiredLeaf, expiredIntermediate), + allowExpiry: true, + }, + } + + for _, valid := range valids { + _, err := ca.ValidateCertChain(rootPool, valid.cert, valid.allowExpiry) + require.NoError(t, err) + } +} diff --git a/ca/config.go b/ca/config.go index 56eb12b20c..c886eba58a 100644 --- a/ca/config.go +++ b/ca/config.go @@ -4,7 +4,6 @@ import ( cryptorand "crypto/rand" "crypto/tls" "crypto/x509" - "encoding/pem" "fmt" "math/big" "math/rand" @@ -290,25 +289,8 @@ func LoadSecurityConfig(ctx context.Context, rootCA RootCA, krw *KeyReadWriter, return nil, err } - // Create an x509 certificate out of the contents on disk - certBlock, _ := pem.Decode([]byte(cert)) - if certBlock == nil { - return nil, errors.New("failed to parse certificate PEM") - } - - // Create an X509Cert so we can .Verify() - X509Cert, err := x509.ParseCertificate(certBlock.Bytes) - if err != nil { - return nil, err - } - - // Include our root pool - opts := x509.VerifyOptions{ - Roots: rootCA.Pool, - } - // Check to see if this certificate was signed by our CA, and isn't expired - if err := verifyCertificate(X509Cert, opts, allowExpired); err != nil { + if _, err := ValidateCertChain(rootCA.Pool, cert, allowExpired); err != nil { return nil, err } diff --git a/ca/config_test.go b/ca/config_test.go index 3aa0e97f5d..a231ade60c 100644 --- a/ca/config_test.go +++ b/ca/config_test.go @@ -1,13 +1,9 @@ package ca_test import ( - cryptorand "crypto/rand" "crypto/tls" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "io/ioutil" - "math/big" "net" "os" "strings" @@ -19,7 +15,6 @@ import ( "golang.org/x/net/context" cfconfig "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/helpers" "github.com/docker/swarmkit/ca" "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/ioutils" @@ -145,45 +140,17 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() - _, key, err := ca.GenerateNewCSR() - require.NoError(t, err) - require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Key, key, 0600)) - certKey, err := helpers.ParsePrivateKeyPEM(key) - require.NoError(t, err) + krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) + now := time.Now() - rootKey, err := helpers.ParsePrivateKeyPEM(tc.RootCA.Key) - require.NoError(t, err) - rootCert, err := helpers.ParseCertificatePEM(tc.RootCA.Cert) + _, err := tc.RootCA.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := cryptorand.Int(cryptorand.Reader, serialNumberLimit) + certBytes, _, err := krw.Read() require.NoError(t, err) - genCert := func(notBefore, notAfter time.Time) { - derBytes, err := x509.CreateCertificate(cryptorand.Reader, &x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: "CN", - OrganizationalUnit: []string{"OU"}, - Organization: []string{"ORG"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - }, rootCert, certKey.Public(), rootKey) - require.NoError(t, err) - certBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: derBytes, - }) - require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Cert, certBytes, 0644)) - } - - krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) - now := time.Now() - // A cert that is not yet valid is not valid even if expiry is allowed - genCert(now.Add(time.Hour), now.Add(time.Hour*2)) + invalidCert := testutils.ReDateCert(t, certBytes, tc.RootCA.Cert, tc.RootCA.Key, now.Add(time.Hour), now.Add(time.Hour*2)) + require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Cert, invalidCert, 0700)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) require.Error(t, err) @@ -194,7 +161,8 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { require.IsType(t, x509.CertificateInvalidError{}, errors.Cause(err)) // a cert that is expired is not valid if expiry is not allowed - genCert(now.Add(time.Hour*-3), now.Add(time.Hour*-1)) + invalidCert = testutils.ReDateCert(t, certBytes, tc.RootCA.Cert, tc.RootCA.Key, now.Add(-2*time.Minute), now.Add(-1*time.Minute)) + require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Cert, invalidCert, 0700)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) require.Error(t, err) @@ -269,6 +237,47 @@ func TestLoadSecurityConfigIncorrectPassphrase(t *testing.T) { require.IsType(t, ca.ErrInvalidKEK{}, err) } +func TestLoadSecurityConfigIntermediates(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-load-config-with-intermediates") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + paths := ca.NewConfigPaths(tempdir) + krw := ca.NewKeyReadWriter(paths.Node, nil, nil) + + rootCA, err := ca.NewRootCA(testutils.ECDSACertChain[2], nil, ca.DefaultNodeCertExpiration) + require.NoError(t, err) + + // loading the incomplete chain fails + require.NoError(t, krw.Write(testutils.ECDSACertChain[0], testutils.ECDSACertChainKeys[0], nil)) + _, err = ca.LoadSecurityConfig(context.Background(), rootCA, krw, false) + require.Error(t, err) + + // loading the complete chain succeeds + require.NoError(t, krw.Write(append(testutils.ECDSACertChain[0], testutils.ECDSACertChain[1]...), testutils.ECDSACertChainKeys[0], nil)) + secConfig, err := ca.LoadSecurityConfig(context.Background(), rootCA, krw, false) + require.NoError(t, err) + require.NotNil(t, secConfig) + + // set up a GRPC server using these credentials + secConfig.ServerTLSCreds.Config().ClientAuth = tls.RequireAndVerifyClientCert + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + serverOpts := []grpc.ServerOption{grpc.Creds(secConfig.ServerTLSCreds)} + grpcServer := grpc.NewServer(serverOpts...) + go grpcServer.Serve(l) + defer grpcServer.Stop() + + // we should be able to connect to the server using the client credentials + dialOpts := []grpc.DialOption{ + grpc.WithBlock(), + grpc.WithTimeout(10 * time.Second), + grpc.WithTransportCredentials(secConfig.ClientTLSCreds), + } + conn, err := grpc.Dial(l.Addr().String(), dialOpts...) + require.NoError(t, err) + conn.Close() +} + func TestSecurityConfigUpdateRootCA(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index cd66d60870..012c550015 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -1,8 +1,10 @@ package testutils import ( + cryptorand "crypto/rand" "crypto/tls" "crypto/x509" + "encoding/pem" "io/ioutil" "net" "os" @@ -27,6 +29,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -432,3 +435,23 @@ func createAndWriteRootCA(rootCN string, paths ca.CertPaths, expiry time.Duratio Digest: digest.FromBytes(cert), }, nil } + +// ReDateCert takes an existing cert and changes the not before and not after date, to make it easier +// to test expiry +func ReDateCert(t *testing.T, cert, signerCert, signerKey []byte, notBefore, notAfter time.Time) []byte { + signee, err := helpers.ParseCertificatePEM(cert) + require.NoError(t, err) + signer, err := helpers.ParseCertificatePEM(signerCert) + require.NoError(t, err) + key, err := helpers.ParsePrivateKeyPEM(signerKey) + require.NoError(t, err) + signee.NotBefore = notBefore + signee.NotAfter = notAfter + + derBytes, err := x509.CreateCertificate(cryptorand.Reader, signee, signer, signee.PublicKey, key) + require.NoError(t, err) + return pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: derBytes, + }) +} diff --git a/ca/testutils/staticcerts.go b/ca/testutils/staticcerts.go index bb1cbbf3f6..0b70abfa26 100644 --- a/ca/testutils/staticcerts.go +++ b/ca/testutils/staticcerts.go @@ -291,4 +291,74 @@ fOGKriJEUSW+M+6JgYBQCxJioIbHuC3ZOJZbgBZsKRmLdLzqmnF568jY6AIVAJ8Z 5HzoPpFuQiZ6/H/N6RYpQmAO -----END DSA PRIVATE KEY----- `) + // ECDSACertChain contains 3 SHA256 curve P-256 certificates: leaf, intermediate, and root + // They all expire in 2117. The leaf cert's OU is swarm-manager. + ECDSACertChain = [][]byte{ + []byte(` +-----BEGIN CERTIFICATE----- +MIIB3TCCAYOgAwIBAgIUG2izItTi/0YNpfdwUwo7UcjddawwCgYIKoZIzj0EAwIw +EjEQMA4GA1UEAxMHcm9vdENOMjAgFw0xNzAzMDEyMzA1MDBaGA8yMTE3MDIwNjAw +MDUwMFowKDEMMAoGA1UEChMDb3JnMQswCQYDVQQLEwJvdTELMAkGA1UEAxMCY24w +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATCVPwZBGYQ0SpeXahXzU8BB+ZBjdw9 +WsKBa03qSic4O0qtUrLTQSvg2bWoKlo2fVe5g6Sl29gMm0912fTG5nHro4GeMIGb +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU/hk9CSt3C+8+hVVe1+xTHdAYka4wHwYD +VR0jBBgwFoAU0qlzziAdvItofIcj5PK+SLIRngAwHAYDVR0RBBUwE4ICY26CDXN3 +YXJtLW1hbmFnZXIwCgYIKoZIzj0EAwIDSAAwRQIhAIV+zZKA58KkkeV9lC7EgVjT +nXZuicOq8369KseHDSINAiAy8QKshS5XUHXFJi778Mclr2jvx88XnV2yYb7osJv4 +Ew== +-----END CERTIFICATE----- +`), + []byte(` +-----BEGIN CERTIFICATE----- +MIIBizCCATCgAwIBAgIUcGcL0qGDloPcLE69t6X81DKiaZAwCgYIKoZIzj0EAwIw +ETEPMA0GA1UEAxMGcm9vdENOMCAXDTE3MDMwMjAwMDAwMFoYDzIxMTcwMjA2MDAw +MDAwWjASMRAwDgYDVQQDEwdyb290Q04yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD +QgAEL4g4/wWhZM/YfCk/zEXbmTIgaiNUsXrqexXGrsFeoxfojAEuA8tygI8mu45V +fNk16nzO4AfXMFBiChB9fPE1dKNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNKpc84gHbyLaHyHI+TyvkiyEZ4AMB8GA1UdIwQY +MBaAFGD5gOqAIojsuSKECZwWE5aeGDD9MAoGCCqGSM49BAMCA0kAMEYCIQDN10Lz +9mqWPOgqlpSboPf+VzC0HA1ZZI5wqETUKCK1wQIhANkepyJrCapiQ6Vuvc+qycuS +ZS16fmlAEKrBm2KgpZt2 +-----END CERTIFICATE----- +`), + []byte(` +-----BEGIN CERTIFICATE----- +MIIBaDCCAQ6gAwIBAgIUfmVlMNH1dFyOjZHL18pw0ji9aTkwCgYIKoZIzj0EAwIw +ETEPMA0GA1UEAxMGcm9vdENOMCAXDTE3MDMwMjAwMDAwMFoYDzIxMTcwMjA2MDAw +MDAwWjARMQ8wDQYDVQQDEwZyb290Q04wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AAT6NjQeSstS/gi2wN+AoWnMZaLfiBjpNSqryqEiPH03viwbtWMG9aCu7cU/3alJ +iIlmQl6Y3n3cFhiQV2dum+UUo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUYPmA6oAiiOy5IoQJnBYTlp4YMP0wCgYIKoZIzj0E +AwIDSAAwRQIgP8iV0PKFeQZey6j89ieI+IPucjfl8Hp1OLJbamrVEr8CIQD0PsI8 +pMJFqD7k4votyNu3W82NrBSe+xyMgFqI5tfx4g== +-----END CERTIFICATE----- +`), + } + + // ECDSACertChainKeys contains 3 SHA256 curve P-256 keys: corresponding, respectively, + // to the certificates in ECDSACertChain + ECDSACertChainKeys = [][]byte{ + []byte(` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIN+BaGyxGLSgEDLjmQBHdL7JuuAIYlSGCwYS2CCUxMEOoAoGCCqGSM49 +AwEHoUQDQgAEwlT8GQRmENEqXl2oV81PAQfmQY3cPVrCgWtN6konODtKrVKy00Er +4Nm1qCpaNn1XuYOkpdvYDJtPddn0xuZx6w== +-----END EC PRIVATE KEY----- +`), + []byte(` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIP7yNfaUImD76q1pfgx+8PYSq50zK1imh41SKFPzR5fioAoGCCqGSM49 +AwEHoUQDQgAEL4g4/wWhZM/YfCk/zEXbmTIgaiNUsXrqexXGrsFeoxfojAEuA8ty +gI8mu45VfNk16nzO4AfXMFBiChB9fPE1dA== +-----END EC PRIVATE KEY----- +`), + []byte(` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDIgEpCpn7wEEYt/hLT+NewO0lgBPBRk3A5nU4ASOShDoAoGCCqGSM49 +AwEHoUQDQgAE+jY0HkrLUv4ItsDfgKFpzGWi34gY6TUqq8qhIjx9N74sG7VjBvWg +ru3FP92pSYiJZkJemN593BYYkFdnbpvlFA== +-----END EC PRIVATE KEY----- +`), + } ) diff --git a/integration/integration_test.go b/integration/integration_test.go index a74d9c5bde..e408f3f0ab 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1,9 +1,6 @@ package integration import ( - cryptorand "crypto/rand" - "crypto/x509" - "encoding/pem" "flag" "fmt" "io/ioutil" @@ -21,6 +18,7 @@ import ( events "github.com/docker/go-events" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" + "github.com/docker/swarmkit/ca/testutils" raftutils "github.com/docker/swarmkit/manager/state/raft/testutils" "github.com/pkg/errors" "github.com/stretchr/testify/require" @@ -503,25 +501,12 @@ func TestForceNewCluster(t *testing.T) { pollServiceReady(t, cl, sid, 2) // generate an expired certificate - rootKey, err := helpers.ParsePrivateKeyPEM(rootCA.Key) - require.NoError(t, err) - rootCert, err := helpers.ParseCertificatePEM(rootCA.Cert) - require.NoError(t, err) - managerCertFile := filepath.Join(leader.stateDir, "certificates", "swarm-node.crt") certBytes, err := ioutil.ReadFile(managerCertFile) require.NoError(t, err) - managerCerts, err := helpers.ParseCertificatesPEM(certBytes) - require.NoError(t, err) - expiredCertTemplate := managerCerts[0] - expiredCertTemplate.NotBefore = time.Now().Add(time.Hour * -5) - expiredCertTemplate.NotAfter = time.Now().Add(time.Hour * -3) - expiredCertDERBytes, err := x509.CreateCertificate(cryptorand.Reader, expiredCertTemplate, rootCert, expiredCertTemplate.PublicKey, rootKey) - require.NoError(t, err) - expiredCertPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: expiredCertDERBytes, - }) + now := time.Now() + // we don't want it too expired, because it can't have expired before the root CA cert is valid + expiredCertPEM := testutils.ReDateCert(t, certBytes, rootCA.Cert, rootCA.Key, now.Add(-1*time.Hour), now.Add(-1*time.Second)) // restart node with an expired certificate while forcing a new cluster - it should start without error and the certificate should be renewed nodeID := leader.node.NodeID()