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()