386 lines
9.0 KiB
Go
386 lines
9.0 KiB
Go
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")
|
|
}
|
|
}
|