package initca import ( "bytes" "crypto/ecdsa" "crypto/rsa" "io/ioutil" "strings" "testing" "time" "github.com/cloudflare/cfssl/config" "github.com/cloudflare/cfssl/csr" "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/signer" "github.com/cloudflare/cfssl/signer/local" ) var validKeyParams = []csr.BasicKeyRequest{ {A: "rsa", S: 2048}, {A: "rsa", S: 3072}, {A: "rsa", S: 4096}, {A: "ecdsa", S: 256}, {A: "ecdsa", S: 384}, {A: "ecdsa", S: 521}, } var validCAConfigs = []csr.CAConfig{ {PathLength: 0, PathLenZero: true}, {PathLength: 0, PathLenZero: false}, {PathLength: 2}, {PathLength: 2, Expiry: "1h"}, // invalid PathLenZero value will be ignored {PathLength: 2, PathLenZero: true}, } var invalidCAConfig = csr.CAConfig{ PathLength: 2, // Expiry must be a duration string Expiry: "2116/12/31", } var csrFiles = []string{ "testdata/rsa2048.csr", "testdata/rsa3072.csr", "testdata/rsa4096.csr", "testdata/ecdsa256.csr", "testdata/ecdsa384.csr", "testdata/ecdsa521.csr", } var testRSACAFile = "testdata/5min-rsa.pem" var testRSACAKeyFile = "testdata/5min-rsa-key.pem" var testECDSACAFile = "testdata/5min-ecdsa.pem" var testECDSACAKeyFile = "testdata/5min-ecdsa-key.pem" var invalidCryptoParams = []csr.BasicKeyRequest{ // Weak Key {A: "rsa", S: 1024}, // Bad param {A: "rsaCrypto", S: 2048}, {A: "ecdsa", S: 2000}, } func TestInitCA(t *testing.T) { var req *csr.CertificateRequest hostname := "cloudflare.com" for _, param := range validKeyParams { for _, caconfig := range validCAConfigs { req = &csr.CertificateRequest{ Names: []csr.Name{ { C: "US", ST: "California", L: "San Francisco", O: "CloudFlare", OU: "Systems Engineering", }, }, CN: hostname, Hosts: []string{hostname, "www." + hostname}, KeyRequest: ¶m, CA: &caconfig, } certBytes, _, keyBytes, err := New(req) if err != nil { t.Fatal("InitCA failed:", err) } key, err := helpers.ParsePrivateKeyPEM(keyBytes) if err != nil { t.Fatal("InitCA private key parsing failed:", err) } cert, err := helpers.ParseCertificatePEM(certBytes) if err != nil { t.Fatal("InitCA cert parsing failed:", err) } // Verify key parameters. switch req.KeyRequest.Algo() { case "rsa": if cert.PublicKey.(*rsa.PublicKey).N.BitLen() != param.Size() { t.Fatal("Cert key length mismatch.") } if key.(*rsa.PrivateKey).N.BitLen() != param.Size() { t.Fatal("Private key length mismatch.") } case "ecdsa": if cert.PublicKey.(*ecdsa.PublicKey).Curve.Params().BitSize != param.Size() { t.Fatal("Cert key length mismatch.") } if key.(*ecdsa.PrivateKey).Curve.Params().BitSize != param.Size() { t.Fatal("Private key length mismatch.") } } // Verify CA MaxPathLen if caconfig.PathLength == 0 && cert.MaxPathLenZero != caconfig.PathLenZero { t.Fatalf("fail to init a CA cert with specified CA pathlen zero: expect %v, got %v", caconfig.PathLenZero, cert.MaxPathLenZero) } if caconfig.PathLength != 0 { if cert.MaxPathLen != caconfig.PathLength { t.Fatalf("fail to init a CA cert with specified CA pathlen: expect %d, got %d", caconfig.PathLength, cert.MaxPathLen) } if cert.MaxPathLenZero != false { t.Fatalf("fail to init a CA cert with specified CA pathlen zero: expect false, got %t", cert.MaxPathLenZero) } } // Replace the default CAPolicy with a test (short expiry) version. CAPolicy = func() *config.Signing { return &config.Signing{ Default: &config.SigningProfile{ Usage: []string{"cert sign", "crl sign"}, ExpiryString: "300s", Expiry: 300 * time.Second, CAConstraint: config.CAConstraint{IsCA: true}, }, } } // Start a signer s, err := local.NewSigner(key, cert, signer.DefaultSigAlgo(key), nil) if err != nil { t.Fatal("Signer Creation error:", err) } s.SetPolicy(CAPolicy()) // Sign RSA and ECDSA customer CSRs. for _, csrFile := range csrFiles { csrBytes, err := ioutil.ReadFile(csrFile) if err != nil { t.Fatal("CSR loading error:", err) } req := signer.SignRequest{ Request: string(csrBytes), Hosts: signer.SplitHosts(hostname), Profile: "", Label: "", } bytes, err := s.Sign(req) if err != nil { t.Fatal(err) } customerCert, _ := helpers.ParseCertificatePEM(bytes) if customerCert.SignatureAlgorithm != s.SigAlgo() { t.Fatal("Signature Algorithm mismatch") } err = customerCert.CheckSignatureFrom(cert) if err != nil { t.Fatal("Signing CSR failed.", err) } } } } } func TestInvalidCAConfig(t *testing.T) { hostname := "example.com" req := &csr.CertificateRequest{ Names: []csr.Name{ { C: "US", ST: "California", L: "San Francisco", O: "CloudFlare", OU: "Systems Engineering", }, }, CN: hostname, Hosts: []string{hostname, "www." + hostname}, KeyRequest: &validKeyParams[0], CA: &invalidCAConfig, } _, _, _, err := New(req) if err == nil { t.Fatal("InitCA with bad CAConfig should fail:", err) } } func TestInvalidCryptoParams(t *testing.T) { var req *csr.CertificateRequest hostname := "cloudflare.com" for _, invalidParam := range invalidCryptoParams { req = &csr.CertificateRequest{ Names: []csr.Name{ { C: "US", ST: "California", L: "San Francisco", O: "CloudFlare", OU: "Systems Engineering", }, }, CN: hostname, Hosts: []string{hostname, "www." + hostname}, KeyRequest: &invalidParam, } _, _, _, err := New(req) if err == nil { t.Fatal("InitCA with bad params should fail:", err) } if !strings.Contains(err.Error(), `"code":2400`) { t.Fatal(err) } } } type validation struct { r *csr.CertificateRequest v bool } var testValidations = []validation{ {&csr.CertificateRequest{}, false}, {&csr.CertificateRequest{ CN: "test CA", }, true}, {&csr.CertificateRequest{ Names: []csr.Name{{}}, }, false}, {&csr.CertificateRequest{ Names: []csr.Name{ {O: "Example CA"}, }, }, true}, } func TestValidations(t *testing.T) { for i, tv := range testValidations { err := validator(tv.r) if tv.v && err != nil { t.Fatalf("%v", err) } if !tv.v && err == nil { t.Fatalf("%d: expected error, but no error was reported", i) } } } func TestRenewRSA(t *testing.T) { certPEM, err := RenewFromPEM(testRSACAFile, testRSACAKeyFile) if err != nil { t.Fatal(err) } // must parse ok cert, err := helpers.ParseCertificatePEM(certPEM) if err != nil { t.Fatal(err) } if !cert.IsCA { t.Fatal("renewed CA certificate is not CA") } // cert expiry must be 5 minutes expiry := cert.NotAfter.Sub(cert.NotBefore).Seconds() if expiry >= 301 || expiry <= 299 { t.Fatal("expiry is not correct:", expiry) } // check subject if cert.Subject.CommonName != "" { t.Fatal("Bad CommonName") } if len(cert.Subject.Country) != 1 || cert.Subject.Country[0] != "US" { t.Fatal("Bad Subject") } if len(cert.Subject.Organization) != 1 || cert.Subject.Organization[0] != "CloudFlare, Inc." { t.Fatal("Bad Subject") } } func TestRenewECDSA(t *testing.T) { certPEM, err := RenewFromPEM(testECDSACAFile, testECDSACAKeyFile) if err != nil { t.Fatal(err) } // must parse ok cert, err := helpers.ParseCertificatePEM(certPEM) if err != nil { t.Fatal(err) } if !cert.IsCA { t.Fatal("renewed CA certificate is not CA") } // cert expiry must be 5 minutes expiry := cert.NotAfter.Sub(cert.NotBefore).Seconds() if expiry >= 301 || expiry <= 299 { t.Fatal("expiry is not correct:", expiry) } // check subject if cert.Subject.CommonName != "" { t.Fatal("Bad CommonName") } if len(cert.Subject.Country) != 1 || cert.Subject.Country[0] != "US" { t.Fatal("Bad Subject") } if len(cert.Subject.Organization) != 1 || cert.Subject.Organization[0] != "CloudFlare, Inc." { t.Fatal("Bad Subject") } } func TestRenewMismatch(t *testing.T) { _, err := RenewFromPEM(testECDSACAFile, testRSACAKeyFile) if err == nil { t.Fatal("Fail to detect cert/key mismatch") } } func TestRenew(t *testing.T) { in, err := ioutil.ReadFile(testECDSACAFile) if err != nil { t.Fatal(err) } cert, err := helpers.ParseCertificatePEM(in) if err != nil { t.Fatal(err) } in, err = ioutil.ReadFile(testECDSACAKeyFile) if err != nil { t.Fatal(err) } priv, err := helpers.ParsePrivateKeyPEM(in) if err != nil { t.Fatal(err) } renewed, err := Update(cert, priv) if err != nil { t.Fatal(err) } newCert, err := helpers.ParseCertificatePEM(renewed) if err != nil { t.Fatal(err) } if !bytes.Equal(newCert.RawSubjectPublicKeyInfo, cert.RawSubjectPublicKeyInfo) { t.Fatal("Update returned a certificate with different subject public key info") } if !bytes.Equal(newCert.RawSubject, cert.RawSubject) { t.Fatal("Update returned a certificate with different subject info") } if !bytes.Equal(newCert.RawIssuer, cert.RawIssuer) { t.Fatal("Update returned a certificate with different issuer info") } }