Merge pull request #262 from letsencrypt/ra-tests

Miscellaneous Fixes
This commit is contained in:
James 'J.C.' Jones 2015-05-30 22:08:49 -07:00
commit 3e593d73c9
17 changed files with 380 additions and 110 deletions

View File

@ -45,6 +45,8 @@ type Config struct {
// How long issue certificates are valid for, should match expiry field
// in cfssl config.
Expiry string
// The maximum number of subjectAltNames in a single certificate
MaxNames int
}
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
@ -60,6 +62,7 @@ type CertificateAuthorityImpl struct {
Prefix int // Prepended to the serial number
ValidityPeriod time.Duration
NotAfter time.Time
MaxNames int
}
// NewCertificateAuthorityImpl creates a CA that talks to a remote CFSSL
@ -139,6 +142,8 @@ func NewCertificateAuthorityImpl(cadb core.CertificateAuthorityDatabase, config
return nil, err
}
ca.MaxNames = config.MaxNames
return ca, nil
}
@ -169,15 +174,17 @@ func loadIssuerKey(filename string) (issuerKey crypto.Signer, err error) {
return
}
func dupeNames(names []string) bool {
func uniqueNames(names []string) (unique []string) {
nameMap := make(map[string]int, len(names))
for _, name := range names {
nameMap[name] = 1
}
if len(names) != len(nameMap) {
return true
unique = make([]string, 0, len(nameMap))
for name := range nameMap {
unique = append(unique, name)
}
return false
return
}
// GenerateOCSP produces a new OCSP response and returns it
@ -236,12 +243,14 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
return emptyCert, fmt.Errorf("Invalid public key in CSR.")
}
// XXX Take in authorizations and verify that union covers CSR?
// Pull hostnames from CSR
hostNames := csr.DNSNames // DNSNames + CN from CSR
var commonName string
// Authorization is checked by the RA
commonName := ""
hostNames := make([]string, len(csr.DNSNames))
copy(hostNames, csr.DNSNames)
if len(csr.Subject.CommonName) > 0 {
commonName = csr.Subject.CommonName
hostNames = append(hostNames, csr.Subject.CommonName)
} else if len(hostNames) > 0 {
commonName = hostNames[0]
} else {
@ -250,8 +259,10 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
return emptyCert, err
}
if dupeNames(hostNames) {
err = errors.New("Cannot issue a certificate with duplicate DNS names.")
// Collapse any duplicate names. Note that this operation may re-order the names
hostNames = uniqueNames(hostNames)
if ca.MaxNames > 0 && len(hostNames) > ca.MaxNames {
err = errors.New(fmt.Sprintf("Certificate request has %d > %d names", len(hostNames), ca.MaxNames))
ca.log.WarningErr(err)
return emptyCert, err
}

View File

@ -117,8 +117,8 @@ var CA_CERT_PEM = "-----BEGIN CERTIFICATE-----\n" +
// CSR generated by Go:
// * Random public key
// * CN = example.com
// * DNSNames = example.com, www.example.com
// * CN = not-example.com
// * DNSNames = not-example.com, www.not-example.com
var CN_AND_SAN_CSR_HEX = "308202a130820189020100301a311830160603550403130f6e6f742d6578" +
"616d706c652e636f6d30820122300d06092a864886f70d01010105000382" +
"010f003082010a0282010100e56ccbe37003c150202e6f543f9eb1d0e590" +
@ -225,22 +225,59 @@ var NO_NAME_CSR_HEX = "308202523082013a020100300d310b300906035504061302555330820
// CSR generated by Go:
// * Random public key
// * CN = example.com
// * CN = [none]
// * DNSNames = a.example.com, a.example.com
var DUPE_NAME_CSR_HEX = "3082018d3081f90201003016311430120603550403130b6578616d706c65" +
"2e636f6d30819f300d06092a864886f70d010101050003818d0030818902" +
"818100cc4a0cf2cf67811e4457fe1106597013e84be141c583b663f2ef6d" +
"a0c9254ca4c37fcd1945fdddc6db66f395c679de33501d333efd60d941d5" +
"a32d29a1e5af6da853ba28419b471081a8476d7bdf7159cc09606eec807f" +
"da89586ebee0e46a5f53a14c2210a934e92afd314c0bc1b6946afce63a21" +
"0b6eac62eca728efbb36c70203010001a03a303806092a864886f70d0109" +
"0e312b302930270603551d110420301e820d612e6578616d706c652e636f" +
"6d820d612e6578616d706c652e636f6d300b06092a864886f70d01010b03" +
"818100604965228739c63f5d94d29295a7c327f70c08f361d4873166f112" +
"d420ca424d9a86cfb49483cf54090d1d81e56b1aeea09cafd783e7ef4fb8" +
"fdbd43e1918e474abb2ea8962960c5c77ac5be5cbf67e515d8234ca7fe4e" +
"5b7c0134e95b77a43a6b5789ff97b3262f949e75690314e417c4c2bd3d1f" +
"7bedb21db1dd5dd4f71b82"
var DUPE_NAME_CSR_HEX = "308202943082017c020100300d310b300906035504061302555330820122" +
"300d06092a864886f70d01010105000382010f003082010a0282010100ee" +
"7d298c2a8237dd84e75e71dcfbbf8e1124327b103b01f3a99bc76b29be64" +
"55329dc523ad1372ed12853dc74a775f2c79d1e4e28ae2a3ce69b78ec161" +
"76b31056a72b5b87bcd7501ab9f15e83346e788c085e472718494849c012" +
"ff6c25f12c040a0c68e99a46e8646523bf2b4103dcc7c4e583c6bf02c896" +
"09b55e470beb15eaefc0bb6d57aca39e75312833e87faf2ba33e12a3204c" +
"a19e5a1016c85b750619be9cb76fb93e653f3169b873dce42deab328a034" +
"57454fd8e135e125bdb4529144a34bcc5ab20f25c6b33300ab2ad994d251" +
"9261920da78e05b1c8847852e93a74000e7b29304ef2cccfa5315888150c" +
"14e9d51e2c343de44e6bd2903740ef0203010001a042304006092a864886" +
"f70d01090e31333031302f0603551d11042830268211612e6e6f742d6578" +
"616d706c652e636f6d8211612e6e6f742d6578616d706c652e636f6d300d" +
"06092a864886f70d01010b050003820101001358a156338bacd21bd66f35" +
"83bf26afbaa6750f1d5d8e6285d062d5ae4f379000cd53568e8ef11bb542" +
"97d906879614960266fece8f9060881c3b0ec34a1bb4b28f62cc444e5831" +
"09c278d21265b2ef6f120ae8bc2554ba46b60e8d46a8cd3093ea6b72cb42" +
"9d2d6072273e4a0c1a5ec3316cc2495a2776698f12cd4f4226f4b3e85f87" +
"0b0f32785cf823b9dfa7116eec8a6f9bdc5cf01c1fff46afbb55e636ced4" +
"0ccdf4cab09e7717aa2a67c5573590bb5d461cb13cc57b8451314ed3613e" +
"21757aeb846f5b33f107a01f93a718b3a83539562e6486a72f114df898ed" +
"c34e38d9bbdb96d0b8ab243c46da274cece70deb771985c477098468063a" +
"8400ecb6"
// CSR generated by Go:
// * Random pulic key
// * CN = [none]
// * DNSNames = not-example.com, www.not-example.com, mail.example.com
var TOO_MANY_NAME_CSR_HEX = "308202aa30820192020100300d310b300906035504061302555330820122" +
"300d06092a864886f70d01010105000382010f003082010a0282010100a7" +
"75d8f833651d9a4cfa1fa0e134912b772366c7d070ca3183d3c79ffc99bb" +
"c706d328c2389b360b99f60e9a447023a019931d410b4cea0eafb7869a6d" +
"879d6a976163e3e8de715bcc6a0d80654484450a837b9abae0c5c2fa28dd" +
"4f33d8043c32e2ecb9c41a0b6c7df60f9f6eaaef6cef38722db9b278b773" +
"c8251c75d63c36c5708024d3ec67372c205342ad93a68cc4cc9f90a92789" +
"b4fcdcbe050990b6e3edf2655c9df8365eb8be89a6ee4f80267e056e5cca" +
"853094a96ec1d76a00cef8724ed3b402f602c30cfc16128abc25bab65a9c" +
"f8a046b576e314a5d01e0887cfde86c01094b621e2230fc5db588ffd0e0e" +
"2454e60e8b83a9e82f33419abc62710203010001a058305606092a864886" +
"f70d01090e3149304730450603551d11043e303c820f6e6f742d6578616d" +
"706c652e636f6d82137777772e6e6f742d6578616d706c652e636f6d8214" +
"6d61696c2e6e6f742d6578616d706c652e636f6d300d06092a864886f70d" +
"01010b05000382010100486c9cea322a3a81ec9b828ccb303524cc914e0b" +
"81b45ea23214b0dd254a79cdc77d1a661c7de8dcfcc7d404c0ad8707a67a" +
"1ecc43ed5b4a7d96fabe41cab5ad8fde18f894bd2e57c8291b3bb0220c95" +
"7c178c1c4251be4252663a14e4c303a8563bb86056f8e07ab14082693f72" +
"3a55efe017b15d22cd31b6aa7b1bb5e1078e6f7b7fb12962230b192e0d40" +
"a4f4515d6f2922b587e3586ea7f61ae7196cfeab5b4e5f78e03de0dab5a6" +
"a537e6740bbb2a72b32c94068b4b46766f94ab3d24a704869d127fab6fb9" +
"9c470101aed4852cd7b3a0a5a489b264779118b30d51907dc745879a821b" +
"85f19a24d20dc5b48141eff276aa73bb41141305905b0223ded7"
// CFSSL config
const hostPort = "localhost:9000"
@ -346,6 +383,7 @@ func setup(t *testing.T) (cadb core.CertificateAuthorityDatabase, storageAuthori
IssuerKey: "../test/test-ca.key",
TestMode: true,
Expiry: "8760h",
MaxNames: 2,
}
return cadb, storageAuthority, caConfig
}
@ -360,6 +398,7 @@ func TestFailNoSerial(t *testing.T) {
func TestRevoke(t *testing.T) {
cadb, storageAuthority, caConfig := setup(t)
ca, err := NewCertificateAuthorityImpl(cadb, caConfig)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
csrDER, _ := hex.DecodeString(CN_AND_SAN_CSR_HEX)
@ -416,8 +455,22 @@ func TestIssueCertificate(t *testing.T) {
test.AssertEquals(t, cert.Subject.CommonName, "not-example.com")
if len(cert.DNSNames) == 0 || cert.DNSNames[0] != "not-example.com" {
// NB: This does not check for www.not-example.com in the 'both' case
switch len(cert.DNSNames) {
case 1:
if cert.DNSNames[0] != "not-example.com" {
t.Errorf("Improper list of domain names %v", cert.DNSNames)
}
case 2:
switch {
case (cert.DNSNames[0] == "not-example.com" && cert.DNSNames[1] == "www.not-example.com"):
t.Log("case 1")
case (cert.DNSNames[0] == "www.not-example.com" && cert.DNSNames[1] == "not-example.com"):
t.Log("case 2")
default:
t.Errorf("Improper list of domain names %v", cert.DNSNames)
}
default:
t.Errorf("Improper list of domain names %v", cert.DNSNames)
}
@ -457,32 +510,63 @@ func TestRejectNoName(t *testing.T) {
if err == nil {
t.Errorf("CA improperly agreed to create a certificate with no name")
}
}
// Test that the CA rejects CSRs with duplicate names
csrDER, _ = hex.DecodeString(DUPE_NAME_CSR_HEX)
csr, _ = x509.ParseCertificateRequest(csrDER)
func TestRejectTooManyNames(t *testing.T) {
cadb, storageAuthority, caConfig := setup(t)
ca, err := NewCertificateAuthorityImpl(cadb, caConfig)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
// Test that the CA collapses duplicate names
csrDER, _ := hex.DecodeString(TOO_MANY_NAME_CSR_HEX)
csr, _ := x509.ParseCertificateRequest(csrDER)
_, err = ca.IssueCertificate(*csr, 1)
if err == nil {
t.Errorf("CA improperly agreed to create a certificate with duplicate names")
test.Assert(t, err != nil, "Issued certificate with too many names")
}
func TestDeduplication(t *testing.T) {
cadb, storageAuthority, caConfig := setup(t)
ca, err := NewCertificateAuthorityImpl(cadb, caConfig)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
// Test that the CA collapses duplicate names
csrDER, _ := hex.DecodeString(DUPE_NAME_CSR_HEX)
csr, _ := x509.ParseCertificateRequest(csrDER)
cert, err := ca.IssueCertificate(*csr, 1)
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with duplicate names")
if err != nil {
return
}
parsedCert, err := x509.ParseCertificate(cert.DER)
test.AssertNotError(t, err, "Error parsing certificate produced by CA")
if err != nil {
return
}
correctName := "a.not-example.com"
correctNames := len(parsedCert.DNSNames) == 1 &&
parsedCert.DNSNames[0] == correctName &&
parsedCert.Subject.CommonName == correctName
test.Assert(t, correctNames, "Incorrect set of names in deduplicated certificate")
}
func TestRejectValidityTooLong(t *testing.T) {
cadb, storageAuthority, caConfig := setup(t)
ca, err := NewCertificateAuthorityImpl(cadb, caConfig)
test.AssertNotError(t, err, "Failed to create CA")
ca.SA = storageAuthority
// Test that the CA rejects CSRs that would expire after the intermediate cert
csrDER, _ = hex.DecodeString(NO_CN_CSR_HEX)
csr, _ = x509.ParseCertificateRequest(csrDER)
csrDER, _ := hex.DecodeString(NO_CN_CSR_HEX)
csr, _ := x509.ParseCertificateRequest(csrDER)
ca.NotAfter = time.Now()
_, err = ca.IssueCertificate(*csr, 1)
test.AssertEquals(t, err.Error(), "Cannot issue a certificate that expires after the intermediate certificate.")
}
func TestDupeNames(t *testing.T) {
unique := []string{"a", "b"}
notUnique := []string{"a", "a"}
test.Assert(t, !dupeNames([]string{}), "Empty list can't contain duplicates")
test.Assert(t, !dupeNames(unique), "Unique list doesn't have duplicates")
test.Assert(t, dupeNames(notUnique), "Non-unique list does have duplicates")
}
func TestShortKey(t *testing.T) {
cadb, storageAuthority, caConfig := setup(t)
ca, err := NewCertificateAuthorityImpl(cadb, caConfig)

View File

@ -69,7 +69,7 @@ func setupContext(context *cli.Context) (rpc.CertificateAuthorityClient, *blog.A
ch := cmd.AmqpChannel(c.AMQP.Server)
cac, err := rpc.NewCertificateAuthorityClient(c.AMQP.CA.Client, c.AMQP.CA.Server, ch)
cac, err := rpc.NewCertificateAuthorityClient("revoker->CA", c.AMQP.CA.Server, ch)
cmd.FailOnError(err, "Unable to create CA client")
dbMap, err := sa.NewDbMap(c.Revoker.DBDriver, c.Revoker.DBName)

View File

@ -97,8 +97,8 @@ func findStaleResponses(cac rpc.CertificateAuthorityClient, dbMap *gorp.DbMap, o
var certificateStatus []core.CertificateStatus
_, err := dbMap.Select(&certificateStatus,
`SELECT cs.* FROM certificateStatus AS cs
WHERE cs.ocspLastUpdated < ?
`SELECT cs.* FROM certificateStatus AS cs JOIN certificates AS cert ON cs.serial = cert.serial
WHERE cs.ocspLastUpdated < ? AND cert.expires > now()
ORDER BY cs.ocspLastUpdated ASC
LIMIT ?`, oldestLastUpdatedTime, responseLimit)

View File

@ -15,15 +15,22 @@ import (
"github.com/letsencrypt/boulder/test"
)
func TestUnknownKeyType(t *testing.T) {
notAKey := struct{}{}
test.Assert(t, !GoodKey(notAKey), "Should have rejeected a key of unknown type")
}
func TestWrongKeyType(t *testing.T) {
ecdsaKey := ecdsa.PublicKey{}
test.Assert(t, !GoodKey(&ecdsaKey), "Should have rejected ECDSA key.")
test.Assert(t, !GoodKey(ecdsaKey), "Should have rejected ECDSA key.")
}
func TestSmallModulus(t *testing.T) {
private, err := rsa.GenerateKey(rand.Reader, 2040)
test.AssertNotError(t, err, "Error generating key")
test.Assert(t, !GoodKey(&private.PublicKey), "Should have rejected too-short key.")
test.Assert(t, !GoodKey(private.PublicKey), "Should have rejected too-short key.")
}
func TestSmallExponent(t *testing.T) {

View File

@ -114,8 +114,6 @@ type Registration struct {
// Agreement with terms of service
Agreement string `json:"agreement,omitempty" db:"agreement"`
Thumbprint string `json:"thumbprint" db:"thumbprint"`
LockCol int64 `json:"-"`
}
@ -279,10 +277,6 @@ type Authorization struct {
// The server may suggest combinations of challenges if it
// requires more than one challenge to be completed.
Combinations [][]int `json:"combinations,omitempty" db:"combinations"`
// The client may provide contact URIs to allow the server
// to push information to it.
Contact []AcmeURL `json:"contact,omitempty" db:"contact"`
}
// Fields of this type get encoded and decoded JOSE-style, in base64url encoding
@ -326,10 +320,11 @@ type Certificate struct {
// * "revoked" - revoked
Status AcmeStatus `db:"status"`
Serial string `db:"serial"`
Digest string `db:"digest"`
DER []byte `db:"der"`
Issued time.Time `db:"issued"`
Serial string `db:"serial"`
Digest string `db:"digest"`
DER []byte `db:"der"`
Issued time.Time `db:"issued"`
Expires time.Time `db:"expires"`
}
// CertificateStatus structs are internal to the server. They represent the

View File

@ -6,11 +6,32 @@
package core
import (
"encoding/json"
"net/url"
"testing"
"github.com/letsencrypt/boulder/test"
)
func TestRegistrationUupdate(t *testing.T) {
oldURL, _ := url.Parse("http://old.invalid")
newURL, _ := url.Parse("http://new.invalid")
reg := Registration{
ID: 1,
Contact: []AcmeURL{AcmeURL(*oldURL)},
Agreement: "",
}
update := Registration{
Contact: []AcmeURL{AcmeURL(*newURL)},
Agreement: "totally!",
}
reg.MergeUpdate(update)
test.Assert(t, len(reg.Contact) == 1 && reg.Contact[0] == update.Contact[0], "Contact was not updated %v != %v")
test.Assert(t, reg.Agreement == update.Agreement, "Agreement was not updated")
}
func TestSanityCheck(t *testing.T) {
chall := Challenge{Type: ChallengeTypeSimpleHTTPS, Status: StatusValid}
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
@ -58,4 +79,18 @@ func TestSanityCheck(t *testing.T) {
test.Assert(t, !chall.IsSane(true), "IsSane should be false")
chall.S = "KQqLsiS5j0CONR_eUXTUSUDNVaHODtc-0pD6ACif7U4"
test.Assert(t, chall.IsSane(true), "IsSane should be true")
chall = Challenge{Type: "bogus", Status: StatusPending}
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
test.Assert(t, !chall.IsSane(true), "IsSane should be false")
}
func TestJsonBufferUnmarshal(t *testing.T) {
testStruct := struct {
Buffer JsonBuffer
}{}
notValidBase64 := []byte(`{"Buffer":"!!!!"}`)
err := json.Unmarshal(notValidBase64, &testStruct)
test.Assert(t, err != nil, "Should have choked on invalid base64")
}

View File

@ -6,10 +6,13 @@
package core
import (
"encoding/json"
"fmt"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/square/go-jose"
"github.com/letsencrypt/boulder/test"
"math"
"math/big"
"net/url"
"testing"
)
@ -31,11 +34,6 @@ func TestNewToken(t *testing.T) {
return
}
func TestRandString(t *testing.T) {
// This is covered by NewToken
return
}
func TestSerialUtils(t *testing.T) {
serial := SerialToString(big.NewInt(100000000000000000))
test.AssertEquals(t, serial, "0000000000000000016345785d8a0000")
@ -52,3 +50,49 @@ func TestSerialUtils(t *testing.T) {
func TestBuildID(t *testing.T) {
test.AssertEquals(t, "Unspecified", GetBuildID())
}
const JWK_1_JSON = `{
"kty": "RSA",
"n": "vuc785P8lBj3fUxyZchF_uZw6WtbxcorqgTyq-qapF5lrO1U82Tp93rpXlmctj6fyFHBVVB5aXnUHJ7LZeVPod7Wnfl8p5OyhlHQHC8BnzdzCqCMKmWZNX5DtETDId0qzU7dPzh0LP0idt5buU7L9QNaabChw3nnaL47iu_1Di5Wp264p2TwACeedv2hfRDjDlJmaQXuS8Rtv9GnRWyC9JBu7XmGvGDziumnJH7Hyzh3VNu-kSPQD3vuAFgMZS6uUzOztCkT0fpOalZI6hqxtWLvXUMj-crXrn-Maavz8qRhpAyp5kcYk3jiHGgQIi7QSK2JIdRJ8APyX9HlmTN5AQ",
"e": "AAEAAQ"
}`
const JWK_1_DIGEST = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=`
const JWK_2_JSON = `{
"kty":"RSA",
"n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw",
"e":"AAEAAQ"
}`
func TestKeyDigest(t *testing.T) {
// Test with JWK (value, reference, and direct)
var jwk jose.JsonWebKey
json.Unmarshal([]byte(JWK_1_JSON), &jwk)
digest, err := KeyDigest(jwk)
test.Assert(t, err == nil && digest == JWK_1_DIGEST, "Failed to digest JWK by value")
digest, err = KeyDigest(&jwk)
test.Assert(t, err == nil && digest == JWK_1_DIGEST, "Failed to digest JWK by reference")
digest, err = KeyDigest(jwk.Key)
test.Assert(t, err == nil && digest == JWK_1_DIGEST, "Failed to digest bare key")
// Test with unknown key type
digest, err = KeyDigest(struct{}{})
test.Assert(t, err != nil, "Should have rejected unknown key type")
}
func TestKeyDigestEquals(t *testing.T) {
var jwk1, jwk2 jose.JsonWebKey
json.Unmarshal([]byte(JWK_1_JSON), &jwk1)
json.Unmarshal([]byte(JWK_2_JSON), &jwk2)
test.Assert(t, KeyDigestEquals(jwk1, jwk1), "Key digests for same key should match")
test.Assert(t, !KeyDigestEquals(jwk1, jwk2), "Key digests for different keys should not match")
test.Assert(t, !KeyDigestEquals(jwk1, struct{}{}), "Unknown key types should not match anything")
test.Assert(t, !KeyDigestEquals(struct{}{}, struct{}{}), "Unknown key types should not match anything")
}
func TestAcmeURL(t *testing.T) {
s := "http://example.invalid"
u, _ := url.Parse(s)
a := AcmeURL(*u)
test.AssertEquals(t, s, a.String())
}

View File

@ -166,7 +166,20 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
logEvent.CommonName = csr.Subject.CommonName
logEvent.Names = csr.DNSNames
csrPreviousDenied, err := ra.SA.AlreadyDeniedCSR(append(csr.DNSNames, csr.Subject.CommonName))
// Validate that authorization key is authorized for all domains
names := make([]string, len(csr.DNSNames))
copy(names, csr.DNSNames)
if len(csr.Subject.CommonName) > 0 {
names = append(names, csr.Subject.CommonName)
}
if len(names) == 0 {
err = core.UnauthorizedError("CSR has no names in it")
logEvent.Error = err.Error()
return emptyCert, err
}
csrPreviousDenied, err := ra.SA.AlreadyDeniedCSR(names)
if err != nil {
logEvent.Error = err.Error()
return emptyCert, err
@ -220,12 +233,6 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
}
logEvent.VerificationMethods = verificationMethods
// Validate that authorization key is authorized for all domains
names := csr.DNSNames
if len(csr.Subject.CommonName) > 0 {
names = append(names, csr.Subject.CommonName)
}
// Validate all domains
for _, name := range names {
if !authorizedDomains[name] {

View File

@ -112,11 +112,33 @@ var (
ExampleCSR = &x509.CertificateRequest{}
// These values are populated by the tests as we go
AuthzInitial = core.Authorization{}
AuthzUpdated = core.Authorization{}
AuthzFromVA = core.Authorization{}
AuthzFinal = core.Authorization{}
AuthzFinalWWW = core.Authorization{}
url0, _ = url.Parse("http://acme.invalid/authz/60p2Dc_XmUB2UUJBV4wYkF7BJbPD9KlDnUL3SmFMuTE?challenge=0")
url1, _ = url.Parse("http://acme.invalid/authz/60p2Dc_XmUB2UUJBV4wYkF7BJbPD9KlDnUL3SmFMuTE?challenge=0")
Registration = core.Registration{}
AuthzInitial = core.Authorization{
ID: "60p2Dc_XmUB2UUJBV4wYkF7BJbPD9KlDnUL3SmFMuTE",
Identifier: core.AcmeIdentifier{Type: "dns", Value: "not-example.com"},
RegistrationID: 1,
Status: "pending",
Challenges: []core.Challenge{
core.Challenge{
Type: "simpleHttps",
Status: "pending",
URI: core.AcmeURL(*url0),
Token: "pDX9vBFJ043_gEc9Wyp8of-SqZMN2H3-fvj5iUgP7mg",
},
core.Challenge{
Type: "dvsni",
Status: "pending",
URI: core.AcmeURL(*url1),
R: "AI83O7gCMPDr4z7OIdl8T6axx6nui4HV1aAFQ5LJvVs",
Nonce: "f011c9a0ce1a4fe0f18f2252d64c4239",
},
},
Combinations: [][]int{[]int{0}, []int{1}},
}
AuthzUpdated = core.Authorization{}
AuthzFinal = core.Authorization{}
)
func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationAuthority, *sa.SQLStorageAuthority, core.RegistrationAuthority) {
@ -148,13 +170,24 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
ExampleCSR, _ = x509.ParseCertificateRequest(csrDER)
// This registration implicitly gets ID = 1
sa.NewRegistration(core.Registration{Key: AccountKey})
Registration, _ = sa.NewRegistration(core.Registration{Key: AccountKey})
ra := NewRegistrationAuthorityImpl()
ra.SA = sa
ra.VA = va
ra.CA = &ca
ra.PA = pa
ra.AuthzBase = "http://acme.invalid/authz/"
AuthzInitial.RegistrationID = Registration.ID
AuthzUpdated = AuthzInitial
AuthzUpdated.Challenges[0].Path = "Hf5GrX4Q7EBax9hc2jJnfw"
AuthzFinal = AuthzUpdated
AuthzFinal.Status = "valid"
AuthzFinal.Expires = time.Now().Add(24 * time.Hour)
AuthzFinal.Challenges[0].Status = "valid"
return &ca, va, sa, &ra
}
@ -164,7 +197,7 @@ func assertAuthzEqual(t *testing.T, a1, a2 core.Authorization) {
test.Assert(t, a1.Identifier == a2.Identifier, "ret != DB: Identifier")
test.Assert(t, a1.Status == a2.Status, "ret != DB: Status")
test.Assert(t, a1.RegistrationID == a2.RegistrationID, "ret != DB: RegID")
// Not testing: Contact, Challenges
// Not testing: Challenges
}
func TestNewRegistration(t *testing.T) {
@ -217,7 +250,6 @@ func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
test.AssertNotError(t, err, "Could not update registration")
test.Assert(t, result2.ID != 33, "ID shouldn't be overwritten.")
test.Assert(t, !core.KeyDigestEquals(result2.Key, ShortKey), "Key shouldn't be overwritten")
test.Assert(t, result2.RecoveryToken != "RecoverMe2", "Recovery token shouldn't be overwritten by user")
}
@ -258,6 +290,7 @@ func TestNewAuthorization(t *testing.T) {
test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI")
// If we get to here, we'll use this authorization for the next test
fmt.Printf("AuthzInitial: %+v\n", authz)
AuthzInitial = authz
// TODO Test failure cases
@ -286,9 +319,6 @@ func TestUpdateAuthorization(t *testing.T) {
simpleHttps := va.Argument.Challenges[0]
test.Assert(t, simpleHttps.Path == Response.Path, "simpleHttps changed")
// If we get to here, we'll use this authorization for the next test
AuthzUpdated = authz
// TODO Test failure cases
t.Log("DONE TestUpdateAuthorization")
}
@ -299,22 +329,19 @@ func TestOnValidationUpdate(t *testing.T) {
sa.UpdatePendingAuthorization(AuthzUpdated)
// Simulate a successful simpleHTTPS challenge
AuthzFromVA = AuthzUpdated
AuthzFromVA.Challenges[0].Status = core.StatusValid
authzFromVA := AuthzUpdated
authzFromVA.Challenges[0].Status = core.StatusValid
ra.OnValidationUpdate(AuthzFromVA)
ra.OnValidationUpdate(authzFromVA)
// Verify that the Authz in the DB is the same except for Status->StatusValid
AuthzFromVA.Status = core.StatusValid
dbAuthz, err := sa.GetAuthorization(AuthzFromVA.ID)
authzFromVA.Status = core.StatusValid
dbAuthz, err := sa.GetAuthorization(authzFromVA.ID)
test.AssertNotError(t, err, "Could not fetch authorization from database")
assertAuthzEqual(t, AuthzFromVA, dbAuthz)
t.Log(" ~~> from VA: ", AuthzFromVA.Status)
assertAuthzEqual(t, authzFromVA, dbAuthz)
t.Log(" ~~> from VA: ", authzFromVA.Status)
t.Log(" ~~> from DB: ", dbAuthz.Status)
// If we get to here, we'll use this authorization for the next test
AuthzFinal = dbAuthz
// TODO Test failure cases
t.Log("DONE TestOnValidationUpdate")
}
@ -348,6 +375,31 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
_, err = ra.NewCertificate(certRequest, 1)
test.AssertError(t, err, "Should have rejected cert with key = account key")
test.AssertEquals(t, err.Error(), "Certificate public key must be different than account key")
t.Log("DONE TestCertificateKeyNotEqualAccountKey")
}
func TestAuthorizationRequired(t *testing.T) {
_, _, sa, ra := initAuthorities(t)
AuthzFinal.RegistrationID = 1
AuthzFinal, _ = sa.NewPendingAuthorization(AuthzFinal)
sa.UpdatePendingAuthorization(AuthzFinal)
sa.FinalizeAuthorization(AuthzFinal)
// Construct a cert request referencing the authorization
url1, _ := url.Parse("http://doesnt.matter/" + AuthzFinal.ID)
// ExampleCSR requests not-example.com and www.not-example.com,
// but the authorization only covers not-example.com
certRequest := core.CertificateRequest{
CSR: ExampleCSR,
Authorizations: []core.AcmeURL{core.AcmeURL(*url1)},
}
_, err := ra.NewCertificate(certRequest, 1)
test.Assert(t, err != nil, "Issued certificate with insufficient authorization")
t.Log("DONE TestAuthorizationRequired")
}
func TestNewCertificate(t *testing.T) {
@ -358,14 +410,14 @@ func TestNewCertificate(t *testing.T) {
sa.FinalizeAuthorization(AuthzFinal)
// Inject another final authorization to cover www.example.com
AuthzFinalWWW = AuthzFinal
AuthzFinalWWW.Identifier.Value = "www.example.com"
AuthzFinalWWW, _ = sa.NewPendingAuthorization(AuthzFinalWWW)
sa.FinalizeAuthorization(AuthzFinalWWW)
authzFinalWWW := AuthzFinal
authzFinalWWW.Identifier.Value = "www.not-example.com"
authzFinalWWW, _ = sa.NewPendingAuthorization(authzFinalWWW)
sa.FinalizeAuthorization(authzFinalWWW)
// Construct a cert request referencing the two authorizations
url1, _ := url.Parse("http://doesnt.matter/" + AuthzFinal.ID)
url2, _ := url.Parse("http://doesnt.matter/" + AuthzFinalWWW.ID)
url2, _ := url.Parse("http://doesnt.matter/" + authzFinalWWW.ID)
certRequest := core.CertificateRequest{
CSR: ExampleCSR,
@ -374,16 +426,25 @@ func TestNewCertificate(t *testing.T) {
cert, err := ra.NewCertificate(certRequest, 1)
test.AssertNotError(t, err, "Failed to issue certificate")
if err != nil {
return
}
parsedCert, err := x509.ParseCertificate(cert.DER)
test.AssertNotError(t, err, "Failed to parse certificate")
if err != nil {
return
}
// Verify that cert shows up and is as expected
dbCert, err := sa.GetCertificate(core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, fmt.Sprintf("Could not fetch certificate %032x from database",
parsedCert.SerialNumber))
if err != nil {
return
}
test.Assert(t, bytes.Compare(cert.DER, dbCert) == 0, "Certificates differ")
// TODO Test failure cases
t.Log("DONE TestOnValidationUpdate")
}
@ -476,7 +537,8 @@ var CA_CERT_PEM = "-----BEGIN CERTIFICATE-----\n" +
// CSR generated by Go:
// * Random public key
// * CN = not-example.com
// * DNSNames = not-example.com
// * DNSNames = not-example.com, www.not-example.com
/*
var CSR_HEX = "3082028c30820174020100301a311830160603550403130f" +
"6e6f742d6578616d706c652e636f6d30820122300d06092a" +
"864886f70d01010105000382010f003082010a0282010100" +
@ -505,3 +567,34 @@ var CSR_HEX = "3082028c30820174020100301a311830160603550403130f" +
"53f07e1654e6077f4aaaf5c5b27edaf0385b48e0fc281424" +
"6363a01370c89e666169276eb133731cff2379d46d2fff9d" +
"f277b6d23fa24f9b"
*/
var CSR_HEX = "308202ae308201960201003027310b300906035504061302" +
"5553311830160603550403130f6e6f742d6578616d706c65" +
"2e636f6d30820122300d06092a864886f70d010101050003" +
"82010f003082010a0282010100a4f507b52ca2766e2cea7b" +
"aaada9c3e08ea3423d6617ae84df65b6ed7e6c031605851b" +
"f0a14f3461a9f1882de9808b8e59d639c85eec58dbe653e3" +
"855e94d81904b7ce6675a1930e0ea6537aa3936fdc9d9780" +
"bc9596e5ec183811b137f83f28781d619fae8471ff3db1ad" +
"5a4b5cbf96d127d0f16e3c6ccbb97c48b43a7ddfcc17fdf3" +
"eac049cc81e4703ba90ce15d3cdfd9d0a3b0ec138f1c06e0" +
"8212c94e6884480d4b8f16fcf38f1b10d942cfca558b322e" +
"d8896be3104fb40e6851f3b414929b4f54fae89668ab0cbf" +
"76b7eb94703b17a73c9189409b088e7d61f39560a413562e" +
"64f26b650aede2d27bd2bacfc55d6a106243ba6ce07046d4" +
"fda618881b0203010001a042304006092a864886f70d0109" +
"0e31333031302f0603551d1104283026820f6e6f742d6578" +
"616d706c652e636f6d82137777772e6e6f742d6578616d70" +
"6c652e636f6d300d06092a864886f70d01010b0500038201" +
"01006e168e521ea37595698ceab29a3815c57b301dcd9c86" +
"6fdc7cfb966afde87da52c699f43133a6abfbbeb031f1b02" +
"cb072c8543b73fdffff6ee002ed367fe3b09992ac496c4ef" +
"1b7487e68c25f66b8d1223a07feebfad8fd7f19727bff7b4" +
"02bf6bef705c0a48e800e15bafbc622cb62ee446814234a3" +
"ebf9b8ba3c094d64b64aaa1b2b955f769ce60e9e304f7781" +
"57814f2f1cb1c4e2ee58bcdc0640dd2f0ff387ddb61ed479" +
"7ea935e79638a63dd64bd36723f34c1e6725ae57d8ff63f8" +
"749ac154cfaa55b3d3cccd7d42994c922cbb171a43c7ab68" +
"5170d833829d28a574fb25ffcf0fd5d3f19becaef2223541" +
"c2a8e596a80c8cde27bc78e20d7171fe43d8"

View File

@ -54,7 +54,6 @@ const (
MethodUpdatePendingAuthorization = "UpdatePendingAuthorization" // SA
MethodFinalizeAuthorization = "FinalizeAuthorization" // SA
MethodAddCertificate = "AddCertificate" // SA
MethodAddDeniedCSR = "AddDeniedCSR" // SA
MethodAlreadyDeniedCSR = "AlreadyDeniedCSR" // SA
)

View File

@ -558,6 +558,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(certDER []byte, regID int64) (dig
Digest: digest,
DER: certDER,
Issued: time.Now(),
Expires: parsedCertificate.NotAfter,
}
certStatus := &core.CertificateStatus{
SubscriberApproved: false,

View File

@ -119,7 +119,7 @@ func TestAddAuthorization(t *testing.T) {
combos := make([][]int, 1)
combos[0] = []int{0, 1}
newPa := core.Authorization{ID: PA.ID, Identifier: core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}, RegistrationID: 0, Status: core.StatusPending, Expires: time.Now().AddDate(0, 0, 1), Challenges: []core.Challenge{chall}, Combinations: combos, Contact: []core.AcmeURL{u}}
newPa := core.Authorization{ID: PA.ID, Identifier: core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}, RegistrationID: 0, Status: core.StatusPending, Expires: time.Now().AddDate(0, 0, 1), Challenges: []core.Challenge{chall}, Combinations: combos}
err = sa.UpdatePendingAuthorization(newPa)
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+PA.ID)

View File

@ -26,14 +26,6 @@ func (tc BoulderTypeConverter) ToDb(val interface{}) (interface{}, error) {
}
return string(jsonBytes), nil
case jose.JsonWebKey:
// HACK: Some of our storage methods, like NewAuthorization, expect to
// write to the DB with the default, empty key, so we treat it specially,
// serializing to an empty string. TODO: Modify authorizations to refer
// to a registration id, and make sure registration ids are always filled.
// https://github.com/letsencrypt/boulder/issues/181
if t.Key == nil {
return "", nil
}
jsonBytes, err := t.MarshalJSON()
if err != nil {
return "", err

View File

@ -46,7 +46,8 @@
"issuerCert": "test/test-ca.pem",
"_comment": "This should only be present in testMode. In prod use an HSM.",
"issuerKey": "test/test-ca.key",
"expiry": "8760h"
"expiry": "8760h",
"maxNames": 1000
},
"sa": {

View File

@ -46,7 +46,8 @@
"issuerCert": "test/test-ca.pem",
"_comment": "This should only be present in testMode. In prod use an HSM.",
"issuerKey": "test/test-ca.key",
"expiry": "8760h"
"expiry": "8760h",
"maxNames": 1000
},
"sa": {

View File

@ -497,7 +497,7 @@ func TestNewRegistration(t *testing.T) {
Body: makeBody(signRequest(t, "{\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/be-good\"}")),
})
test.AssertEquals(t, responseWriter.Body.String(), "{\"id\":0,\"key\":{\"kty\":\"RSA\",\"n\":\"z2NsNdHeqAiGdPP8KuxfQXat_uatOK9y12SyGpfKw1sfkizBIsNxERjNDke6Wp9MugN9srN3sr2TDkmQ-gK8lfWo0v1uG_QgzJb1vBdf_hH7aejgETRGLNJZOdaKDsyFnWq1WGJq36zsHcd0qhggTk6zVwqczSxdiWIAZzEakIUZ13KxXvoepYLY0Q-rEEQiuX71e4hvhfeJ4l7m_B-awn22UUVvo3kCqmaRlZT-36vmQhDGoBsoUo1KBEU44jfeK5PbNRk7vDJuH0B7qinr_jczHcvyD-2TtPzKaCioMtNh_VZbPNDaG67sYkQlC15-Ff3HPzKKJW2XvkVG91qMvQ\",\"e\":\"AAEAAQ\"},\"recoveryToken\":\"\",\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/be-good\",\"thumbprint\":\"\"}")
test.AssertEquals(t, responseWriter.Body.String(), "{\"id\":0,\"key\":{\"kty\":\"RSA\",\"n\":\"z2NsNdHeqAiGdPP8KuxfQXat_uatOK9y12SyGpfKw1sfkizBIsNxERjNDke6Wp9MugN9srN3sr2TDkmQ-gK8lfWo0v1uG_QgzJb1vBdf_hH7aejgETRGLNJZOdaKDsyFnWq1WGJq36zsHcd0qhggTk6zVwqczSxdiWIAZzEakIUZ13KxXvoepYLY0Q-rEEQiuX71e4hvhfeJ4l7m_B-awn22UUVvo3kCqmaRlZT-36vmQhDGoBsoUo1KBEU44jfeK5PbNRk7vDJuH0B7qinr_jczHcvyD-2TtPzKaCioMtNh_VZbPNDaG67sYkQlC15-Ff3HPzKKJW2XvkVG91qMvQ\",\"e\":\"AAEAAQ\"},\"recoveryToken\":\"\",\"contact\":[\"tel:123456789\"],\"agreement\":\"https://letsencrypt.org/be-good\"}")
var reg core.Registration
err := json.Unmarshal([]byte(responseWriter.Body.String()), &reg)
test.AssertNotError(t, err, "Couldn't unmarshal returned registration object")