162 lines
5.9 KiB
Go
162 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"io"
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
func TestGenerateCRLTimeBounds(t *testing.T) {
|
|
_, err := generateCRL(nil, nil, time.Now().Add(time.Hour), time.Now(), 1, nil)
|
|
test.AssertError(t, err, "generateCRL did not fail")
|
|
test.AssertEquals(t, err.Error(), "thisUpdate must be before nextUpdate")
|
|
|
|
_, err = generateCRL(nil, &x509.Certificate{
|
|
NotBefore: time.Now().Add(time.Hour),
|
|
NotAfter: time.Now(),
|
|
}, time.Now(), time.Now(), 1, nil)
|
|
test.AssertError(t, err, "generateCRL did not fail")
|
|
test.AssertEquals(t, err.Error(), "thisUpdate is before issuing certificate's notBefore")
|
|
|
|
_, err = generateCRL(nil, &x509.Certificate{
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Hour * 2),
|
|
}, time.Now().Add(time.Hour), time.Now().Add(time.Hour*3), 1, nil)
|
|
test.AssertError(t, err, "generateCRL did not fail")
|
|
test.AssertEquals(t, err.Error(), "nextUpdate is after issuing certificate's notAfter")
|
|
|
|
_, err = generateCRL(nil, &x509.Certificate{
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Hour * 24 * 370),
|
|
}, time.Now(), time.Now().Add(time.Hour*24*366), 1, nil)
|
|
test.AssertError(t, err, "generateCRL did not fail")
|
|
test.AssertEquals(t, err.Error(), "nextUpdate must be less than 12 months after thisUpdate")
|
|
}
|
|
|
|
// wrappedSigner wraps a crypto.Signer. In order to use a crypto.Signer in tests
|
|
// we need to wrap it as we pass a purposefully broken io.Reader to Sign in order
|
|
// to verify that go isn't using it as a source of randomness (we expect this
|
|
// randomness to come from the HSM). If we directly call Sign on the crypto.Signer
|
|
// it would fail, so we wrap it so that we can use a shim rand.Reader in the Sign
|
|
// call.
|
|
type wrappedSigner struct{ k crypto.Signer }
|
|
|
|
func (p wrappedSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
return p.k.Sign(rand.Reader, digest, opts)
|
|
}
|
|
|
|
func (p wrappedSigner) Public() crypto.PublicKey {
|
|
return p.k.Public()
|
|
}
|
|
|
|
func TestGenerateCRLLints(t *testing.T) {
|
|
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
test.AssertNotError(t, err, "failed to generate test key")
|
|
|
|
cert := &x509.Certificate{
|
|
Subject: pkix.Name{CommonName: "asd"},
|
|
SerialNumber: big.NewInt(7),
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
|
IsCA: true,
|
|
KeyUsage: x509.KeyUsageCRLSign,
|
|
SubjectKeyId: []byte{1, 2, 3},
|
|
}
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, k.Public(), k)
|
|
test.AssertNotError(t, err, "failed to generate test cert")
|
|
cert, err = x509.ParseCertificate(certBytes)
|
|
test.AssertNotError(t, err, "failed to parse test cert")
|
|
|
|
// This CRL should fail the following lint:
|
|
// - e_crl_acceptable_reason_codes (because 6 is forbidden)
|
|
_, err = generateCRL(&wrappedSigner{k}, cert, time.Now().Add(time.Hour), time.Now().Add(100*24*time.Hour), 1, []x509.RevocationListEntry{
|
|
{
|
|
SerialNumber: big.NewInt(12345),
|
|
RevocationTime: time.Now().Add(time.Hour),
|
|
ReasonCode: 6,
|
|
},
|
|
})
|
|
test.AssertError(t, err, "generateCRL did not fail")
|
|
test.AssertContains(t, err.Error(), "e_crl_acceptable_reason_codes")
|
|
}
|
|
|
|
func TestGenerateCRL(t *testing.T) {
|
|
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
test.AssertNotError(t, err, "failed to generate test key")
|
|
|
|
template := &x509.Certificate{
|
|
Subject: pkix.Name{CommonName: "asd"},
|
|
SerialNumber: big.NewInt(7),
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
KeyUsage: x509.KeyUsageCRLSign,
|
|
SubjectKeyId: []byte{1, 2, 3},
|
|
}
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, k.Public(), k)
|
|
test.AssertNotError(t, err, "failed to generate test cert")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
test.AssertNotError(t, err, "failed to parse test cert")
|
|
|
|
crlPEM, err := generateCRL(&wrappedSigner{k}, cert, time.Now().Add(time.Hour), time.Now().Add(time.Hour*2), 1, nil)
|
|
test.AssertNotError(t, err, "generateCRL failed with valid profile")
|
|
|
|
pemBlock, _ := pem.Decode(crlPEM)
|
|
crlDER := pemBlock.Bytes
|
|
|
|
// use crypto/x509 to check signature is valid and list is empty
|
|
goCRL, err := x509.ParseRevocationList(crlDER)
|
|
test.AssertNotError(t, err, "failed to parse CRL")
|
|
err = goCRL.CheckSignatureFrom(cert)
|
|
test.AssertNotError(t, err, "CRL signature check failed")
|
|
test.AssertEquals(t, len(goCRL.RevokedCertificateEntries), 0)
|
|
|
|
// fully parse the CRL to check that the version is correct, and that
|
|
// it contains the CRL number extension containing the number we expect
|
|
var crl asn1CRL
|
|
_, err = asn1.Unmarshal(crlDER, &crl)
|
|
test.AssertNotError(t, err, "failed to parse CRL")
|
|
test.AssertEquals(t, crl.TBS.Version, 1) // x509v2 == 1
|
|
test.AssertEquals(t, len(crl.TBS.Extensions), 3) // AKID, CRL number, IssuingDistributionPoint
|
|
test.Assert(t, crl.TBS.Extensions[1].Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 20}), "unexpected OID in extension")
|
|
test.Assert(t, crl.TBS.Extensions[2].Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 28}), "unexpected OID in extension")
|
|
var number int
|
|
_, err = asn1.Unmarshal(crl.TBS.Extensions[1].Value, &number)
|
|
test.AssertNotError(t, err, "failed to parse CRL number extension")
|
|
test.AssertEquals(t, number, 1)
|
|
}
|
|
|
|
type asn1CRL struct {
|
|
TBS struct {
|
|
Version int `asn1:"optional"`
|
|
SigAlg pkix.AlgorithmIdentifier
|
|
Issuer struct {
|
|
Raw asn1.RawContent
|
|
}
|
|
ThisUpdate time.Time
|
|
NextUpdate time.Time `asn1:"optional"`
|
|
RevokedCertificates []struct {
|
|
Serial *big.Int
|
|
RevokedAt time.Time
|
|
Extensions []pkix.Extension `asn1:"optional"`
|
|
} `asn1:"optional"`
|
|
Extensions []pkix.Extension `asn1:"optional,explicit,tag:0"`
|
|
}
|
|
SigAlg pkix.AlgorithmIdentifier
|
|
Sig asn1.BitString
|
|
}
|