func TestGetRemoteSignedCertificate(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() // Create a new CSR to be signed csr, _, err := ca.GenerateAndWriteNewKey(tc.Paths.Node) assert.NoError(t, err) certs, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.ManagerToken, tc.RootCA.Pool, tc.Remotes, nil, nil) assert.NoError(t, err) assert.NotNil(t, certs) // Test the expiration for a manager certificate parsedCerts, err := helpers.ParseCertificatesPEM(certs) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, 1).After(parsedCerts[0].NotAfter)) assert.Equal(t, parsedCerts[0].Subject.OrganizationalUnit[0], ca.ManagerRole) // Test the expiration for an agent certificate certs, err = ca.GetRemoteSignedCertificate(tc.Context, csr, tc.WorkerToken, tc.RootCA.Pool, tc.Remotes, nil, nil) assert.NoError(t, err) assert.NotNil(t, certs) parsedCerts, err = helpers.ParseCertificatesPEM(certs) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, 1).After(parsedCerts[0].NotAfter)) assert.Equal(t, parsedCerts[0].Subject.OrganizationalUnit[0], ca.AgentRole) }
// NewBundlerFromPEM creates a new Bundler from PEM-encoded root certificates and // intermediate certificates. func NewBundlerFromPEM(caBundlePEM, intBundlePEM []byte) (*Bundler, error) { b := &Bundler{ RootPool: x509.NewCertPool(), IntermediatePool: x509.NewCertPool(), KnownIssuers: map[string]bool{}, } log.Debug("parsing root certificates from PEM") roots, err := helpers.ParseCertificatesPEM(caBundlePEM) if err != nil { log.Errorf("failed to parse root bundle: %v", err) return nil, errors.New(errors.RootError, errors.ParseFailed) } log.Debug("parse intermediate certificates from PEM") var intermediates []*x509.Certificate if intermediates, err = helpers.ParseCertificatesPEM(intBundlePEM); err != nil { log.Errorf("failed to parse intermediate bundle: %v", err) return nil, errors.New(errors.IntermediatesError, errors.ParseFailed) } log.Debug("building certificate pools") for _, c := range roots { b.RootPool.AddCert(c) b.KnownIssuers[string(c.Signature)] = true } for _, c := range intermediates { b.IntermediatePool.AddCert(c) b.KnownIssuers[string(c.Signature)] = true } log.Debug("bundler set up") return b, nil }
func TestIssueAndSaveNewCertificates(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() // Test the creation of a manager certificate cert, err := tc.RootCA.IssueAndSaveNewCertificates(tc.Paths.Node, "CN", ca.ManagerRole, tc.Organization) assert.NoError(t, err) assert.NotNil(t, cert) perms, err := permbits.Stat(tc.Paths.Node.Cert) assert.NoError(t, err) assert.False(t, perms.GroupWrite()) assert.False(t, perms.OtherWrite()) certBytes, err := ioutil.ReadFile(tc.Paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, ca.ManagerRole, certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, tc.Organization, certs[0].Subject.Organization[0]) assert.Equal(t, "swarm-test-CA", certs[1].Subject.CommonName) assert.Contains(t, certs[0].DNSNames, "CN") assert.Contains(t, certs[0].DNSNames, "swarm-ca") assert.Contains(t, certs[0].DNSNames, "swarm-manager") // Test the creation of a worker node cert cert, err = tc.RootCA.IssueAndSaveNewCertificates(tc.Paths.Node, "CN", ca.AgentRole, tc.Organization) assert.NoError(t, err) assert.NotNil(t, cert) perms, err = permbits.Stat(tc.Paths.Node.Cert) assert.NoError(t, err) assert.False(t, perms.GroupWrite()) assert.False(t, perms.OtherWrite()) certBytes, err = ioutil.ReadFile(tc.Paths.Node.Cert) assert.NoError(t, err) certs, err = helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, ca.AgentRole, certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, tc.Organization, certs[0].Subject.Organization[0]) assert.Equal(t, "swarm-test-CA", certs[1].Subject.CommonName) assert.Contains(t, certs[0].DNSNames, "CN") assert.Contains(t, certs[0].DNSNames, "swarm-worker") }
// NewCFSSL produces a new CFSSL root. func NewCFSSL(metadata map[string]string) ([]*x509.Certificate, error) { host, ok := metadata["host"] if !ok { return nil, errors.New("transport: CFSSL root provider requires a host") } label := metadata["label"] profile := metadata["profile"] cert, err := helpers.LoadClientCertificate(metadata["mutual-tls-cert"], metadata["mutual-tls-key"]) if err != nil { return nil, err } remoteCAs, err := helpers.LoadPEMCertPool(metadata["tls-remote-ca"]) if err != nil { return nil, err } srv := client.NewServerTLS(host, helpers.CreateTLSConfig(remoteCAs, cert)) data, err := json.Marshal(info.Req{Label: label, Profile: profile}) if err != nil { return nil, err } resp, err := srv.Info(data) if err != nil { return nil, err } return helpers.ParseCertificatesPEM([]byte(resp.Certificate)) }
func TestGenerateAndSignNewTLSCert(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) _, err = ca.GenerateAndSignNewTLSCert(rootCA, "CN", "OU", "ORG", paths.Node) assert.NoError(t, err) perms, err := permbits.Stat(paths.Node.Cert) assert.NoError(t, err) assert.False(t, perms.GroupWrite()) assert.False(t, perms.OtherWrite()) perms, err = permbits.Stat(paths.Node.Key) assert.NoError(t, err) assert.False(t, perms.GroupRead()) assert.False(t, perms.OtherRead()) certBytes, err := ioutil.ReadFile(paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, "OU", certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, "ORG", certs[0].Subject.Organization[0]) assert.Equal(t, "rootCN", certs[1].Subject.CommonName) }
func TestParseValidateAndSignCSR(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) csr, _, err := ca.GenerateAndWriteNewKey(paths.Node) assert.NoError(t, err) signedCert, err := rootCA.ParseValidateAndSignCSR(csr, "CN", "OU", "ORG") assert.NoError(t, err) assert.NotNil(t, signedCert) parsedCert, err := helpers.ParseCertificatesPEM(signedCert) assert.NoError(t, err) assert.Equal(t, 2, len(parsedCert)) assert.Equal(t, "CN", parsedCert[0].Subject.CommonName) assert.Equal(t, 1, len(parsedCert[0].Subject.OrganizationalUnit)) assert.Equal(t, "OU", parsedCert[0].Subject.OrganizationalUnit[0]) assert.Equal(t, 3, len(parsedCert[0].Subject.Names)) assert.Equal(t, "ORG", parsedCert[0].Subject.Organization[0]) assert.Equal(t, "rootCN", parsedCert[1].Subject.CommonName) }
// Tests on verifying the rebundle flag and error code in Bundle.Status when rebundling. func TestRebundleFromPEM(t *testing.T) { newBundler := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, interL1, "") newBundle, err := newBundler.BundleFromPEMorDER(expiredBundlePEM, nil, Optimal, "") if err != nil { t.Fatalf("Re-bundle failed. %s", err.Error()) } newChain := newBundle.Chain if len(newChain) != 2 { t.Fatalf("Expected bundle chain length is 2. Got %d.", len(newChain)) } expiredChain, _ := helpers.ParseCertificatesPEM(expiredBundlePEM) for i, cert := range newChain { old := expiredChain[i] if i == 0 { if !bytes.Equal(old.Signature, cert.Signature) { t.Fatal("Leaf cert should be the same.") } } else { if bytes.Equal(old.Signature, cert.Signature) { t.Fatal("Intermediate cert should be different.") } } } // The status must be {Code: ExpiringBit is not set, IsRebundled:true, ExpiringSKIs:{}} if len(newBundle.Status.ExpiringSKIs) != 0 || !newBundle.Status.IsRebundled || newBundle.Status.Code&errors.BundleExpiringBit != 0 { t.Fatal("Rebundle Status is incorrect.") } }
// NewBundlerFromPEM creates a new Bundler from PEM-encoded root certificates and // intermediate certificates. // If caBundlePEM is nil, the resulting Bundler can only do "Force" bundle. func NewBundlerFromPEM(caBundlePEM, intBundlePEM []byte) (*Bundler, error) { log.Debug("parsing root certificates from PEM") roots, err := helpers.ParseCertificatesPEM(caBundlePEM) if err != nil { log.Errorf("failed to parse root bundle: %v", err) return nil, errors.New(errors.RootError, errors.ParseFailed) } log.Debug("parse intermediate certificates from PEM") intermediates, err := helpers.ParseCertificatesPEM(intBundlePEM) if err != nil { log.Errorf("failed to parse intermediate bundle: %v", err) return nil, errors.New(errors.IntermediatesError, errors.ParseFailed) } b := &Bundler{ KnownIssuers: map[string]bool{}, IntermediatePool: x509.NewCertPool(), } log.Debug("building certificate pools") // RootPool will be nil if caBundlePEM is nil, also // that translates to caBundleFile is "". // Systems root store will be used. if caBundlePEM != nil { b.RootPool = x509.NewCertPool() } for _, c := range roots { b.RootPool.AddCert(c) b.KnownIssuers[string(c.Signature)] = true } for _, c := range intermediates { b.IntermediatePool.AddCert(c) b.KnownIssuers[string(c.Signature)] = true } log.Debug("bundler set up") return b, nil }
// TrustPEM takes a source file containing one or more certificates // and adds them to the trust store. func TrustPEM(metadata map[string]string) ([]*x509.Certificate, error) { sourceFile, ok := metadata["source"] if !ok { return nil, errors.New("transport: PEM source requires a source file") } in, err := ioutil.ReadFile(sourceFile) if err != nil { return nil, err } return helpers.ParseCertificatesPEM(in) }
func TestNewRootCANonDefaultExpiry(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, 1*time.Hour) assert.NoError(t, err) // Create and sign a new CSR csr, _, err := ca.GenerateAndWriteNewKey(paths.Node) assert.NoError(t, err) cert, err := newRootCA.ParseValidateAndSignCSR(csr, "CN", ca.ManagerRole, "ORG") assert.NoError(t, err) parsedCerts, err := helpers.ParseCertificatesPEM(cert) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(time.Minute*59).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(time.Hour).Add(time.Minute).After(parsedCerts[0].NotAfter)) // Sign the same CSR again, this time with a 59 Minute expiration RootCA (under the 60 minute minimum). // This should use the default of 3 months newRootCA, err = ca.NewRootCA(rootCA.Cert, rootCA.Key, 59*time.Minute) assert.NoError(t, err) cert, err = newRootCA.ParseValidateAndSignCSR(csr, "CN", ca.ManagerRole, "ORG") assert.NoError(t, err) parsedCerts, err = helpers.ParseCertificatesPEM(cert) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, 1).After(parsedCerts[0].NotAfter)) }
func TestNewRootCABundle(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) // Write one root CA to disk, keep the bytes secondRootCA, err := ca.CreateAndWriteRootCA("rootCN2", paths.RootCA) assert.NoError(t, err) // Overwrite the first root CA on disk firstRootCA, err := ca.CreateAndWriteRootCA("rootCN1", paths.RootCA) assert.NoError(t, err) // Overwrite the bytes of the second Root CA with the bundle, creating a valid 2 cert bundle bundle := append(firstRootCA.Cert, secondRootCA.Cert...) err = ioutil.WriteFile(paths.RootCA.Cert, bundle, 0644) assert.NoError(t, err) newRootCA, err := ca.NewRootCA(bundle, firstRootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, bundle, newRootCA.Cert) assert.Equal(t, 2, len(newRootCA.Pool.Subjects())) // Now load the bundle from disk diskRootCA, err := ca.GetLocalRootCA(tempBaseDir) assert.NoError(t, err) assert.Equal(t, bundle, diskRootCA.Cert) assert.Equal(t, 2, len(diskRootCA.Pool.Subjects())) // If I use GenerateAndSignNewTLSCert to sign certs, I'll get the correct CA in the chain _, err = ca.GenerateAndSignNewTLSCert(diskRootCA, "CN", "OU", "ORG", paths.Node) assert.NoError(t, err) certBytes, err := ioutil.ReadFile(paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, "OU", certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, "ORG", certs[0].Subject.Organization[0]) assert.Equal(t, "rootCN1", certs[1].Subject.CommonName) }
func TestNewRootCABundle(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) // make one rootCA secondRootCA, err := ca.CreateRootCA("rootCN2", paths.RootCA) assert.NoError(t, err) // make a second root CA firstRootCA, err := ca.CreateRootCA("rootCN1", paths.RootCA) assert.NoError(t, err) // Overwrite the bytes of the second Root CA with the bundle, creating a valid 2 cert bundle bundle := append(firstRootCA.Cert, secondRootCA.Cert...) err = ioutil.WriteFile(paths.RootCA.Cert, bundle, 0644) assert.NoError(t, err) newRootCA, err := ca.NewRootCA(bundle, firstRootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, bundle, newRootCA.Cert) assert.Equal(t, 2, len(newRootCA.Pool.Subjects())) // If I use newRootCA's IssueAndSaveNewCertificates to sign certs, I'll get the correct CA in the chain kw := ca.NewKeyReadWriter(paths.Node, nil, nil) _, err = newRootCA.IssueAndSaveNewCertificates(kw, "CN", "OU", "ORG") assert.NoError(t, err) certBytes, err := ioutil.ReadFile(paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, "OU", certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, "ORG", certs[0].Subject.Organization[0]) assert.Equal(t, "rootCN1", certs[1].Subject.CommonName) }
func TestParseValidateAndSignMaliciousCSR(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) req := &cfcsr.CertificateRequest{ Names: []cfcsr.Name{ { O: "maliciousOrg", OU: "maliciousOU", L: "maliciousLocality", }, }, CN: "maliciousCN", Hosts: []string{"docker.com"}, KeyRequest: &cfcsr.BasicKeyRequest{A: "ecdsa", S: 256}, } csr, _, err := cfcsr.ParseRequest(req) assert.NoError(t, err) signedCert, err := rootCA.ParseValidateAndSignCSR(csr, "CN", "OU", "ORG") assert.NoError(t, err) assert.NotNil(t, signedCert) parsedCert, err := helpers.ParseCertificatesPEM(signedCert) assert.NoError(t, err) assert.Equal(t, 2, len(parsedCert)) assert.Equal(t, "CN", parsedCert[0].Subject.CommonName) assert.Equal(t, 1, len(parsedCert[0].Subject.OrganizationalUnit)) assert.Equal(t, "OU", parsedCert[0].Subject.OrganizationalUnit[0]) assert.Equal(t, 3, len(parsedCert[0].Subject.Names)) assert.Empty(t, parsedCert[0].Subject.Locality) assert.Equal(t, "ORG", parsedCert[0].Subject.Organization[0]) assert.Equal(t, "rootCN", parsedCert[1].Subject.CommonName) }
// NewCFSSL produces a new CFSSL root. func NewCFSSL(metadata map[string]string) ([]*x509.Certificate, error) { host, ok := metadata["host"] if !ok { return nil, errors.New("transport: CFSSL root provider requires a host") } label := metadata["label"] profile := metadata["profile"] srv := client.NewServer(host) data, err := json.Marshal(info.Req{Label: label, Profile: profile}) if err != nil { return nil, err } resp, err := srv.Info(data) if err != nil { return nil, err } return helpers.ParseCertificatesPEM([]byte(resp.Certificate)) }
// newCustomizedBundleCreator is a helper function that returns a new Bundler // takes specified CA bundle, intermediate bundle, and any additional intermdiate certs to generate a bundler. func newCustomizedBundlerFromFile(t *testing.T, caBundle, intBundle, adhocInters string) (b *Bundler) { b, err := NewBundler(caBundle, intBundle) if err != nil { t.Fatal(err) } if adhocInters != "" { moreIntersPEM, err := ioutil.ReadFile(adhocInters) if err != nil { t.Fatal("Read additional intermediates failed. %s", err.Error()) } intermediates, err := helpers.ParseCertificatesPEM(moreIntersPEM) if err != nil { t.Fatalf("Parsing additional intermediates failed. %s", err.Error()) } for _, c := range intermediates { b.IntermediatePool.AddCert(c) } } return }
func TestIssueAndSaveNewCertificates(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() // Copy the current RootCA without the signer cert, err := tc.RootCA.IssueAndSaveNewCertificates(tc.Paths.Node, "CN", ca.ManagerRole, tc.Organization) assert.NoError(t, err) assert.NotNil(t, cert) perms, err := permbits.Stat(tc.Paths.Node.Cert) assert.NoError(t, err) assert.False(t, perms.GroupWrite()) assert.False(t, perms.OtherWrite()) certBytes, err := ioutil.ReadFile(tc.Paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, ca.ManagerRole, certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, tc.Organization, certs[0].Subject.Organization[0]) assert.Equal(t, "swarm-test-CA", certs[1].Subject.CommonName) }
// BundleFromPEMorDER builds a certificate bundle from the set of byte // slices containing the PEM or DER-encoded certificate(s), private key. func (b *Bundler) BundleFromPEMorDER(certsRaw, keyPEM []byte, flavor BundleFlavor, password string) (*Bundle, error) { log.Debug("bundling from PEM files") var key crypto.Signer var err error if len(keyPEM) != 0 { key, err = helpers.ParsePrivateKeyPEM(keyPEM) if err != nil { log.Debugf("failed to parse private key: %v", err) return nil, err } } certs, err := helpers.ParseCertificatesPEM(certsRaw) if err != nil { // If PEM doesn't work try DER var keyDER crypto.Signer var errDER error certs, keyDER, errDER = helpers.ParseCertificatesDER(certsRaw, password) // Only use DER key if no key read from file if key == nil && keyDER != nil { key = keyDER } if errDER != nil { log.Debugf("failed to parse certificates: %v", err) // If neither parser works pass along PEM error return nil, err } } if len(certs) == 0 { log.Debugf("no certificates found") return nil, errors.New(errors.CertificateError, errors.DecodeFailed) } log.Debugf("bundle ready") return b.Bundle(certs, key, flavor) }
// BundleFromPEM builds a certificate bundle from the set of byte // slices containing the PEM-encoded certificate(s), private key. func (b *Bundler) BundleFromPEM(certsPEM, keyPEM []byte, flavor BundleFlavor) (*Bundle, error) { log.Debug("bundling from PEM files") var key interface{} var err error if len(keyPEM) != 0 { key, err = helpers.ParsePrivateKeyPEM(keyPEM) log.Debugf("failed to parse private key: %v", err) if err != nil { return nil, err } } certs, err := helpers.ParseCertificatesPEM(certsPEM) if err != nil { log.Debugf("failed to parse certificates: %v", err) return nil, err } else if len(certs) == 0 { log.Debugf("no certificates found") return nil, errors.New(errors.CertificateError, errors.DecodeFailed, nil) } log.Debugf("bundle ready") return b.Bundle(certs, key, flavor) }
func displayAllCerts(in []byte, leafOnly bool) { certs, err := helpers.ParseCertificatesPEM(in) if err != nil { certs, _, err = helpers.ParseCertificatesDER(in, "") if err != nil { Warn(TranslateCFSSLError(err), "failed to parse certificates") return } } if len(certs) == 0 { Warnx("no certificates found") return } if leafOnly { displayCert(certs[0]) return } for i := range certs { displayCert(certs[i]) } }
// NewRootCA creates a new RootCA object from unparsed PEM cert bundle and key byte // slices. key may be nil, and in this case NewRootCA will return a RootCA // without a signer. func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, error) { // Parse all the certificates in the cert bundle parsedCerts, err := helpers.ParseCertificatesPEM(certBytes) if err != nil { return RootCA{}, err } // Check to see if we have at least one valid cert if len(parsedCerts) < 1 { return RootCA{}, fmt.Errorf("no valid Root CA certificates found") } // Create a Pool with all of the certificates found pool := x509.NewCertPool() for _, cert := range parsedCerts { // Check to see if all of the certificates are valid, self-signed root CA certs if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil { return RootCA{}, fmt.Errorf("error while validating Root CA Certificate: %v", err) } pool.AddCert(cert) } // Calculate the digest for our Root CA bundle digest := digest.FromBytes(certBytes) if len(keyBytes) == 0 { // This RootCA does not have a valid signer. return RootCA{Cert: certBytes, Digest: digest, Pool: pool}, nil } var ( passphraseStr string passphrase, passphrasePrev []byte priv crypto.Signer ) // Attempt two distinct passphrases, so we can do a hitless passphrase rotation if passphraseStr = os.Getenv(PassphraseENVVar); passphraseStr != "" { passphrase = []byte(passphraseStr) } if p := os.Getenv(PassphraseENVVarPrev); p != "" { passphrasePrev = []byte(p) } // Attempt to decrypt the current private-key with the passphrases provided priv, err = helpers.ParsePrivateKeyPEMWithPassword(keyBytes, passphrase) if err != nil { priv, err = helpers.ParsePrivateKeyPEMWithPassword(keyBytes, passphrasePrev) if err != nil { log.Debug("Malformed private key %v", err) return RootCA{}, err } } // We will always use the first certificate inside of the root bundle as the active one if err := ensureCertKeyMatch(parsedCerts[0], priv.Public()); err != nil { return RootCA{}, err } signer, err := local.NewSigner(priv, parsedCerts[0], cfsigner.DefaultSigAlgo(priv), SigningPolicy(certExpiry)) if err != nil { return RootCA{}, err } // If the key was loaded from disk unencrypted, but there is a passphrase set, // ensure it is encrypted, so it doesn't hit raft in plain-text keyBlock, _ := pem.Decode(keyBytes) if keyBlock == nil { // This RootCA does not have a valid signer. return RootCA{Cert: certBytes, Digest: digest, Pool: pool}, nil } if passphraseStr != "" && !x509.IsEncryptedPEMBlock(keyBlock) { keyBytes, err = EncryptECPrivateKey(keyBytes, passphraseStr) if err != nil { return RootCA{}, err } } return RootCA{Signer: signer, Key: keyBytes, Digest: digest, Cert: certBytes, Pool: pool}, nil }
// RunAPITests runs a test suite based on on API Input and returns an API Result. func RunAPITests(in *testapi.Input, c *client.Client, testLen time.Duration, workers int) (*testapi.Results, error) { log.Debugf("Testing %s", in.Keyserver) var err error var certs []*x509.Certificate if len(in.CertsPEM) > 0 { log.Debug("Parsing certificate PEM") certs, err = helpers.ParseCertificatesPEM([]byte(in.CertsPEM)) if err != nil { log.Warning("Couldn't parse certificate PEM") return nil, err } } var sni string if in.Domain != "" { log.Debugf("Getting certificate from %s", in.Domain) if cert, err := getCertFromDomain(in.Domain); err == nil { certs = append(certs, cert) } else { log.Warningf("Couldn't get certificate from %s: %v", in.Domain, err) } if sni, _, err = net.SplitHostPort(in.Domain); err != nil { sni = in.Domain } } c.Config.InsecureSkipVerify = in.InsecureSkipVerify serverIP := net.ParseIP(in.ServerIP) if newTestLen, err := time.ParseDuration(in.TestLen); err == nil { if newTestLen > 0 && newTestLen < 30*time.Second { testLen = newTestLen } } if newWorkers, err := strconv.Atoi(in.Workers); err == nil { if newWorkers > 0 && newWorkers < 1024 { workers = newWorkers } } results := testapi.NewResults() if len(in.HashedToken) > 0 { results.RegisterTest("activate", NewActivateTest(c, in.Keyserver, in.HashedToken)) } results.RegisterTest("ping", NewPingTest(c, in.Keyserver)) for _, cert := range certs { priv, err := c.RegisterPublicKeyTemplate(in.Keyserver, cert.PublicKey, sni, serverIP) if err != nil { return nil, err } ski, err := gokeyless.GetSKICert(cert) if err != nil { return nil, err } if _, ok := priv.Public().(*rsa.PublicKey); ok { results.RegisterTest(ski.String()+"."+"decrypt", NewDecryptTest(priv)) } for name, test := range NewSignTests(priv) { results.RegisterTest(ski.String()+"."+name, test) } } results.RunTests(testLen, workers) return results, nil }
func TestForceNewCluster(t *testing.T) { t.Parallel() // create an external CA so that we can use it to generate expired certificates tempDir, err := ioutil.TempDir("", "external-ca") require.NoError(t, err) defer os.RemoveAll(tempDir) rootCA, err := ca.CreateRootCA("externalRoot", ca.NewConfigPaths(tempDir).RootCA) require.NoError(t, err) // start a new cluster with the external CA bootstrapped numWorker, numManager := 0, 1 cl := newTestCluster() defer func() { require.NoError(t, cl.Stop()) }() require.NoError(t, cl.AddManager(false, &rootCA), "manager number 1") pollClusterReady(t, cl, numWorker, numManager) leader, err := cl.Leader() require.NoError(t, err) sid, err := cl.CreateService("test_service", 2) require.NoError(t, err) pollServiceReady(t, cl, sid) // 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(rand.Reader, expiredCertTemplate, rootCert, expiredCertTemplate.PublicKey, rootKey) require.NoError(t, err) expiredCertPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: expiredCertDERBytes, }) // 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() require.NoError(t, leader.Pause(true)) require.NoError(t, ioutil.WriteFile(managerCertFile, expiredCertPEM, 0644)) require.NoError(t, cl.StartNode(nodeID)) pollClusterReady(t, cl, numWorker, numManager) pollServiceReady(t, cl, sid) err = raftutils.PollFuncWithTimeout(nil, func() error { certBytes, err := ioutil.ReadFile(managerCertFile) if err != nil { return err } managerCerts, err := helpers.ParseCertificatesPEM(certBytes) if err != nil { return err } if managerCerts[0].NotAfter.Before(time.Now()) { return errors.New("certificate hasn't been renewed yet") } return nil }, opsTimeout) require.NoError(t, err) // restart node with an expired certificate without forcing a new cluster - it should error on start require.NoError(t, leader.Pause(true)) require.NoError(t, ioutil.WriteFile(managerCertFile, expiredCertPEM, 0644)) require.Error(t, cl.StartNode(nodeID)) }
func TestGetRemoteCA(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() shaHash := sha256.New() shaHash.Write(tc.RootCA.Cert) md := shaHash.Sum(nil) mdStr := hex.EncodeToString(md) d, err := digest.Parse("sha256:" + mdStr) require.NoError(t, err) downloadedRootCA, err := ca.GetRemoteCA(tc.Context, d, tc.ConnBroker) require.NoError(t, err) require.Equal(t, downloadedRootCA.Cert, tc.RootCA.Cert) // update the test CA to include a multi-certificate bundle as the root - the digest // we use to verify with must be the digest of the whole bundle tmpDir, err := ioutil.TempDir("", "GetRemoteCA") require.NoError(t, err) defer os.RemoveAll(tmpDir) paths := ca.NewConfigPaths(tmpDir) otherRootCA, err := ca.CreateRootCA("other", paths.RootCA) require.NoError(t, err) comboCertBundle := append(tc.RootCA.Cert, otherRootCA.Cert...) require.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error { cluster := store.GetCluster(tx, tc.Organization) cluster.RootCA.CACert = comboCertBundle cluster.RootCA.CAKey = tc.RootCA.Key return store.UpdateCluster(tx, cluster) })) require.NoError(t, raftutils.PollFunc(nil, func() error { _, err := ca.GetRemoteCA(tc.Context, d, tc.ConnBroker) if err == nil { return fmt.Errorf("testca's rootca hasn't updated yet") } require.Contains(t, err.Error(), "remote CA does not match fingerprint") return nil })) // If we provide the right digest, the root CA is updated and we can validate // certs signed by either one d = digest.FromBytes(comboCertBundle) downloadedRootCA, err = ca.GetRemoteCA(tc.Context, d, tc.ConnBroker) require.NoError(t, err) require.Equal(t, comboCertBundle, downloadedRootCA.Cert) require.Equal(t, 2, len(downloadedRootCA.Pool.Subjects())) for _, rootCA := range []ca.RootCA{tc.RootCA, otherRootCA} { krw := ca.NewKeyReadWriter(paths.Node, nil, nil) _, err := rootCA.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) certPEM, _, err := krw.Read() require.NoError(t, err) cert, err := helpers.ParseCertificatesPEM(certPEM) require.NoError(t, err) chains, err := cert[0].Verify(x509.VerifyOptions{ Roots: downloadedRootCA.Pool, }) require.NoError(t, err) require.Len(t, chains, 1) } }
func main() { var caFile, intFile string var forceIntermediateBundle, revexp, verbose bool flag.StringVar(&caFile, "ca", "", "CA certificate `bundle`") flag.StringVar(&intFile, "i", "", "intermediate `bundle`") flag.BoolVar(&forceIntermediateBundle, "f", false, "force the use of the intermediate bundle, ignoring any intermediates bundled with certificate") flag.BoolVar(&revexp, "r", false, "print revocation and expiry information") flag.BoolVar(&verbose, "v", false, "verbose") flag.Parse() var roots *x509.CertPool if caFile != "" { var err error if verbose { fmt.Println("[+] loading root certificates from", caFile) } roots, err = helpers.LoadPEMCertPool(caFile) die.If(err) } var ints *x509.CertPool if intFile != "" { var err error if verbose { fmt.Println("[+] loading intermediate certificates from", intFile) } ints, err = helpers.LoadPEMCertPool(caFile) die.If(err) } else { ints = x509.NewCertPool() } if flag.NArg() != 1 { fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert", lib.ProgName()) } fileData, err := ioutil.ReadFile(flag.Arg(0)) die.If(err) chain, err := helpers.ParseCertificatesPEM(fileData) die.If(err) if verbose { fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain)) } cert := chain[0] if len(chain) > 1 { if !forceIntermediateBundle { for _, intermediate := range chain[1:] { if verbose { fmt.Printf("[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId) } ints.AddCert(intermediate) } } } opts := x509.VerifyOptions{ Intermediates: ints, Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } _, err = cert.Verify(opts) if err != nil { fmt.Fprintf(os.Stderr, "Verification failed: %v\n", err) os.Exit(1) } if verbose { fmt.Println("OK") } if revexp { printRevocation(cert) } }
func TestCreateCertificateChain(t *testing.T) { // N is the number of certificates that will be chained together. N := 10 // --- TEST: Create a chain of one certificate. --- // encodedChainFromCode, _, err := CreateCertificateChain([]csr.CertificateRequest{CARequest}) checkError(err, t) // Now compare to a pre-made certificate chain using a JSON file containing // the same request data. CLIOutputFile := preMadeOutput CLIOutput, err := ioutil.ReadFile(CLIOutputFile) checkError(err, t) encodedChainFromCLI, err := cleanCLIOutput(CLIOutput, "cert") checkError(err, t) chainFromCode, err := helpers.ParseCertificatesPEM(encodedChainFromCode) checkError(err, t) chainFromCLI, err := helpers.ParseCertificatesPEM(encodedChainFromCLI) checkError(err, t) if !chainsEqual(chainFromCode, chainFromCLI) { unequalFieldSlices := checkFieldsOfChains(chainFromCode, chainFromCLI) for i, unequalFields := range unequalFieldSlices { if len(unequalFields) > 0 { t.Log("The certificate chains held unequal fields for chain " + strconv.Itoa(i)) t.Log("The following fields were unequal:") for _, field := range unequalFields { t.Log("\t" + field) } } } t.Fatal("Certificate chains unequal.") } // --- TEST: Create a chain of N certificates. --- // // First we make a slice of N requests. We make each slightly different. cnGrabBag := []string{"example", "invalid", "test"} topLevelDomains := []string{".com", ".net", ".org"} subDomains := []string{"www.", "secure.", "ca.", ""} countryGrabBag := []string{"USA", "China", "England", "Vanuatu"} stateGrabBag := []string{"California", "Texas", "Alaska", "London"} localityGrabBag := []string{"San Francisco", "Houston", "London", "Oslo"} orgGrabBag := []string{"Internet Widgets, LLC", "CloudFlare, Inc."} orgUnitGrabBag := []string{"Certificate Authority", "Systems Engineering"} requests := make([]csr.CertificateRequest, N) requests[0] = CARequest for i := 1; i < N; i++ { requests[i] = baseRequest cn := randomElement(cnGrabBag) tld := randomElement(topLevelDomains) subDomain1 := randomElement(subDomains) subDomain2 := randomElement(subDomains) country := randomElement(countryGrabBag) state := randomElement(stateGrabBag) locality := randomElement(localityGrabBag) org := randomElement(orgGrabBag) orgUnit := randomElement(orgUnitGrabBag) requests[i].CN = cn + "." + tld requests[i].Names = []csr.Name{ {C: country, ST: state, L: locality, O: org, OU: orgUnit, }, } hosts := []string{subDomain1 + requests[i].CN} if subDomain2 != subDomain1 { hosts = append(hosts, subDomain2+requests[i].CN) } requests[i].Hosts = hosts } // Now we make a certificate chain out of these requests. encodedCertChain, _, err := CreateCertificateChain(requests) checkError(err, t) // To test this chain, we compare the data encoded in each certificate to // each request we used to generate the chain. chain, err := helpers.ParseCertificatesPEM(encodedCertChain) checkError(err, t) if len(chain) != len(requests) { t.Log("Length of chain: " + strconv.Itoa(len(chain))) t.Log("Length of requests: " + strconv.Itoa(len(requests))) t.Fatal("Length of chain not equal to length of requests.") } mismatchOccurred := false for i := 0; i < len(chain); i++ { certEqualsRequest, unequalFields := certEqualsRequest(chain[i], requests[i]) if !certEqualsRequest { mismatchOccurred = true t.Log( "Certificate " + strconv.Itoa(i) + " and request " + strconv.Itoa(i) + " unequal.", ) t.Log("Unequal fields for index " + strconv.Itoa(i) + ":") for _, field := range unequalFields { t.Log("\t" + field) } } } // TODO: check that each certificate is actually signed by the previous one if mismatchOccurred { t.Fatal("Unequal certificate(s) and request(s) found.") } // --- TEST: Create a chain of certificates with invalid path lengths. --- // // Other invalid chains? }