vendor
This commit is contained in:
432
vendor/github.com/cloudflare/cfssl/csr/csr.go
generated
vendored
Normal file
432
vendor/github.com/cloudflare/cfssl/csr/csr.go
generated
vendored
Normal file
@ -0,0 +1,432 @@
|
||||
// Package csr implements certificate requests for CFSSL.
|
||||
package csr
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"net"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
cferr "github.com/cloudflare/cfssl/errors"
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/cloudflare/cfssl/log"
|
||||
)
|
||||
|
||||
const (
|
||||
curveP256 = 256
|
||||
curveP384 = 384
|
||||
curveP521 = 521
|
||||
)
|
||||
|
||||
// A Name contains the SubjectInfo fields.
|
||||
type Name struct {
|
||||
C string // Country
|
||||
ST string // State
|
||||
L string // Locality
|
||||
O string // OrganisationName
|
||||
OU string // OrganisationalUnitName
|
||||
SerialNumber string
|
||||
}
|
||||
|
||||
// A KeyRequest is a generic request for a new key.
|
||||
type KeyRequest interface {
|
||||
Algo() string
|
||||
Size() int
|
||||
Generate() (crypto.PrivateKey, error)
|
||||
SigAlgo() x509.SignatureAlgorithm
|
||||
}
|
||||
|
||||
// A BasicKeyRequest contains the algorithm and key size for a new private key.
|
||||
type BasicKeyRequest struct {
|
||||
A string `json:"algo" yaml:"algo"`
|
||||
S int `json:"size" yaml:"size"`
|
||||
}
|
||||
|
||||
// NewBasicKeyRequest returns a default BasicKeyRequest.
|
||||
func NewBasicKeyRequest() *BasicKeyRequest {
|
||||
return &BasicKeyRequest{"ecdsa", curveP256}
|
||||
}
|
||||
|
||||
// Algo returns the requested key algorithm represented as a string.
|
||||
func (kr *BasicKeyRequest) Algo() string {
|
||||
return kr.A
|
||||
}
|
||||
|
||||
// Size returns the requested key size.
|
||||
func (kr *BasicKeyRequest) Size() int {
|
||||
return kr.S
|
||||
}
|
||||
|
||||
// Generate generates a key as specified in the request. Currently,
|
||||
// only ECDSA and RSA are supported.
|
||||
func (kr *BasicKeyRequest) Generate() (crypto.PrivateKey, error) {
|
||||
log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo(), kr.Size())
|
||||
switch kr.Algo() {
|
||||
case "rsa":
|
||||
if kr.Size() < 2048 {
|
||||
return nil, errors.New("RSA key is too weak")
|
||||
}
|
||||
if kr.Size() > 8192 {
|
||||
return nil, errors.New("RSA key size too large")
|
||||
}
|
||||
return rsa.GenerateKey(rand.Reader, kr.Size())
|
||||
case "ecdsa":
|
||||
var curve elliptic.Curve
|
||||
switch kr.Size() {
|
||||
case curveP256:
|
||||
curve = elliptic.P256()
|
||||
case curveP384:
|
||||
curve = elliptic.P384()
|
||||
case curveP521:
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return nil, errors.New("invalid curve")
|
||||
}
|
||||
return ecdsa.GenerateKey(curve, rand.Reader)
|
||||
default:
|
||||
return nil, errors.New("invalid algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
// SigAlgo returns an appropriate X.509 signature algorithm given the
|
||||
// key request's type and size.
|
||||
func (kr *BasicKeyRequest) SigAlgo() x509.SignatureAlgorithm {
|
||||
switch kr.Algo() {
|
||||
case "rsa":
|
||||
switch {
|
||||
case kr.Size() >= 4096:
|
||||
return x509.SHA512WithRSA
|
||||
case kr.Size() >= 3072:
|
||||
return x509.SHA384WithRSA
|
||||
case kr.Size() >= 2048:
|
||||
return x509.SHA256WithRSA
|
||||
default:
|
||||
return x509.SHA1WithRSA
|
||||
}
|
||||
case "ecdsa":
|
||||
switch kr.Size() {
|
||||
case curveP521:
|
||||
return x509.ECDSAWithSHA512
|
||||
case curveP384:
|
||||
return x509.ECDSAWithSHA384
|
||||
case curveP256:
|
||||
return x509.ECDSAWithSHA256
|
||||
default:
|
||||
return x509.ECDSAWithSHA1
|
||||
}
|
||||
default:
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
// CAConfig is a section used in the requests initialising a new CA.
|
||||
type CAConfig struct {
|
||||
PathLength int `json:"pathlen" yaml:"pathlen"`
|
||||
PathLenZero bool `json:"pathlenzero" yaml:"pathlenzero"`
|
||||
Expiry string `json:"expiry" yaml:"expiry"`
|
||||
Backdate string `json:"backdate" yaml:"backdate"`
|
||||
}
|
||||
|
||||
// A CertificateRequest encapsulates the API interface to the
|
||||
// certificate request functionality.
|
||||
type CertificateRequest struct {
|
||||
CN string
|
||||
Names []Name `json:"names" yaml:"names"`
|
||||
Hosts []string `json:"hosts" yaml:"hosts"`
|
||||
KeyRequest KeyRequest `json:"key,omitempty" yaml:"key,omitempty"`
|
||||
CA *CAConfig `json:"ca,omitempty" yaml:"ca,omitempty"`
|
||||
SerialNumber string `json:"serialnumber,omitempty" yaml:"serialnumber,omitempty"`
|
||||
}
|
||||
|
||||
// New returns a new, empty CertificateRequest with a
|
||||
// BasicKeyRequest.
|
||||
func New() *CertificateRequest {
|
||||
return &CertificateRequest{
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
}
|
||||
|
||||
// appendIf appends to a if s is not an empty string.
|
||||
func appendIf(s string, a *[]string) {
|
||||
if s != "" {
|
||||
*a = append(*a, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the PKIX name for the request.
|
||||
func (cr *CertificateRequest) Name() pkix.Name {
|
||||
var name pkix.Name
|
||||
name.CommonName = cr.CN
|
||||
|
||||
for _, n := range cr.Names {
|
||||
appendIf(n.C, &name.Country)
|
||||
appendIf(n.ST, &name.Province)
|
||||
appendIf(n.L, &name.Locality)
|
||||
appendIf(n.O, &name.Organization)
|
||||
appendIf(n.OU, &name.OrganizationalUnit)
|
||||
}
|
||||
name.SerialNumber = cr.SerialNumber
|
||||
return name
|
||||
}
|
||||
|
||||
// BasicConstraints CSR information RFC 5280, 4.2.1.9
|
||||
type BasicConstraints struct {
|
||||
IsCA bool `asn1:"optional"`
|
||||
MaxPathLen int `asn1:"optional,default:-1"`
|
||||
}
|
||||
|
||||
// ParseRequest takes a certificate request and generates a key and
|
||||
// CSR from it. It does no validation -- caveat emptor. It will,
|
||||
// however, fail if the key request is not valid (i.e., an unsupported
|
||||
// curve or RSA key size). The lack of validation was specifically
|
||||
// chosen to allow the end user to define a policy and validate the
|
||||
// request appropriately before calling this function.
|
||||
func ParseRequest(req *CertificateRequest) (csr, key []byte, err error) {
|
||||
log.Info("received CSR")
|
||||
if req.KeyRequest == nil {
|
||||
req.KeyRequest = NewBasicKeyRequest()
|
||||
}
|
||||
|
||||
log.Infof("generating key: %s-%d", req.KeyRequest.Algo(), req.KeyRequest.Size())
|
||||
priv, err := req.KeyRequest.Generate()
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.PrivateKeyError, cferr.GenerationFailed, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch priv := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
key = x509.MarshalPKCS1PrivateKey(priv)
|
||||
block := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: key,
|
||||
}
|
||||
key = pem.EncodeToMemory(&block)
|
||||
case *ecdsa.PrivateKey:
|
||||
key, err = x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.PrivateKeyError, cferr.Unknown, err)
|
||||
return
|
||||
}
|
||||
block := pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: key,
|
||||
}
|
||||
key = pem.EncodeToMemory(&block)
|
||||
default:
|
||||
panic("Generate should have failed to produce a valid key.")
|
||||
}
|
||||
|
||||
csr, err = Generate(priv.(crypto.Signer), req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to generate a CSR: %v", err)
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.BadRequest, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExtractCertificateRequest extracts a CertificateRequest from
|
||||
// x509.Certificate. It is aimed to used for generating a new certificate
|
||||
// from an existing certificate. For a root certificate, the CA expiry
|
||||
// length is calculated as the duration between cert.NotAfter and cert.NotBefore.
|
||||
func ExtractCertificateRequest(cert *x509.Certificate) *CertificateRequest {
|
||||
req := New()
|
||||
req.CN = cert.Subject.CommonName
|
||||
req.Names = getNames(cert.Subject)
|
||||
req.Hosts = getHosts(cert)
|
||||
req.SerialNumber = cert.Subject.SerialNumber
|
||||
|
||||
if cert.IsCA {
|
||||
req.CA = new(CAConfig)
|
||||
// CA expiry length is calculated based on the input cert
|
||||
// issue date and expiry date.
|
||||
req.CA.Expiry = cert.NotAfter.Sub(cert.NotBefore).String()
|
||||
req.CA.PathLength = cert.MaxPathLen
|
||||
req.CA.PathLenZero = cert.MaxPathLenZero
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func getHosts(cert *x509.Certificate) []string {
|
||||
var hosts []string
|
||||
for _, ip := range cert.IPAddresses {
|
||||
hosts = append(hosts, ip.String())
|
||||
}
|
||||
for _, dns := range cert.DNSNames {
|
||||
hosts = append(hosts, dns)
|
||||
}
|
||||
for _, email := range cert.EmailAddresses {
|
||||
hosts = append(hosts, email)
|
||||
}
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
// getNames returns an array of Names from the certificate
|
||||
// It onnly cares about Country, Organization, OrganizationalUnit, Locality, Province
|
||||
func getNames(sub pkix.Name) []Name {
|
||||
// anonymous func for finding the max of a list of interger
|
||||
max := func(v1 int, vn ...int) (max int) {
|
||||
max = v1
|
||||
for i := 0; i < len(vn); i++ {
|
||||
if vn[i] > max {
|
||||
max = vn[i]
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
nc := len(sub.Country)
|
||||
norg := len(sub.Organization)
|
||||
nou := len(sub.OrganizationalUnit)
|
||||
nl := len(sub.Locality)
|
||||
np := len(sub.Province)
|
||||
|
||||
n := max(nc, norg, nou, nl, np)
|
||||
|
||||
names := make([]Name, n)
|
||||
for i := range names {
|
||||
if i < nc {
|
||||
names[i].C = sub.Country[i]
|
||||
}
|
||||
if i < norg {
|
||||
names[i].O = sub.Organization[i]
|
||||
}
|
||||
if i < nou {
|
||||
names[i].OU = sub.OrganizationalUnit[i]
|
||||
}
|
||||
if i < nl {
|
||||
names[i].L = sub.Locality[i]
|
||||
}
|
||||
if i < np {
|
||||
names[i].ST = sub.Province[i]
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// A Generator is responsible for validating certificate requests.
|
||||
type Generator struct {
|
||||
Validator func(*CertificateRequest) error
|
||||
}
|
||||
|
||||
// ProcessRequest validates and processes the incoming request. It is
|
||||
// a wrapper around a validator and the ParseRequest function.
|
||||
func (g *Generator) ProcessRequest(req *CertificateRequest) (csr, key []byte, err error) {
|
||||
|
||||
log.Info("generate received request")
|
||||
err = g.Validator(req)
|
||||
if err != nil {
|
||||
log.Warningf("invalid request: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
csr, key, err = ParseRequest(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsNameEmpty returns true if the name has no identifying information in it.
|
||||
func IsNameEmpty(n Name) bool {
|
||||
empty := func(s string) bool { return strings.TrimSpace(s) == "" }
|
||||
|
||||
if empty(n.C) && empty(n.ST) && empty(n.L) && empty(n.O) && empty(n.OU) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Regenerate uses the provided CSR as a template for signing a new
|
||||
// CSR using priv.
|
||||
func Regenerate(priv crypto.Signer, csr []byte) ([]byte, error) {
|
||||
req, extra, err := helpers.ParseCSR(csr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(extra) > 0 {
|
||||
return nil, errors.New("csr: trailing data in certificate request")
|
||||
}
|
||||
|
||||
return x509.CreateCertificateRequest(rand.Reader, req, priv)
|
||||
}
|
||||
|
||||
// Generate creates a new CSR from a CertificateRequest structure and
|
||||
// an existing key. The KeyRequest field is ignored.
|
||||
func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err error) {
|
||||
sigAlgo := helpers.SignerAlgo(priv)
|
||||
if sigAlgo == x509.UnknownSignatureAlgorithm {
|
||||
return nil, cferr.New(cferr.PrivateKeyError, cferr.Unavailable)
|
||||
}
|
||||
|
||||
var tpl = x509.CertificateRequest{
|
||||
Subject: req.Name(),
|
||||
SignatureAlgorithm: sigAlgo,
|
||||
}
|
||||
|
||||
for i := range req.Hosts {
|
||||
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
||||
} else if email, err := mail.ParseAddress(req.Hosts[i]); err == nil && email != nil {
|
||||
tpl.EmailAddresses = append(tpl.EmailAddresses, email.Address)
|
||||
} else {
|
||||
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
||||
}
|
||||
}
|
||||
|
||||
if req.CA != nil {
|
||||
err = appendCAInfoToCSR(req.CA, &tpl)
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.GenerationFailed, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
csr, err = x509.CreateCertificateRequest(rand.Reader, &tpl, priv)
|
||||
if err != nil {
|
||||
log.Errorf("failed to generate a CSR: %v", err)
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.BadRequest, err)
|
||||
return
|
||||
}
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr,
|
||||
}
|
||||
|
||||
log.Info("encoded CSR")
|
||||
csr = pem.EncodeToMemory(&block)
|
||||
return
|
||||
}
|
||||
|
||||
// appendCAInfoToCSR appends CAConfig BasicConstraint extension to a CSR
|
||||
func appendCAInfoToCSR(reqConf *CAConfig, csr *x509.CertificateRequest) error {
|
||||
pathlen := reqConf.PathLength
|
||||
if pathlen == 0 && !reqConf.PathLenZero {
|
||||
pathlen = -1
|
||||
}
|
||||
val, err := asn1.Marshal(BasicConstraints{true, pathlen})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
csr.ExtraExtensions = []pkix.Extension{
|
||||
{
|
||||
Id: asn1.ObjectIdentifier{2, 5, 29, 19},
|
||||
Value: val,
|
||||
Critical: true,
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
744
vendor/github.com/cloudflare/cfssl/csr/csr_test.go
generated
vendored
Normal file
744
vendor/github.com/cloudflare/cfssl/csr/csr_test.go
generated
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
package csr
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cfssl/errors"
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
)
|
||||
|
||||
//TestNew validate the CertificateRequest created to return with a BasicKeyRequest
|
||||
//in KeyRequest field
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
|
||||
if cr := New(); cr.KeyRequest == nil {
|
||||
t.Fatalf("Should create a new, empty certificate request with BasicKeyRequest")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBasicKeyRequest ensures that key generation returns the same type of
|
||||
// key specified in the BasicKeyRequest.
|
||||
func TestBasicKeyRequest(t *testing.T) {
|
||||
kr := NewBasicKeyRequest()
|
||||
priv, err := kr.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
switch priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if kr.Algo() != "rsa" {
|
||||
t.Fatal("RSA key generated, but expected", kr.Algo())
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
if kr.Algo() != "ecdsa" {
|
||||
t.Fatal("ECDSA key generated, but expected", kr.Algo())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPKIXName validates building a pkix.Name structure from a
|
||||
// CertificateRequest.
|
||||
func TestPKIXName(t *testing.T) {
|
||||
var cr = &CertificateRequest{
|
||||
CN: "Test Common Name",
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare, Inc.",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
{
|
||||
C: "GB",
|
||||
ST: "London",
|
||||
L: "London",
|
||||
O: "CloudFlare, Inc",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
|
||||
name := cr.Name()
|
||||
if len(name.Country) != 2 {
|
||||
t.Fatal("Expected two countries in SubjInfo.")
|
||||
} else if len(name.Province) != 2 {
|
||||
t.Fatal("Expected two states in SubjInfo.")
|
||||
} else if len(name.Locality) != 2 {
|
||||
t.Fatal("Expected two localities in SubjInfo.")
|
||||
} else if len(name.Country) != 2 {
|
||||
t.Fatal("Expected two countries in SubjInfo.")
|
||||
} else if len(name.Organization) != 2 {
|
||||
t.Fatal("Expected two organization in SubjInfo.")
|
||||
} else if len(name.OrganizationalUnit) != 2 {
|
||||
t.Fatal("Expected two organizational units in SubjInfo.")
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseRequest ensures that a valid certificate request does not
|
||||
// error.
|
||||
func TestParseRequest(t *testing.T) {
|
||||
var cr = &CertificateRequest{
|
||||
CN: "Test Common Name",
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare, Inc.",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
{
|
||||
C: "GB",
|
||||
ST: "London",
|
||||
L: "London",
|
||||
O: "CloudFlare, Inc",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1", "jdoe@example.com"},
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
|
||||
_, _, err := ParseRequest(cr)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseRequestCA ensures that a valid CA certificate request does not
|
||||
// error and the resulting CSR includes the BasicConstraint extension
|
||||
func TestParseRequestCA(t *testing.T) {
|
||||
var cr = &CertificateRequest{
|
||||
CN: "Test Common Name",
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare, Inc.",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
{
|
||||
C: "GB",
|
||||
ST: "London",
|
||||
L: "London",
|
||||
O: "CloudFlare, Inc",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CA: &CAConfig{
|
||||
PathLength: 0,
|
||||
PathLenZero: true,
|
||||
},
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
|
||||
csrBytes, _, err := ParseRequest(cr)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(csrBytes)
|
||||
if block == nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE REQUEST" {
|
||||
t.Fatalf("Incorrect block type: %s", block.Type)
|
||||
}
|
||||
|
||||
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, ext := range csr.Extensions {
|
||||
if ext.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 19}) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("CSR did not include BasicConstraint Extension")
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseRequestCANoPathlen ensures that a valid CA certificate request
|
||||
// with an unspecified pathlen does not error and the resulting CSR includes
|
||||
// the BasicConstraint extension
|
||||
func TestParseRequestCANoPathlen(t *testing.T) {
|
||||
var cr = &CertificateRequest{
|
||||
CN: "Test Common Name",
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare, Inc.",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
{
|
||||
C: "GB",
|
||||
ST: "London",
|
||||
L: "London",
|
||||
O: "CloudFlare, Inc",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CA: &CAConfig{
|
||||
PathLength: 0,
|
||||
PathLenZero: false,
|
||||
},
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
|
||||
csrBytes, _, err := ParseRequest(cr)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(csrBytes)
|
||||
if block == nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE REQUEST" {
|
||||
t.Fatalf("Incorrect block type: %s", block.Type)
|
||||
}
|
||||
|
||||
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, ext := range csr.Extensions {
|
||||
if ext.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 19}) {
|
||||
bc := &BasicConstraints{}
|
||||
asn1.Unmarshal(ext.Value, bc)
|
||||
if bc.IsCA == true && bc.MaxPathLen == -1 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("CSR did not include BasicConstraint Extension")
|
||||
}
|
||||
}
|
||||
|
||||
func whichCurve(sz int) elliptic.Curve {
|
||||
switch sz {
|
||||
case 256:
|
||||
return elliptic.P256()
|
||||
case 384:
|
||||
return elliptic.P384()
|
||||
case 521:
|
||||
return elliptic.P521()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestECGeneration ensures that the proper curve is used depending on
|
||||
// the bit size specified in a key request and that an appropriate
|
||||
// signature algorithm is returned.
|
||||
func TestECGeneration(t *testing.T) {
|
||||
var eckey *ecdsa.PrivateKey
|
||||
|
||||
for _, sz := range []int{256, 384, 521} {
|
||||
kr := &BasicKeyRequest{"ecdsa", sz}
|
||||
priv, err := kr.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
eckey = priv.(*ecdsa.PrivateKey)
|
||||
if eckey.Curve != whichCurve(sz) {
|
||||
t.Fatal("Generated key has wrong curve.")
|
||||
}
|
||||
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
|
||||
t.Fatal("Invalid signature algorithm!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAKeyGeneration(t *testing.T) {
|
||||
var rsakey *rsa.PrivateKey
|
||||
|
||||
for _, sz := range []int{2048, 3072, 4096} {
|
||||
kr := &BasicKeyRequest{"rsa", sz}
|
||||
priv, err := kr.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
rsakey = priv.(*rsa.PrivateKey)
|
||||
if rsakey.PublicKey.N.BitLen() != kr.Size() {
|
||||
t.Fatal("Generated key has wrong size.")
|
||||
}
|
||||
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
|
||||
t.Fatal("Invalid signature algorithm!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBadBasicKeyRequest ensures that generating a key from a BasicKeyRequest
|
||||
// fails with an invalid algorithm, or an invalid RSA or ECDSA key
|
||||
// size. An invalid ECDSA key size is any size other than 256, 384, or
|
||||
// 521; an invalid RSA key size is any size less than 2048 bits.
|
||||
func TestBadBasicKeyRequest(t *testing.T) {
|
||||
kr := &BasicKeyRequest{"yolocrypto", 1024}
|
||||
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid algorithm")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.UnknownSignatureAlgorithm {
|
||||
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
|
||||
}
|
||||
|
||||
kr.A = "ecdsa"
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid key size")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.ECDSAWithSHA1 {
|
||||
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
|
||||
}
|
||||
|
||||
kr.A = "rsa"
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid key size")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.SHA1WithRSA {
|
||||
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
|
||||
}
|
||||
|
||||
kr = &BasicKeyRequest{"tobig", 9216}
|
||||
|
||||
kr.A = "rsa"
|
||||
if _, err := kr.Generate(); err == nil {
|
||||
t.Fatal("Key generation should fail with invalid key size")
|
||||
} else if sa := kr.SigAlgo(); sa != x509.SHA512WithRSA {
|
||||
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultBasicKeyRequest makes sure that certificate requests without
|
||||
// explicit key requests fall back to the default key request.
|
||||
func TestDefaultBasicKeyRequest(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "jdoe@example.com"},
|
||||
}
|
||||
_, priv, err := ParseRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
// If the default key type changes, this will need to be changed.
|
||||
block, _ := pem.Decode(priv)
|
||||
if block == nil {
|
||||
t.Fatal("Bad private key was generated!")
|
||||
}
|
||||
|
||||
DefaultKeyRequest := NewBasicKeyRequest()
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
if DefaultKeyRequest.Algo() != "rsa" {
|
||||
t.Fatal("Invalid default key request.")
|
||||
}
|
||||
case "EC PRIVATE KEY":
|
||||
if DefaultKeyRequest.Algo() != "ecdsa" {
|
||||
t.Fatal("Invalid default key request.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRSACertRequest validates parsing a certificate request with an
|
||||
// RSA key.
|
||||
func TestRSACertRequest(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "jdoe@example.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 2048},
|
||||
}
|
||||
_, _, err := ParseRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBadCertRequest checks for failure conditions of ParseRequest.
|
||||
func TestBadCertRequest(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &BasicKeyRequest{"yolo-crypto", 2048},
|
||||
}
|
||||
_, _, err := ParseRequest(req)
|
||||
if err == nil {
|
||||
t.Fatal("ParseRequest should fail with a bad key algorithm.")
|
||||
}
|
||||
}
|
||||
|
||||
// testValidator is a stripped-down validator that checks to make sure
|
||||
// the request has a common name. It should mimic some of the
|
||||
// functionality expected in an actual validator.
|
||||
func testValidator(req *CertificateRequest) error {
|
||||
if req.CN == "" {
|
||||
return errors.NewBadRequestMissingParameter("CN")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestGenerator ensures that a valid request is processed properly
|
||||
// and returns a certificate request and key.
|
||||
func TestGenerator(t *testing.T) {
|
||||
g := &Generator{testValidator}
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1", "jdoe@example.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 2048},
|
||||
}
|
||||
|
||||
csrBytes, _, err := g.ProcessRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(csrBytes))
|
||||
if block == nil {
|
||||
t.Fatalf("bad CSR in PEM")
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE REQUEST" {
|
||||
t.Fatalf("bad CSR in PEM")
|
||||
}
|
||||
|
||||
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(csr.DNSNames) != 2 {
|
||||
t.Fatal("SAN parsing error")
|
||||
}
|
||||
|
||||
if len(csr.IPAddresses) != 1 {
|
||||
t.Fatal("SAN parsing error")
|
||||
}
|
||||
|
||||
if len(csr.EmailAddresses) != 1 {
|
||||
t.Fatal("SAN parsing error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestBadGenerator ensures that a request that fails the validator is
|
||||
// not processed.
|
||||
func TestBadGenerator(t *testing.T) {
|
||||
g := &Generator{testValidator}
|
||||
missingCN := &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
// Missing CN
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 2048},
|
||||
}
|
||||
|
||||
_, _, err := g.ProcessRequest(missingCN)
|
||||
if err == nil {
|
||||
t.Fatalf("Request should have failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeakCSR(t *testing.T) {
|
||||
weakKey := &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "jdoe@example.com"},
|
||||
KeyRequest: &BasicKeyRequest{"rsa", 1024},
|
||||
}
|
||||
g := &Generator{testValidator}
|
||||
|
||||
_, _, err := g.ProcessRequest(weakKey)
|
||||
if err == nil {
|
||||
t.Fatalf("Request should have failed.")
|
||||
}
|
||||
}
|
||||
|
||||
var testEmpty = []struct {
|
||||
name Name
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
Name{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
Name{C: "OK"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
Name{ST: "OK"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
Name{L: "OK"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
Name{O: "OK"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
Name{OU: "OK"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestIsNameEmpty(t *testing.T) {
|
||||
for i, c := range testEmpty {
|
||||
if IsNameEmpty(c.name) != c.ok {
|
||||
t.Fatalf("%d: expected IsNameEmpty to return %v, but have %v", i, c.ok, !c.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1", "jdoe@example.com"},
|
||||
KeyRequest: &BasicKeyRequest{"ecdsa", 256},
|
||||
}
|
||||
|
||||
key, err := req.KeyRequest.Generate()
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
priv, ok := key.(crypto.Signer)
|
||||
if !ok {
|
||||
t.Fatal("Private key is not a signer.")
|
||||
}
|
||||
|
||||
csrPEM, err := Generate(priv, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
csr, _, err := helpers.ParseCSR(csrPEM)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if len(csr.DNSNames) != 2 {
|
||||
t.Fatal("SAN parsing error")
|
||||
}
|
||||
|
||||
if len(csr.IPAddresses) != 1 {
|
||||
t.Fatal("SAN parsing error")
|
||||
}
|
||||
|
||||
if len(csr.EmailAddresses) != 1 {
|
||||
t.Fatal("SAN parsing error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestReGenerate ensures Regenerate() is abel to use the provided CSR as a template for signing a new
|
||||
// CSR using priv.
|
||||
func TestReGenerate(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &BasicKeyRequest{"ecdsa", 256},
|
||||
}
|
||||
|
||||
_, key, err := ParseRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
priv, err := helpers.ParsePrivateKeyPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
csr, err := Generate(priv, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if _, _, err = helpers.ParseCSR(csr); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
_, err = Regenerate(priv, csr)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBadReGenerator ensures that a request that fails the ParseCSR is
|
||||
// not processed.
|
||||
func TestBadReGenerate(t *testing.T) {
|
||||
var req = &CertificateRequest{
|
||||
Names: []Name{
|
||||
{
|
||||
C: "US",
|
||||
ST: "California",
|
||||
L: "San Francisco",
|
||||
O: "CloudFlare",
|
||||
OU: "Systems Engineering",
|
||||
},
|
||||
},
|
||||
CN: "cloudflare.com",
|
||||
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "192.168.0.1"},
|
||||
KeyRequest: &BasicKeyRequest{"ecdsa", 256},
|
||||
}
|
||||
|
||||
_, key, err := ParseRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
priv, err := helpers.ParsePrivateKeyPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
csr, err := Generate(priv, req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Headers: map[string]string{
|
||||
"Location": "UCSD",
|
||||
},
|
||||
Bytes: csr,
|
||||
}
|
||||
|
||||
csr = pem.EncodeToMemory(&block)
|
||||
|
||||
_, err = Regenerate(priv, csr)
|
||||
if err == nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var testECDSACertificateFile = "testdata/test-ecdsa-ca.pem"
|
||||
|
||||
func TestExtractCertificateRequest(t *testing.T) {
|
||||
certPEM, err := ioutil.ReadFile(testECDSACertificateFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// must parse ok
|
||||
cert, err := helpers.ParseCertificatePEM(certPEM)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := ExtractCertificateRequest(cert)
|
||||
|
||||
if req.CN != "" {
|
||||
t.Fatal("Bad Certificate Request!")
|
||||
}
|
||||
|
||||
if len(req.Names) != 1 {
|
||||
t.Fatal("Bad Certificate Request!")
|
||||
}
|
||||
|
||||
name := req.Names[0]
|
||||
if name.C != "US" || name.ST != "California" || name.O != "CloudFlare, Inc." ||
|
||||
name.OU != "Test Certificate Authority" || name.L != "San Francisco" {
|
||||
t.Fatal("Bad Certificate Request!")
|
||||
}
|
||||
|
||||
if req.CA == nil || req.CA.PathLength != 2 {
|
||||
t.Fatal("Bad Certificate Request!")
|
||||
}
|
||||
}
|
15
vendor/github.com/cloudflare/cfssl/csr/testdata/test-ecdsa-ca.pem
generated
vendored
Normal file
15
vendor/github.com/cloudflare/cfssl/csr/testdata/test-ecdsa-ca.pem
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICUDCCAfagAwIBAgIIec5PjdpJcNYwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMC
|
||||
VVMxGTAXBgNVBAoTEENsb3VkRmxhcmUsIEluYy4xIzAhBgNVBAsTGlRlc3QgQ2Vy
|
||||
dGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYD
|
||||
VQQIEwpDYWxpZm9ybmlhMB4XDTE1MTAwODIzMDEwMFoXDTE1MTAwODIzMDYwMFow
|
||||
ejELMAkGA1UEBhMCVVMxGTAXBgNVBAoTEENsb3VkRmxhcmUsIEluYy4xIzAhBgNV
|
||||
BAsTGlRlc3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRYwFAYDVQQHEw1TYW4gRnJh
|
||||
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
||||
AQcDQgAEoCV+bVOLTJMy38j50sc3vE5k41GMRgriFJt0g0OVX8yaOZ93CZTI7Lzf
|
||||
GbMU+KqWTgOwGhrPvpusep3fjw+dAaNmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1Ud
|
||||
EwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFDpLhSKBN3njfb6cXQCdRLzCZt0ZMB8G
|
||||
A1UdIwQYMBaAFDpLhSKBN3njfb6cXQCdRLzCZt0ZMAoGCCqGSM49BAMCA0gAMEUC
|
||||
IFU3BmzntGGeXZu2qWZx249nYn37S0AkCnQ3rUtI31bdAiEAsPICnZ+GB8yCN26N
|
||||
OL+N8dHvXiOvZ9/Vl488pyWOccY=
|
||||
-----END CERTIFICATE-----
|
Reference in New Issue
Block a user