317 lines
8.1 KiB
Go
317 lines
8.1 KiB
Go
package csr
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"net"
|
|
"net/netip"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/letsencrypt/boulder/core"
|
|
berrors "github.com/letsencrypt/boulder/errors"
|
|
"github.com/letsencrypt/boulder/features"
|
|
"github.com/letsencrypt/boulder/goodkey"
|
|
"github.com/letsencrypt/boulder/identifier"
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
type mockPA struct{}
|
|
|
|
func (pa *mockPA) ChallengeTypesFor(ident identifier.ACMEIdentifier) ([]core.AcmeChallenge, error) {
|
|
return []core.AcmeChallenge{}, nil
|
|
}
|
|
|
|
func (pa *mockPA) WillingToIssue(idents identifier.ACMEIdentifiers) error {
|
|
for _, ident := range idents {
|
|
if ident.Value == "bad-name.com" || ident.Value == "other-bad-name.com" {
|
|
return errors.New("policy forbids issuing for identifier")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pa *mockPA) ChallengeTypeEnabled(t core.AcmeChallenge) bool {
|
|
return true
|
|
}
|
|
|
|
func (pa *mockPA) CheckAuthzChallenges(a *core.Authorization) error {
|
|
return nil
|
|
}
|
|
|
|
func TestVerifyCSR(t *testing.T) {
|
|
private, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
test.AssertNotError(t, err, "error generating test key")
|
|
signedReqBytes, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{PublicKey: private.PublicKey, SignatureAlgorithm: x509.SHA256WithRSA}, private)
|
|
test.AssertNotError(t, err, "error generating test CSR")
|
|
signedReq, err := x509.ParseCertificateRequest(signedReqBytes)
|
|
test.AssertNotError(t, err, "error parsing test CSR")
|
|
brokenSignedReq := new(x509.CertificateRequest)
|
|
*brokenSignedReq = *signedReq
|
|
brokenSignedReq.Signature = []byte{1, 1, 1, 1}
|
|
signedReqWithHosts := new(x509.CertificateRequest)
|
|
*signedReqWithHosts = *signedReq
|
|
signedReqWithHosts.DNSNames = []string{"a.com", "b.com"}
|
|
signedReqWithLongCN := new(x509.CertificateRequest)
|
|
*signedReqWithLongCN = *signedReq
|
|
signedReqWithLongCN.Subject.CommonName = strings.Repeat("a", maxCNLength+1)
|
|
signedReqWithBadNames := new(x509.CertificateRequest)
|
|
*signedReqWithBadNames = *signedReq
|
|
signedReqWithBadNames.DNSNames = []string{"bad-name.com", "other-bad-name.com"}
|
|
signedReqWithEmailAddress := new(x509.CertificateRequest)
|
|
*signedReqWithEmailAddress = *signedReq
|
|
signedReqWithEmailAddress.EmailAddresses = []string{"foo@bar.com"}
|
|
signedReqWithIPAddress := new(x509.CertificateRequest)
|
|
*signedReqWithIPAddress = *signedReq
|
|
signedReqWithIPAddress.IPAddresses = []net.IP{net.IPv4(1, 2, 3, 4)}
|
|
signedReqWithURI := new(x509.CertificateRequest)
|
|
*signedReqWithURI = *signedReq
|
|
testURI, _ := url.ParseRequestURI("https://example.com/")
|
|
signedReqWithURI.URIs = []*url.URL{testURI}
|
|
signedReqWithAllLongSANs := new(x509.CertificateRequest)
|
|
*signedReqWithAllLongSANs = *signedReq
|
|
signedReqWithAllLongSANs.DNSNames = []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com"}
|
|
|
|
keyPolicy, err := goodkey.NewPolicy(nil, nil)
|
|
test.AssertNotError(t, err, "creating test keypolicy")
|
|
|
|
cases := []struct {
|
|
csr *x509.CertificateRequest
|
|
maxNames int
|
|
pa core.PolicyAuthority
|
|
expectedError error
|
|
}{
|
|
{
|
|
&x509.CertificateRequest{},
|
|
100,
|
|
&mockPA{},
|
|
invalidPubKey,
|
|
},
|
|
{
|
|
&x509.CertificateRequest{PublicKey: &private.PublicKey},
|
|
100,
|
|
&mockPA{},
|
|
unsupportedSigAlg,
|
|
},
|
|
{
|
|
brokenSignedReq,
|
|
100,
|
|
&mockPA{},
|
|
invalidSig,
|
|
},
|
|
{
|
|
signedReq,
|
|
100,
|
|
&mockPA{},
|
|
invalidNoIdent,
|
|
},
|
|
{
|
|
signedReqWithLongCN,
|
|
100,
|
|
&mockPA{},
|
|
nil,
|
|
},
|
|
{
|
|
signedReqWithHosts,
|
|
1,
|
|
&mockPA{},
|
|
berrors.BadCSRError("CSR contains more than 1 identifiers"),
|
|
},
|
|
{
|
|
signedReqWithBadNames,
|
|
100,
|
|
&mockPA{},
|
|
errors.New("policy forbids issuing for identifier"),
|
|
},
|
|
{
|
|
signedReqWithEmailAddress,
|
|
100,
|
|
&mockPA{},
|
|
invalidEmailPresent,
|
|
},
|
|
{
|
|
signedReqWithIPAddress,
|
|
100,
|
|
&mockPA{},
|
|
nil,
|
|
},
|
|
{
|
|
signedReqWithURI,
|
|
100,
|
|
&mockPA{},
|
|
invalidURIPresent,
|
|
},
|
|
{
|
|
signedReqWithAllLongSANs,
|
|
100,
|
|
&mockPA{},
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
err := VerifyCSR(context.Background(), c.csr, c.maxNames, &keyPolicy, c.pa)
|
|
test.AssertDeepEquals(t, c.expectedError, err)
|
|
}
|
|
}
|
|
|
|
func TestCNFromCSR(t *testing.T) {
|
|
tooLongString := strings.Repeat("a", maxCNLength+1)
|
|
|
|
cases := []struct {
|
|
name string
|
|
csr *x509.CertificateRequest
|
|
expectedCN string
|
|
}{
|
|
{
|
|
"no explicit CN",
|
|
&x509.CertificateRequest{DNSNames: []string{"a.com"}},
|
|
"a.com",
|
|
},
|
|
{
|
|
"explicit uppercase CN",
|
|
&x509.CertificateRequest{Subject: pkix.Name{CommonName: "A.com"}, DNSNames: []string{"a.com"}},
|
|
"a.com",
|
|
},
|
|
{
|
|
"no explicit CN, uppercase SAN",
|
|
&x509.CertificateRequest{DNSNames: []string{"A.com"}},
|
|
"a.com",
|
|
},
|
|
{
|
|
"duplicate SANs",
|
|
&x509.CertificateRequest{DNSNames: []string{"b.com", "b.com", "a.com", "a.com"}},
|
|
"b.com",
|
|
},
|
|
{
|
|
"explicit CN not found in SANs",
|
|
&x509.CertificateRequest{Subject: pkix.Name{CommonName: "a.com"}, DNSNames: []string{"b.com"}},
|
|
"a.com",
|
|
},
|
|
{
|
|
"no explicit CN, all SANs too long to be the CN",
|
|
&x509.CertificateRequest{DNSNames: []string{
|
|
tooLongString + ".a.com",
|
|
tooLongString + ".b.com",
|
|
}},
|
|
"",
|
|
},
|
|
{
|
|
"no explicit CN, leading SANs too long to be the CN",
|
|
&x509.CertificateRequest{DNSNames: []string{
|
|
tooLongString + ".a.com",
|
|
tooLongString + ".b.com",
|
|
"a.com",
|
|
"b.com",
|
|
}},
|
|
"a.com",
|
|
},
|
|
{
|
|
"explicit CN, leading SANs too long to be the CN",
|
|
&x509.CertificateRequest{
|
|
Subject: pkix.Name{CommonName: "A.com"},
|
|
DNSNames: []string{
|
|
tooLongString + ".a.com",
|
|
tooLongString + ".b.com",
|
|
"a.com",
|
|
"b.com",
|
|
}},
|
|
"a.com",
|
|
},
|
|
{
|
|
"explicit CN that's too long to be the CN",
|
|
&x509.CertificateRequest{
|
|
Subject: pkix.Name{CommonName: tooLongString + ".a.com"},
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
"explicit CN that's too long to be the CN, with a SAN",
|
|
&x509.CertificateRequest{
|
|
Subject: pkix.Name{CommonName: tooLongString + ".a.com"},
|
|
DNSNames: []string{
|
|
"b.com",
|
|
}},
|
|
"",
|
|
},
|
|
{
|
|
"explicit CN that's an IP",
|
|
&x509.CertificateRequest{
|
|
Subject: pkix.Name{CommonName: "127.0.0.1"},
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
"no CN, only IP SANs",
|
|
&x509.CertificateRequest{
|
|
IPAddresses: []net.IP{
|
|
netip.MustParseAddr("127.0.0.1").AsSlice(),
|
|
},
|
|
},
|
|
"",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
test.AssertEquals(t, CNFromCSR(tc.csr), tc.expectedCN)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSHA1Deprecation(t *testing.T) {
|
|
features.Reset()
|
|
|
|
keyPolicy, err := goodkey.NewPolicy(nil, nil)
|
|
test.AssertNotError(t, err, "creating test keypolicy")
|
|
|
|
private, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
test.AssertNotError(t, err, "error generating test key")
|
|
|
|
makeAndVerifyCsr := func(alg x509.SignatureAlgorithm) error {
|
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader,
|
|
&x509.CertificateRequest{
|
|
DNSNames: []string{"example.com"},
|
|
SignatureAlgorithm: alg,
|
|
PublicKey: &private.PublicKey,
|
|
}, private)
|
|
test.AssertNotError(t, err, "creating test CSR")
|
|
|
|
csr, err := x509.ParseCertificateRequest(csrBytes)
|
|
test.AssertNotError(t, err, "parsing test CSR")
|
|
|
|
return VerifyCSR(context.Background(), csr, 100, &keyPolicy, &mockPA{})
|
|
}
|
|
|
|
err = makeAndVerifyCsr(x509.SHA256WithRSA)
|
|
test.AssertNotError(t, err, "SHA256 CSR should verify")
|
|
|
|
err = makeAndVerifyCsr(x509.SHA1WithRSA)
|
|
test.AssertError(t, err, "SHA1 CSR should not verify")
|
|
}
|
|
|
|
func TestDuplicateExtensionRejection(t *testing.T) {
|
|
private, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
test.AssertNotError(t, err, "error generating test key")
|
|
|
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader,
|
|
&x509.CertificateRequest{
|
|
DNSNames: []string{"example.com"},
|
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
|
PublicKey: &private.PublicKey,
|
|
ExtraExtensions: []pkix.Extension{
|
|
{Id: asn1.ObjectIdentifier{2, 5, 29, 1}, Value: []byte("hello")},
|
|
{Id: asn1.ObjectIdentifier{2, 5, 29, 1}, Value: []byte("world")},
|
|
},
|
|
}, private)
|
|
test.AssertNotError(t, err, "creating test CSR")
|
|
|
|
_, err = x509.ParseCertificateRequest(csrBytes)
|
|
test.AssertError(t, err, "CSR with duplicate extension OID should fail to parse")
|
|
}
|