func TestCAIssuing(t *testing.T) { var caCerts = []string{testCaFile, testECDSACaFile} var caKeys = []string{testCaKeyFile, testECDSACaKeyFile} var interCSRs = []string{ecdsaInterCSR, rsaInterCSR} var interKeys = []string{ecdsaInterKey, rsaInterKey} var CAPolicy = &config.Signing{ Default: &config.SigningProfile{ Usage: []string{"cert sign", "crl sign"}, ExpiryString: "1h", Expiry: 1 * time.Hour, CA: true, }, } var hostname = "cloudflare-inter.com" // Each RSA or ECDSA root CA issues two intermediate CAs (one ECDSA and one RSA). // For each intermediate CA, use it to issue additional RSA and ECDSA intermediate CSRs. for i, caFile := range caCerts { caKeyFile := caKeys[i] s := newCustomSigner(t, caFile, caKeyFile) s.policy = CAPolicy for j, csr := range interCSRs { csrBytes, _ := ioutil.ReadFile(csr) certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csrBytes)}) if err != nil { t.Fatal(err) } interCert, err := helpers.ParseCertificatePEM(certBytes) if err != nil { t.Fatal(err) } keyBytes, _ := ioutil.ReadFile(interKeys[j]) interKey, _ := helpers.ParsePrivateKeyPEM(keyBytes) interSigner := &Signer{ ca: interCert, priv: interKey, policy: CAPolicy, sigAlgo: signer.DefaultSigAlgo(interKey), } for _, anotherCSR := range interCSRs { anotherCSRBytes, _ := ioutil.ReadFile(anotherCSR) bytes, err := interSigner.Sign( signer.SignRequest{ Hosts: signer.SplitHosts(hostname), Request: string(anotherCSRBytes), }) if err != nil { t.Fatal(err) } cert, err := helpers.ParseCertificatePEM(bytes) if err != nil { t.Fatal(err) } if cert.SignatureAlgorithm != interSigner.SigAlgo() { t.Fatal("Cert Signature Algorithm does not match the issuer.") } } } } }
func TestSign(t *testing.T) { testSign(t) s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil) if err != nil { t.Fatal("Failed to produce signer") } // test the empty request _, err = s.Sign(signer.SignRequest{}) if err == nil { t.Fatalf("Empty request failed to produce an error") } // not a csr certPem, err := ioutil.ReadFile("../../helpers/testdata/cert.pem") if err != nil { t.Fatal(err) } // csr with ip as hostname pem, err := ioutil.ReadFile("testdata/ip.csr") if err != nil { t.Fatal(err) } // improper request validReq := signer.SignRequest{Hosts: signer.SplitHosts(testHostName), Request: string(certPem)} _, err = s.Sign(validReq) if err == nil { t.Fatal("A bad case failed to raise an error") } validReq = signer.SignRequest{Hosts: signer.SplitHosts("128.84.126.213"), Request: string(pem)} _, err = s.Sign(validReq) if err != nil { t.Fatal("A bad case failed to raise an error") } pem, err = ioutil.ReadFile("testdata/ex.csr") validReq = signer.SignRequest{ Request: string(pem), Hosts: []string{"example.com"}, } s.Sign(validReq) if err != nil { t.Fatal("Failed to sign") } }
func createCert(c *cfg, s *cli.Config) (*string, error) { signr, err := sign.SignerFromConfig(*s) if err != nil { return nil, err } csrbytes, err := ioutil.ReadFile(s.CSRFile) if err != nil { return nil, err } var cert []byte signReq := signer.SignRequest{ Request: string(csrbytes), Hosts: signer.SplitHosts(s.Hostname), Profile: s.Profile, Label: s.Label, } cert, err = signr.Sign(signReq) if err != nil { return nil, err } certpath := path.Join(c.certpath, fmt.Sprintf("%s.pem", c.certname)) if err := ioutil.WriteFile(certpath, cert, 0600); err != nil { return nil, err } return &certpath, nil }
func TestECDSASigner(t *testing.T) { s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile) hostname := "cloudflare.com" for _, test := range csrTests { csr, err := ioutil.ReadFile(test.file) if err != nil { t.Fatal("CSR loading error:", err) } // Try all ECDSA SignatureAlgorithm SigAlgos := []x509.SignatureAlgorithm{x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512} for _, sigAlgo := range SigAlgos { s.sigAlgo = sigAlgo certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csr)}) if test.errorCallback != nil { test.errorCallback(t, err) } else { if err != nil { t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen) } cert, _ := helpers.ParseCertificatePEM(certBytes) if cert.SignatureAlgorithm != s.SigAlgo() { t.Fatal("Cert Signature Algorithm does not match the issuer.") } } } } }
func TestSignCSRs(t *testing.T) { s := newTestSigner(t) hostname := "cloudflare.com" for _, test := range csrTests { csr, err := ioutil.ReadFile(test.file) if err != nil { t.Fatal("CSR loading error:", err) } // It is possible to use different SHA2 algorithm with RSA CA key. rsaSigAlgos := []x509.SignatureAlgorithm{x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA} for _, sigAlgo := range rsaSigAlgos { s.sigAlgo = sigAlgo certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csr)}) if test.errorCallback != nil { test.errorCallback(t, err) } else { if err != nil { t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen) } cert, _ := helpers.ParseCertificatePEM(certBytes) if cert.SignatureAlgorithm != s.SigAlgo() { t.Fatal("Cert Signature Algorithm does not match the issuer.") } } } } }
func jsonReqToTrue(js jsonSignRequest) signer.SignRequest { sub := new(signer.Subject) if js.Subject == nil { sub = nil } else { // make a copy *sub = *js.Subject } if js.Hostname != "" { return signer.SignRequest{ Hosts: signer.SplitHosts(js.Hostname), Subject: sub, Request: js.Request, Profile: js.Profile, Label: js.Label, SerialSeq: js.SerialSeq, } } return signer.SignRequest{ Hosts: js.Hosts, Subject: sub, Request: js.Request, Profile: js.Profile, Label: js.Label, SerialSeq: js.SerialSeq, } }
func testSignFile(t *testing.T, certFile string) ([]byte, error) { s := newTestSigner(t) pem, err := ioutil.ReadFile(certFile) if err != nil { t.Fatal(err) } return s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(testHostName), Request: string(pem)}) }
func gencertMain(args []string, c cli.Config) error { if c.RenewCA { log.Infof("re-generate a CA certificate from CA cert and key") cert, err := initca.RenewFromPEM(c.CAFile, c.CAKeyFile) if err != nil { log.Errorf("%v\n", err) return err } cli.PrintCert(nil, nil, cert) return nil } csrJSONFile, args, err := cli.PopFirstArgument(args) if err != nil { return err } csrJSONFileBytes, err := cli.ReadStdin(csrJSONFile) if err != nil { return err } req := csr.CertificateRequest{ KeyRequest: csr.NewBasicKeyRequest(), } err = json.Unmarshal(csrJSONFileBytes, &req) if err != nil { return err } switch { case c.IsCA: var key, csrPEM, cert []byte if c.CAKeyFile != "" { log.Infof("re-generate a CA certificate from CSR and CA key") cert, csrPEM, err = initca.NewFromPEM(&req, c.CAKeyFile) if err != nil { log.Errorf("%v\n", err) return err } } else { log.Infof("generating a new CA key and certificate from CSR") cert, csrPEM, key, err = initca.New(&req) if err != nil { return err } } cli.PrintCert(key, csrPEM, cert) default: if req.CA != nil { err = errors.New("ca section only permitted in initca") return err } // Remote can be forced on the command line or in the config if c.Remote == "" && c.CFG == nil { if c.CAFile == "" { log.Error("need a CA certificate (provide one with -ca)") return nil } if c.CAKeyFile == "" { log.Error("need a CA key (provide one with -ca-key)") return nil } } var key, csrBytes []byte g := &csr.Generator{Validator: genkey.Validator} csrBytes, key, err = g.ProcessRequest(&req) if err != nil { key = nil return err } s, err := sign.SignerFromConfig(c) if err != nil { return err } var cert []byte req := signer.SignRequest{ Request: string(csrBytes), Hosts: signer.SplitHosts(c.Hostname), Profile: c.Profile, Label: c.Label, } cert, err = s.Sign(req) if err != nil { return err } cli.PrintCert(key, csrBytes, cert) } return nil }
// signerMain is the main CLI of signer functionality. // [TODO: zi] Decide whether to drop the argument list and only use flags to specify all the inputs. func signerMain(args []string, c cli.Config) (err error) { if c.CSRFile == "" { c.CSRFile, args, err = cli.PopFirstArgument(args) if err != nil { return } } var subjectData *signer.Subject if len(args) > 0 { var subjectFile string subjectFile, args, err = cli.PopFirstArgument(args) if err != nil { return } var subjectJSON []byte subjectJSON, err = ioutil.ReadFile(subjectFile) if err != nil { return } subjectData = new(signer.Subject) err = json.Unmarshal(subjectJSON, subjectData) if err != nil { return } } csr, err := cli.ReadStdin(c.CSRFile) if err != nil { return } // Remote can be forced on the command line or in the config if c.Remote == "" && c.CFG == nil { if c.CAFile == "" { log.Error("need CA certificate (provide one with -ca)") return } if c.CAKeyFile == "" { log.Error("need CA key (provide one with -ca-key)") return } } s, err := SignerFromConfig(c) if err != nil { return } req := signer.SignRequest{ Hosts: signer.SplitHosts(c.Hostname), Request: string(csr), Subject: subjectData, Profile: c.Profile, Label: c.Label, } cert, err := s.Sign(req) if err != nil { return } cli.PrintCert(nil, csr, cert) return }
func gencertMain(args []string, c cli.Config) error { if c.RenewCA { log.Infof("re-generate a CA certificate from CA cert and key") cert, err := initca.RenewFromPEM(c.CAFile, c.CAKeyFile) if err != nil { log.Errorf("%v\n", err) return err } cli.PrintCert(nil, nil, cert) return nil } csrJSONFile, args, err := cli.PopFirstArgument(args) if err != nil { return err } csrJSONFileBytes, err := cli.ReadStdin(csrJSONFile) if err != nil { return err } req := csr.CertificateRequest{ KeyRequest: csr.NewBasicKeyRequest(), } err = json.Unmarshal(csrJSONFileBytes, &req) if err != nil { return err } if c.CNOverride != "" { req.CN = c.CNOverride } switch { case c.IsCA: var key, csrPEM, cert []byte if c.CAKeyFile != "" { log.Infof("re-generate a CA certificate from CSR and CA key") cert, csrPEM, err = initca.NewFromPEM(&req, c.CAKeyFile) if err != nil { log.Errorf("%v\n", err) return err } } else { log.Infof("generating a new CA key and certificate from CSR") cert, csrPEM, key, err = initca.New(&req) if err != nil { return err } } cli.PrintCert(key, csrPEM, cert) default: if req.CA != nil { err = errors.New("ca section only permitted in initca") return err } if c.Hostname != "" { req.Hosts = signer.SplitHosts(c.Hostname) } // Remote can be forced on the command line or in the config if c.Remote == "" && c.CFG == nil { if c.CAFile == "" { log.Error("need a CA certificate (provide one with -ca)") return nil } if c.CAKeyFile == "" { log.Error("need a CA key (provide one with -ca-key)") return nil } } var key, csrBytes []byte g := &csr.Generator{Validator: genkey.Validator} csrBytes, key, err = g.ProcessRequest(&req) if err != nil { key = nil return err } s, err := sign.SignerFromConfig(c) if err != nil { return err } var cert []byte signReq := signer.SignRequest{ Request: string(csrBytes), Hosts: signer.SplitHosts(c.Hostname), Profile: c.Profile, Label: c.Label, } if c.CRL != "" { signReq.CRLOverride = c.CRL } cert, err = s.Sign(signReq) if err != nil { return err } // This follows the Baseline Requirements for the Issuance and // Management of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser // Forum (https://cabforum.org). Specifically, section 10.2.3 ("Information // Requirements"), states: // // "Applicant information MUST include, but not be limited to, at least one // Fully-Qualified Domain Name or IP address to be included in the Certificate’s // SubjectAltName extension." if len(signReq.Hosts) == 0 && len(req.Hosts) == 0 { log.Warning(generator.CSRNoHostMessage) } cli.PrintCert(key, csrBytes, cert) } return nil }
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 TestInitCA(t *testing.T) { var req *csr.CertificateRequest hostname := "cloudflare.com" for _, param := range validKeyParams { 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: &csr.KeyRequest{ Algo: param.keyAlgo, Size: param.keyLen, }, } 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.keyLen { t.Fatal("Cert key length mismatch.") } if key.(*rsa.PrivateKey).N.BitLen() != param.keyLen { t.Fatal("Private key length mismatch.") } case "ecdsa": if cert.PublicKey.(*ecdsa.PublicKey).Curve.Params().BitSize != param.keyLen { t.Fatal("Cert key length mismatch.") } if key.(*ecdsa.PrivateKey).Curve.Params().BitSize != param.keyLen { t.Fatal("Private key length mismatch.") } } // Start a signer var CAPolicy = &config.Signing{ Default: &config.SigningProfile{ Usage: []string{"cert sign", "crl sign"}, ExpiryString: "300s", Expiry: 300 * time.Second, CA: true, }, } 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) } } } }
// Handle responds to requests for the CA to generate a new private // key and certificate on behalf of the client. The format for these // requests is documented in the API documentation. func (cg *CertGeneratorHandler) Handle(w http.ResponseWriter, r *http.Request) error { log.Info("request for CSR") req := new(genSignRequest) body, err := ioutil.ReadAll(r.Body) if err != nil { log.Warningf("failed to read request body: %v", err) return errors.NewBadRequest(err) } err = json.Unmarshal(body, req) if err != nil { log.Warningf("failed to unmarshal request: %v", err) return errors.NewBadRequest(err) } if req.Request == nil { log.Warning("empty request received") return errors.NewBadRequestString("missing request section") } if req.Request.CA != nil { log.Warningf("request received with CA section") return errors.NewBadRequestString("ca section only permitted in initca") } csr, key, err := cg.generator.ProcessRequest(req.Request) if err != nil { log.Warningf("failed to process CSR: %v", err) // The validator returns a *cfssl/errors.HttpError return err } // This API does not override the subject because it was already added to the CSR signReq := signer.SignRequest{ Hosts: signer.SplitHosts(req.Hostname), Request: string(csr), Profile: req.Profile, Label: req.Label, } certBytes, err := cg.signer.Sign(signReq) if err != nil { log.Warningf("failed to sign request: %v", err) return err } reqSum, err := computeSum(csr) if err != nil { return errors.NewBadRequest(err) } certSum, err := computeSum(certBytes) if err != nil { return errors.NewBadRequest(err) } result := map[string]interface{}{ "private_key": string(key), "certificate_request": string(csr), "certificate": string(certBytes), "sums": map[string]Sum{ "certificate_request": reqSum, "certificate": certSum, }, } return api.SendResponse(w, result) }