Merge remote-tracking branch 'upstream/master' into errors

This commit is contained in:
Brad Warren 2015-06-16 10:59:16 -07:00
commit b094c81371
18 changed files with 451 additions and 99 deletions

View File

@ -1,19 +1,29 @@
FROM golang:1.4.2
MAINTAINER J.C. Jones "jjones@letsencrypt.org"
MAINTAINER William Budington "bill@eff.org"
# Install dependencies packages
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libltdl-dev \
rsyslog && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* \
/tmp/* \
/var/tmp/*
# Boulder exposes its web application at port TCP 4000
EXPOSE 4000
# Assume the configuration is in /etc/boulder
ENV BOULDER_CONFIG /go/src/github.com/letsencrypt/boulder/test/example-config.json
ENV BOULDER_CONFIG /go/src/github.com/letsencrypt/boulder/test/boulder-config.json
# Copy in the Boulder sources
RUN mkdir -p /go/src/github.com/letsencrypt/boulder
COPY . /go/src/github.com/letsencrypt/boulder
# Build Boulder
RUN go install \
RUN go install -tags pkcs11 \
github.com/letsencrypt/boulder/cmd/activity-monitor \
github.com/letsencrypt/boulder/cmd/boulder \
github.com/letsencrypt/boulder/cmd/boulder-ca \
@ -22,4 +32,4 @@ RUN go install \
github.com/letsencrypt/boulder/cmd/boulder-va \
github.com/letsencrypt/boulder/cmd/boulder-wfe
CMD ["/go/bin/boulder"]
CMD ["bash", "-c", "rsyslogd && /go/bin/boulder"]

View File

@ -227,13 +227,13 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
// RevokeCertificate revokes the trust of the Cert referred to by the provided Serial.
func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string, reasonCode int) (err error) {
certDER, err := ca.SA.GetCertificate(serial)
coreCert, err := ca.SA.GetCertificate(serial)
if err != nil {
// AUDIT[ Revocation Requests ] 4e85d791-09c0-4ab3-a837-d3d67e945134
ca.log.AuditErr(err)
return err
}
cert, err := x509.ParseCertificate(certDER)
cert, err := x509.ParseCertificate(coreCert.DER)
if err != nil {
// AUDIT[ Revocation Requests ] 4e85d791-09c0-4ab3-a837-d3d67e945134
ca.log.AuditErr(err)

View File

@ -405,14 +405,14 @@ func TestIssueCertificate(t *testing.T) {
csr, _ := x509.ParseCertificateRequest(csrDER)
// Sign CSR
certObj, err := ca.IssueCertificate(*csr, 1, FarFuture)
issuedCert, err := ca.IssueCertificate(*csr, 1, FarFuture)
test.AssertNotError(t, err, "Failed to sign certificate")
if err != nil {
continue
}
// Verify cert contents
cert, err := x509.ParseCertificate(certObj.DER)
cert, err := x509.ParseCertificate(issuedCert.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
test.AssertEquals(t, cert.Subject.CommonName, "not-example.com")
@ -446,10 +446,10 @@ func TestIssueCertificate(t *testing.T) {
// Verify that the cert got stored in the DB
serialString := core.SerialToString(cert.SerialNumber)
certBytes, err := storageAuthority.GetCertificate(serialString)
storedCert, err := storageAuthority.GetCertificate(serialString)
test.AssertNotError(t, err,
fmt.Sprintf("Certificate %s not found in database", serialString))
test.Assert(t, bytes.Equal(certBytes, certObj.DER), "Retrieved cert not equal to issued cert.")
test.Assert(t, bytes.Equal(issuedCert.DER, storedCert.DER), "Retrieved cert not equal to issued cert.")
certStatus, err := storageAuthority.GetCertificateStatus(serialString)
test.AssertNotError(t, err,

View File

@ -96,8 +96,8 @@ type StorageGetter interface {
GetRegistration(int64) (Registration, error)
GetRegistrationByKey(jose.JsonWebKey) (Registration, error)
GetAuthorization(string) (Authorization, error)
GetCertificate(string) ([]byte, error)
GetCertificateByShortSerial(string) ([]byte, error)
GetCertificate(string) (Certificate, error)
GetCertificateByShortSerial(string) (Certificate, error)
GetCertificateStatus(string) (CertificateStatus, error)
AlreadyDeniedCSR([]string) (bool, error)
}

View File

@ -390,11 +390,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"`
Expires time.Time `db:"expires"`
Serial string `db:"serial"`
Digest string `db:"digest"`
DER JsonBuffer `db:"der"`
Issued time.Time `db:"issued"`
Expires time.Time `db:"expires"`
}
// Certificate.MatchesCSR tests the contents of a generated certificate to

View File

@ -42,6 +42,12 @@ var BuildTime string
// Errors
// InternalServerError indicates that something has gone wrong unrelated to the
// user's input, and will be considered by the Load Balancer as an indication
// that this Boulder instance may be malfunctioning. Minimally, returning this
// will cause an error page to be generated at the CDN/LB for the client.
// Consequently, you should only use this error when Boulder's internal
// constraints have been violated.
type InternalServerError string
type NotSupportedError string
type MalformedRequestError string

View File

@ -61,17 +61,32 @@ func validateEmail(address string) (err error) {
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
var mx []*net.MX
mx, err = net.LookupMX(domain)
if err != nil {
err = core.InternalServerError(err.Error())
return
}
if len(mx) == 0 {
if err != nil || len(mx) == 0 {
err = core.MalformedRequestError(fmt.Sprintf("No MX record for domain %s", domain))
return
}
return
}
func validateContacts(contacts []core.AcmeURL) (err error) {
for _, contact := range contacts {
switch contact.Scheme {
case "tel":
continue
case "mailto":
err = validateEmail(contact.Opaque)
if err != nil {
return
}
default:
err = core.MalformedRequestError(fmt.Sprintf("Contact method %s is not supported", contact.Scheme))
return
}
}
return
}
type certificateRequestEvent struct {
ID string `json:",omitempty"`
Requester int64 `json:",omitempty"`
@ -98,24 +113,16 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re
}
reg.MergeUpdate(init)
for _, contact := range reg.Contact {
switch contact.Scheme {
case "tel":
continue
case "mailto":
err = validateEmail(contact.Opaque)
if err != nil {
return
}
default:
err = core.MalformedRequestError(fmt.Sprintf("Contact method %s is not supported", contact.Scheme))
return
}
err = validateContacts(reg.Contact)
if err != nil {
return
}
// Store the authorization object, then return it
reg, err = ra.SA.NewRegistration(reg)
if err != nil {
// InternalServerError since the user-data was validated before being
// passed to the SA.
err = core.InternalServerError(err.Error())
}
@ -124,7 +131,7 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re
func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization, regID int64) (authz core.Authorization, err error) {
if regID <= 0 {
err = core.InternalServerError("Invalid registration ID")
err = core.MalformedRequestError(fmt.Sprintf("Invalid registration ID: %d", regID))
return authz, err
}
@ -162,7 +169,9 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
// Get a pending Auth first so we can get our ID back, then update with challenges
authz, err = ra.SA.NewPendingAuthorization(authz)
if err != nil {
err = core.InternalServerError(err.Error())
// InternalServerError since the user-data was validated before being
// passed to the SA.
err = core.InternalServerError(fmt.Sprintf("Invalid authorization request: %s", err))
return authz, err
}
@ -173,6 +182,8 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
challenges[i].URI = core.AcmeURL(*challengeURI)
if !challenges[i].IsSane(false) {
// InternalServerError because we generated these challenges, they should
// be OK.
err = core.InternalServerError(fmt.Sprintf("Challenge didn't pass sanity check: %+v", challenges[i]))
return authz, err
}
@ -184,6 +195,8 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
// Store the authorization object, then return it
err = ra.SA.UpdatePendingAuthorization(authz)
if err != nil {
// InternalServerError because we created the authorization just above,
// and adding Sane challenges should not break it.
err = core.InternalServerError(err.Error())
}
return authz, err
@ -211,7 +224,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
}()
if regID <= 0 {
err = core.InternalServerError("Invalid registration ID")
err = core.MalformedRequestError(fmt.Sprintf("Invalid registration ID: %d", regID))
return emptyCert, err
}
@ -312,7 +325,10 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
// Create the certificate and log the result
if cert, err = ra.CA.IssueCertificate(*csr, regID, earliestExpiry); err != nil {
err = core.InternalServerError(err.Error())
// While this could be InternalServerError for certain conditions, most
// of the failure reasons (such as GoodKey failing) are caused by malformed
// requests.
err = core.MalformedRequestError(err.Error())
logEvent.Error = err.Error()
return emptyCert, err
}
@ -325,6 +341,8 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
if err != nil {
// InternalServerError because the certificate from the CA should be
// parseable.
err = core.InternalServerError(err.Error())
logEvent.Error = err.Error()
return emptyCert, err
@ -342,10 +360,18 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
base.MergeUpdate(update)
err = validateContacts(base.Contact)
if err != nil {
return
}
reg = base
err = ra.SA.UpdateRegistration(base)
if err != nil {
err = core.InternalServerError(err.Error())
// InternalServerError since the user-data was validated before being
// passed to the SA.
err = core.InternalServerError(fmt.Sprintf("Could not update registration: %s", err))
}
return
}
@ -354,14 +380,16 @@ func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization
// Copy information over that the client is allowed to supply
authz = base
if challengeIndex >= len(authz.Challenges) {
err = core.MalformedRequestError("Invalid challenge index")
err = core.MalformedRequestError(fmt.Sprintf("Invalid challenge index: %d", challengeIndex))
return
}
authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response)
// Store the updated version
if err = ra.SA.UpdatePendingAuthorization(authz); err != nil {
err = core.InternalServerError(err.Error())
// This can pretty much only happen when the client corrupts the Challenge
// data.
err = core.MalformedRequestError(err.Error())
return
}

View File

@ -219,6 +219,48 @@ func assertAuthzEqual(t *testing.T, a1, a2 core.Authorization) {
// Not testing: Challenges
}
func TestValidateContacts(t *testing.T) {
tel, _ := url.Parse("tel:")
ansible, _ := url.Parse("ansible:earth.sol.milkyway.laniakea/letsencrypt")
validEmail, _ := url.Parse("mailto:admin@email.com")
invalidEmail, _ := url.Parse("mailto:admin@example.com")
malformedEmail, _ := url.Parse("mailto:admin.com")
err := validateContacts([]core.AcmeURL{})
test.AssertNotError(t, err, "No Contacts")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*tel)})
test.AssertNotError(t, err, "Simple Telephone")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*validEmail)})
test.AssertNotError(t, err, "Valid Email")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*invalidEmail)})
test.AssertError(t, err, "Invalid Email")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*malformedEmail)})
test.AssertError(t, err, "Malformed Email")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*ansible)})
test.AssertError(t, err, "Unknown scehme")
}
func TestValidateEmail(t *testing.T) {
err := validateEmail("an email`")
test.AssertError(t, err, "Malformed")
err = validateEmail("a@not.a.domain")
test.AssertError(t, err, "Cannot resolve")
t.Logf("No Resolve: %s", err)
err = validateEmail("a@example.com")
test.AssertError(t, err, "No MX Record")
t.Logf("No MX: %s", err)
err = validateEmail("a@email.com")
test.AssertNotError(t, err, "Valid")
}
func TestNewRegistration(t *testing.T) {
_, _, sa, ra := initAuthorities(t)
mailto, _ := url.Parse("mailto:foo@letsencrypt.org")
@ -455,7 +497,7 @@ func TestNewCertificate(t *testing.T) {
if err != nil {
return
}
test.Assert(t, bytes.Compare(cert.DER, dbCert) == 0, "Certificates differ")
test.Assert(t, bytes.Compare(cert.DER, dbCert.DER) == 0, "Certificates differ")
t.Log("DONE TestOnValidationUpdate")
}

View File

@ -21,7 +21,7 @@ import (
// where ROLE covers:
// * RegistrationAuthority
// * ValidationAuthority
// * CertficateAuthority
// * CertificateAuthority
// * StorageAuthority
//
// For each one of these, the are ${ROLE}Client and ${ROLE}Server
@ -777,18 +777,34 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error
rpc.Handle(MethodGetCertificate, func(req []byte) (response []byte, err error) {
cert, err := impl.GetCertificate(string(req))
if err == nil {
response = []byte(cert)
if err != nil {
return
}
return
jsonResponse, err := json.Marshal(cert)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetCertificate, err, req)
return
}
return jsonResponse, nil
})
rpc.Handle(MethodGetCertificateByShortSerial, func(req []byte) (response []byte, err error) {
cert, err := impl.GetCertificateByShortSerial(string(req))
if err == nil {
response = []byte(cert)
if err != nil {
return
}
return
jsonResponse, err := json.Marshal(cert)
if err != nil {
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
errorCondition(MethodGetCertificateByShortSerial, err, req)
return
}
return jsonResponse, nil
})
rpc.Handle(MethodGetCertificateStatus, func(req []byte) (response []byte, err error) {
@ -910,13 +926,23 @@ func (cac StorageAuthorityClient) GetAuthorization(id string) (authz core.Author
return
}
func (cac StorageAuthorityClient) GetCertificate(id string) (cert []byte, err error) {
cert, err = cac.rpc.DispatchSync(MethodGetCertificate, []byte(id))
func (cac StorageAuthorityClient) GetCertificate(id string) (cert core.Certificate, err error) {
jsonCert, err := cac.rpc.DispatchSync(MethodGetCertificate, []byte(id))
if err != nil {
return
}
err = json.Unmarshal(jsonCert, &cert)
return
}
func (cac StorageAuthorityClient) GetCertificateByShortSerial(id string) (cert []byte, err error) {
cert, err = cac.rpc.DispatchSync(MethodGetCertificateByShortSerial, []byte(id))
func (cac StorageAuthorityClient) GetCertificateByShortSerial(id string) (cert core.Certificate, err error) {
jsonCert, err := cac.rpc.DispatchSync(MethodGetCertificateByShortSerial, []byte(id))
if err != nil {
return
}
err = json.Unmarshal(jsonCert, &cert)
return
}

View File

@ -166,44 +166,40 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
// serial number and returns the first certificate whose full serial number is
// lexically greater than that id. This allows clients to query on the known
// sequential half of our serial numbers to enumerate all certificates.
func (ssa *SQLStorageAuthority) GetCertificateByShortSerial(shortSerial string) (cert []byte, err error) {
func (ssa *SQLStorageAuthority) GetCertificateByShortSerial(shortSerial string) (cert core.Certificate, err error) {
if len(shortSerial) != 16 {
err = errors.New("Invalid certificate short serial " + shortSerial)
return
}
var certificate core.Certificate
err = ssa.dbMap.SelectOne(&certificate, "SELECT * FROM certificates WHERE serial LIKE :shortSerial",
err = ssa.dbMap.SelectOne(&cert, "SELECT * FROM certificates WHERE serial LIKE :shortSerial",
map[string]interface{}{"shortSerial": shortSerial + "%"})
if err != nil {
return
}
return certificate.DER, nil
return
}
// GetCertificate takes a serial number and returns the corresponding
// certificate, or error if it does not exist.
func (ssa *SQLStorageAuthority) GetCertificate(serial string) ([]byte, error) {
func (ssa *SQLStorageAuthority) GetCertificate(serial string) (core.Certificate, error) {
if len(serial) != 32 {
err := fmt.Errorf("Invalid certificate serial %s", serial)
return nil, err
return core.Certificate{}, err
}
certObj, err := ssa.dbMap.Get(core.Certificate{}, serial)
if err != nil {
return nil, err
return core.Certificate{}, err
}
if certObj == nil {
ssa.log.Debug(fmt.Sprintf("Nil cert for %s", serial))
return nil, fmt.Errorf("Certificate does not exist for %s", serial)
return core.Certificate{}, fmt.Errorf("Certificate does not exist for %s", serial)
}
cert, ok := certObj.(*core.Certificate)
certPtr, ok := certObj.(*core.Certificate)
if !ok {
ssa.log.Debug("Failed to convert cert")
return nil, fmt.Errorf("Error converting certificate response for %s", serial)
return core.Certificate{}, fmt.Errorf("Error converting certificate response for %s", serial)
}
return cert.DER, err
return *certPtr, err
}
// GetCertificateStatus takes a hexadecimal string representing the full 128-bit serial

View File

@ -143,13 +143,13 @@ func TestAddCertificate(t *testing.T) {
test.AssertEquals(t, digest, "qWoItDZmR4P9eFbeYgXXP3SR4ApnkQj8x4LsB_ORKBo")
// Example cert serial is 0x21bd4, so a prefix of all zeroes should fetch it.
retrievedDER, err := sa.GetCertificateByShortSerial("0000000000000000")
retrievedCert, err := sa.GetCertificateByShortSerial("0000000000000000")
test.AssertNotError(t, err, "Couldn't get www.eff.org.der by short serial")
test.AssertByteEquals(t, certDER, retrievedDER)
test.AssertByteEquals(t, certDER, retrievedCert.DER)
retrievedDER, err = sa.GetCertificate("00000000000000000000000000021bd4")
retrievedCert, err = sa.GetCertificate("00000000000000000000000000021bd4")
test.AssertNotError(t, err, "Couldn't get www.eff.org.der by full serial")
test.AssertByteEquals(t, certDER, retrievedDER)
test.AssertByteEquals(t, certDER, retrievedCert.DER)
certificateStatus, err := sa.GetCertificateStatus("00000000000000000000000000021bd4")
test.AssertNotError(t, err, "Couldn't get status for www.eff.org.der")
@ -166,13 +166,13 @@ func TestAddCertificate(t *testing.T) {
test.AssertEquals(t, digest2, "CMVYqWzyqUW7pfBF2CxL0Uk6I0Upsk7p4EWSnd_vYx4")
// Example cert serial is 0x21bd4, so a prefix of all zeroes should fetch it.
retrievedDER2, err := sa.GetCertificateByShortSerial("ff00000000000002")
retrievedCert2, err := sa.GetCertificateByShortSerial("ff00000000000002")
test.AssertNotError(t, err, "Couldn't get test-cert.der")
test.AssertByteEquals(t, certDER2, retrievedDER2)
test.AssertByteEquals(t, certDER2, retrievedCert2.DER)
retrievedDER2, err = sa.GetCertificate("ff00000000000002238054509817da5a")
retrievedCert2, err = sa.GetCertificate("ff00000000000002238054509817da5a")
test.AssertNotError(t, err, "Couldn't get test-cert.der")
test.AssertByteEquals(t, certDER2, retrievedDER2)
test.AssertByteEquals(t, certDER2, retrievedCert2.DER)
certificateStatus2, err := sa.GetCertificateStatus("ff00000000000002238054509817da5a")
test.AssertNotError(t, err, "Couldn't get status for test-cert.der")

View File

@ -38,6 +38,8 @@ func (tc BoulderTypeConverter) ToDb(val interface{}) (interface{}, error) {
return string(t), nil
case core.OCSPStatus:
return string(t), nil
case core.JsonBuffer:
return []byte(t), nil
default:
return val, nil
}

18
wfe/test/178.crt Normal file
View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgICALIwDQYJKoZIhvcNAQELBQAwDjEMMAoGA1UEAwwDMTc4
MB4XDTE1MDYxMzAwMTY1OFoXDTE2MDYxMjAwMTY1OFowDjEMMAoGA1UEAwwDMTc4
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJ
Ama2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1M
IY+NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf/qbt4vmSGKlo
Iim41QXt55QFW6O+84s8Kd2OE6df0wTsEwLhZB3j5pDU+t7j5vTMv4Tc7EptaPkO
dfQn+68viUJjlYM/4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI11
5DU49tzscXU/3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd/emrj1xuioSe6
PwIDAQABo1AwTjAdBgNVHQ4EFgQUEaQm2CFKX3v9tNF3LLRNqd5mGcMwHwYDVR0j
BBgwFoAUEaQm2CFKX3v9tNF3LLRNqd5mGcMwDAYDVR0TBAUwAwEB/zANBgkqhkiG
9w0BAQsFAAOCAQEAdTi8Mt6JwfXPJU6ILNIXlySl01s7pfNf8Qz43k7AaZSJeI2A
blM6ilFwbXpWls64XKFQRYfsQ9+wPA044pF1zR05PSI8PJwzIVAjW34myJnbsywb
Yc1eQXlz0Di7R+w9HRkpVHG2CgnIBGJFa1H7p0FG9tyI7SaJ/Qri5BRJhnu2gYjx
B+JV3ol+0oYYMhVVaGXwHpyjelsEiWaIFoO3o0YxfW19NM90QQnJ3BGX7ibJSxAr
Lwbh8DWnWi4X3MdIPG88BKoavcXlJ/pyW2PvarUe31xVBNbyDlcZvrTZ8PXVw7TA
lumboAhMDLhYNBWrJTJe5LOiapEJaOBNN/ZMFQ==
-----END CERTIFICATE-----

28
wfe/test/178.key Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaqzue57mgXEoG
TZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12
HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5+UED2bFRL1MsyBdbmCF/+pu3i
+ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1/TBOwTAuFkHePmkNT63uPm9My/hNzs
Sm1o+Q519Cf7ry+JQmOVgz/jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoX
RUlQjXXkNTj23OxxdT/cVLcLJjytyG6e5izME2R2aCkDBWIc1a4/sRJ0R396auPX
G6KhJ7o/AgMBAAECggEAC7GkmVgVzc0Mf7uAArV7YaYYapQFCbLX6jUU2VIfpBbn
uXroZQUo5FzKhAT4jYuMiaoFU+K6Wp6l+fcyz0sh9WugGOaupNiPrRhNfl6WeZvp
5+9r1nRLjztMHhWErhMRpd+RJuU9NMi0NbP+2sR8LhEPe3OuUBL98LbJqio9y0Bp
8+JxmATL7vTvORMMamt80bDRGLBzoGySWZ0lEWwTuv8RjiCqO85YmHd9BmnTpOUn
siYfEDZ9t+EF6NgJaISMLmcbzxZI5+aPIvfc1TwzDA96LUccGwvAR4qZ/+eYsG2I
Iwn1wMMSsvFWeq7AccKL/povPg1Iud0wzkLLZmMjGQKBgQDLRUtENxbVH/T4xw8W
tdgfzddQWZmVPtQXHAt2/zmrgJ3DKu7HOUajCGGWdLnoiFrIO2LsQYpP1XUA4hG1
vzLDBng6SHDqZdGfd8eFaHesafr/FG4zkk+GBIs+P8GzHs2/znpEiqLnPbWrrB3t
2/zBIXluZSytGt1ysC+TZZFwhQKBgQDCymmryD0JRV0M1IgwXOZrO+2MWDdliujx
uBfD6DdLCJ5wHrPiySjVvl0njPLDQcWigigJD8Jl0SRQ8JSbceFzEGjzEj3NR2oB
aLn3Gf3nORo7tJdCZVQn7QcHT+HzAWffm6qjqKzPXjRAEFFcW48Bnw+BU/JuZNOe
v7dTuyv88wKBgFROGAphwsF/8I0hmht0Lf/60ltL3gvtM++lvQeMkTGVNVlVvBS6
p5ZEipzpKpXLv8MeBkgwYpn70PwdxvSXKQmD7GdX1iURN6CpAAJPspq6ldQneBFB
lGPkDJAzxzVwCCuOCl3VFf1MNcXOq9cUDz9Wj9N+eMoOw1umwQSj8m81AoGAP/li
gzycbzMMwG383IVmV8my1ukSKJNatiiUBY96uXX3MzOiONWAR9LhnV+5S0+KrTi6
FV/LpMzvdHXPGM5qEPROw6Y2DflqY1QV34X10b77UqiZFQFahlJegJRHzRulFdd2
T5HST7jMyE2TqxWW/h1TZlI/yOnsZrLobuOGKukCgYAGk/TRPSOWEkH3rL7cDJFp
ql4d33iuW9b3dMvQgkDW8H2kj3QA5os9dtSFLA/fWubULXOPpOsA7Ny7okBxsUBW
tn4ER+HjUHogGir5d5cBBpvi4xM2/B4KaZnX8IHYhFcT42eb1oYmpfz5GcRyqzTX
OoetYnUS5t4QuAAMadjtig==
-----END PRIVATE KEY-----

18
wfe/test/238.crt Normal file
View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgICAO4wDQYJKoZIhvcNAQELBQAwDjEMMAoGA1UEAwwDMjM4
MB4XDTE1MDYxMzAwMTU1NVoXDTE2MDYxMjAwMTU1NVowDjEMMAoGA1UEAwwDMjM4
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvP9z1YFDa1WD9hVI9W3K
lWQmUGfLW35x6xkgDm8o3OTWR2QoxjXratacKhm2VevV22QjCBvHXeHx3fxSp5w/
p4CH+Ul76wCq3+WAPidO42YCP7SZdqYUR4GHKQ/oOyistRAKEamg4aPAbIs7l1Kn
T5YHFdSzCWpe6F2+ceoluvKEn6vFVloXKghaeEyTDKnnJKs3/04TdtZjVM5OObvQ
CGFlQlysDJxWahtVM93gylB8WYgyiekDAx1I3lCd3Vv0hF+x04xT3fwVRzmaKNzT
wN+znae643Qfg2oSSLV066K2WYepgzqKwv3IUdrLbes331AMs+FbdxHanMrOU1i+
OQIDAQABo1AwTjAdBgNVHQ4EFgQUjog7s8eJhAvSKMvu6xHZxPnnjsgwHwYDVR0j
BBgwFoAUjog7s8eJhAvSKMvu6xHZxPnnjsgwDAYDVR0TBAUwAwEB/zANBgkqhkiG
9w0BAQsFAAOCAQEAIugSn0o0HQMLy02inFZDso4PiRfoqahsT60oIMmWhF3nY3Jq
GZmkozKGnvyNDeKPlf6TV04VLq6dRg7+yQDL6LCiq2wcGZ+8feMLjyRFwZDSjAJe
sAMhNq9OQdGNfUV1iZF0SUzqrT68BCT0JTtuDpwlMcmH1O+jFf2HCzROLLBdRC3w
tJGiA6DH2TqVnucql6sMrnxPVEB+uVfFaKNc9YzwDCp8dSmBbCz7wRmLobGKcnbQ
lByD5j4dxYkFvJ6n/YX1HKJzwqTWhLQaxvFW7YvnPWepEiXiB6BaIsRgyK7Qa8EW
3jL5yiB1Dd8OQ7aV7+PNwBNXHd3J1Vie2k52KA==
-----END CERTIFICATE-----

28
wfe/test/238.key Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8/3PVgUNrVYP2
FUj1bcqVZCZQZ8tbfnHrGSAObyjc5NZHZCjGNetq1pwqGbZV69XbZCMIG8dd4fHd
/FKnnD+ngIf5SXvrAKrf5YA+J07jZgI/tJl2phRHgYcpD+g7KKy1EAoRqaDho8Bs
izuXUqdPlgcV1LMJal7oXb5x6iW68oSfq8VWWhcqCFp4TJMMqeckqzf/ThN21mNU
zk45u9AIYWVCXKwMnFZqG1Uz3eDKUHxZiDKJ6QMDHUjeUJ3dW/SEX7HTjFPd/BVH
OZoo3NPA37Odp7rjdB+DahJItXTrorZZh6mDOorC/chR2stt6zffUAyz4Vt3Edqc
ys5TWL45AgMBAAECggEAc1PSJCt/r2R8ZNJyNclsQCLfulrL3aXX7+TiCczM+5Xs
J543v1Oxtv0ESDBuchm54ulE8zK4QlKYm6PX8A1JTnYBAx5TLoC2xG8wBT1JRzu9
DZCvwJXxc/zXNDhPtqHIWahS7Jo84NNinRmNIHbAP7FF241yPsGY7mQdzTdbFKrR
JH0l7VPCY4OG+CjxUJqoNuwkfrNh0hRh02IHU/rFlgR2Q7JP0XBwuufW1M6j7fYM
7PGZRA+6Ry72UcaCEVuOtGlz3wLrFq6CGTGWlUehQqch+nrTri0jMSH4Bd83mLz2
8+X0y/EONQlirbHbJxXq+mLASHrp3KCtdpCiLKcX8QKBgQDr+TNqLa7PIOhlw29A
RftunKwEdsi9uAg3jFSpHC/jLxR4/fUiz2XZrAfHNxn7mOK72V/9pj9zshLnxeSm
jEelEB2bABX8RhD38SUxoHoiWmqpPVOtBSXvMSQEO0F/1hGlxndHwe9mE2Zyq3eV
9MoJVeExkCP3Bxk9tjZfj4WC9QKBgQDNCab2WjLy7T9Bfmh2RmWXckzUMphYCLpX
CGG2O5nH2zOPAOxUpyLFDq3/WkzPnCdWOveI/LlZmkcjdslWp3tizk5kE1zgaFbO
s+7o/cYVrU5J3+kIq563ba7/xZ7wpfkg58milUWStpjQrB0H5tSlUEoC7fJ/GjHd
5j1raKQrtQKBgF9elSgJlIgD/cj7JqBsaET5LxCSzWjX0wJYRfMfAD+qTHTl9sf9
2GUUAQTDwU2NKb3QCdqi8SwaQUfJFDM3qNEOZVi6vSf7TWpX3Ldk61etAUSrE4Fu
/jjgvHS1WjCHXRSJ1LV8rPutRY98u1Uw3OLPAbedUNvK06m8VddjUwttAoGAAmca
jciA0Ff3Zc0VbE1m419zhwkQv/daN6rhekE4jB8Fe6eHHXbX8Xc6ksN8IvKxg1Et
lW1gvqwQKVo7Acj0qTPBt2qCrB6M5d817YULzTU6taLqGC/qrDuc0WJ/elJ3mOse
cclOB2ocYFWkAXOzCjzmoSIotVSZQQBxt9CCHAECgYEA01w8tKVCG2ucbC1GoCl0
t2MRmLqiRqRrn53fJ6j56fDbdLmnRAaaD1slZ0jpLk7JoDKGmNG2Rl9UXuydPaNZ
8h1Lu+CnhG50uOF3A/OIXsBiRsAgI2ez4/Jb+lNe3l3UcPV5gyGejAiymqRigbkn
bcixOm4jdOWV5Bpfv65AivQ=
-----END PRIVATE KEY-----

View File

@ -71,8 +71,18 @@ func statusCodeFromError(err interface{}) int {
switch err.(type) {
case core.MalformedRequestError:
return http.StatusBadRequest
case core.NotSupportedError:
return http.StatusNotImplemented
case core.SyntaxError:
return http.StatusBadRequest
case core.UnauthorizedError:
return http.StatusForbidden
case core.NotFoundError:
return http.StatusNotFound
case core.SignatureValidationError:
return http.StatusPreconditionFailed
case core.InternalServerError:
return http.StatusInternalServerError
default:
return http.StatusInternalServerError
}
@ -208,11 +218,16 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool) ([]
return nil, nil, reg, errors.New("JWS has invalid anti-replay nonce")
}
if regCheck {
// Check that the key is assosiated with an actual account
reg, err = wfe.SA.GetRegistrationByKey(*key)
if err != nil {
reg, err = wfe.SA.GetRegistrationByKey(*key)
if err != nil {
// If we are requiring a valid registration, any failure to look up the
// registration is an overall failure to verify.
if regCheck {
return nil, nil, reg, err
} else {
// Otherwise we just return an empty registration. The caller is expected
// to use the returned key instead.
reg = core.Registration{}
}
}
@ -306,6 +321,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
regURL := fmt.Sprintf("%s%d", wfe.RegBase, id)
responseBody, err := json.Marshal(reg)
if err != nil {
// StatusInternalServerError because we just created this registration, it should be OK.
wfe.sendError(response, "Error marshaling registration", err, http.StatusInternalServerError)
return
}
@ -369,6 +385,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
authz.RegistrationID = 0
responseBody, err := json.Marshal(authz)
if err != nil {
// StatusInternalServerError because we generated the authz, it should be OK
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
return
}
@ -395,7 +412,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
// We don't ask verifyPOST to verify there is a correponding registration,
// because anyone with the right private key can revoke a certificate.
body, requestKey, _, err := wfe.verifyPOST(request, false)
body, requestKey, registration, err := wfe.verifyPOST(request, false)
if err != nil {
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
return
@ -418,20 +435,21 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
}
serial := core.SerialToString(providedCert.SerialNumber)
certDER, err := wfe.SA.GetCertificate(serial)
if err != nil || !bytes.Equal(certDER, revokeRequest.CertificateDER) {
cert, err := wfe.SA.GetCertificate(serial)
if err != nil || !bytes.Equal(cert.DER, revokeRequest.CertificateDER) {
wfe.sendError(response, "No such certificate", err, http.StatusNotFound)
return
}
parsedCertificate, err := x509.ParseCertificate(certDER)
parsedCertificate, err := x509.ParseCertificate(cert.DER)
if err != nil {
// InternalServerError because this is a failure to decode from our DB.
wfe.sendError(response, "Invalid certificate", err, http.StatusInternalServerError)
return
}
certStatus, err := wfe.SA.GetCertificateStatus(serial)
if err != nil {
wfe.sendError(response, "No such certificate", err, http.StatusNotFound)
wfe.sendError(response, "Certificate status not yet available", err, http.StatusNotFound)
return
}
@ -440,9 +458,9 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
return
}
// TODO: Implement other methods of validating revocation, e.g. through
// authorizations on account.
if !core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) {
// TODO: Implement method of revocation by authorizations on account.
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
registration.ID == cert.RegistrationID) {
wfe.log.Debug("Key mismatch for revoke")
wfe.sendError(response,
"Revocation request must be signed by private key of cert to be revoked",
@ -536,7 +554,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
wfe.Stats.Inc("Certificates", 1, 1.0)
}
func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.ResponseWriter, request *http.Request) {
func (wfe *WebFrontEndImpl) challenge(authz core.Authorization, response http.ResponseWriter, request *http.Request) {
wfe.sendStandardHeaders(response)
if request.Method != "GET" && request.Method != "POST" {
@ -572,6 +590,8 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
challenge := authz.Challenges[challengeIndex]
jsonReply, err := json.Marshal(challenge)
if err != nil {
// InternalServerError because this is a failure to decode data passed in
// by the caller, which got it from the DB.
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
return
}
@ -630,6 +650,7 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
// assumption: UpdateAuthorization does not modify order of challenges
jsonReply, err := json.Marshal(challenge)
if err != nil {
// StatusInternalServerError because we made the challenges, they should be OK
wfe.sendError(response, "Failed to marshal challenge", err, http.StatusInternalServerError)
return
}
@ -713,6 +734,7 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
jsonReply, err := json.Marshal(updatedReg)
if err != nil {
// StatusInternalServerError because we just generated the reg, it should be OK
wfe.sendError(response, "Failed to marshal registration", err, http.StatusInternalServerError)
return
}
@ -742,7 +764,7 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
// If there is a fragment, then this is actually a request to a challenge URI
if len(request.URL.RawQuery) != 0 {
wfe.Challenge(authz, response, request)
wfe.challenge(authz, response, request)
return
}
@ -759,6 +781,7 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
jsonReply, err := json.Marshal(authz)
if err != nil {
// InternalServerError because this is a failure to decode from our DB.
wfe.sendError(response, "Failed to marshal authz", err, http.StatusInternalServerError)
return
}
@ -806,7 +829,7 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
cert, err := wfe.SA.GetCertificateByShortSerial(serial)
if err != nil {
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusInternalServerError)
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
} else {
wfe.sendError(response, "Not found", err, http.StatusNotFound)
}
@ -817,7 +840,7 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
response.Header().Set("Content-Type", "application/pkix-cert")
response.Header().Add("Link", link(IssuerPath, "up"))
response.WriteHeader(http.StatusOK)
if _, err = response.Write(cert); err != nil {
if _, err = response.Write(cert.DER); err != nil {
wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err))
}
}

View File

@ -11,6 +11,7 @@ import (
"database/sql"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"io"
"io/ioutil"
@ -170,16 +171,44 @@ func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) {
return core.Authorization{}, nil
}
func (sa *MockSA) GetCertificate(string) ([]byte, error) {
return []byte{}, nil
func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) {
// Serial ee == 238.crt
if serial == "000000000000000000000000000000ee" {
certPemBytes, _ := ioutil.ReadFile("test/238.crt")
certBlock, _ := pem.Decode(certPemBytes)
return core.Certificate{
RegistrationID: 1,
DER: certBlock.Bytes,
}, nil
} else if serial == "000000000000000000000000000000b2" {
certPemBytes, _ := ioutil.ReadFile("test/178.crt")
certBlock, _ := pem.Decode(certPemBytes)
return core.Certificate{
RegistrationID: 1,
DER: certBlock.Bytes,
}, nil
} else {
return core.Certificate{}, errors.New("No cert")
}
}
func (sa *MockSA) GetCertificateByShortSerial(string) ([]byte, error) {
return []byte{}, nil
func (sa *MockSA) GetCertificateByShortSerial(string) (core.Certificate, error) {
return core.Certificate{}, nil
}
func (sa *MockSA) GetCertificateStatus(string) (core.CertificateStatus, error) {
return core.CertificateStatus{}, nil
func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, error) {
// Serial ee == 238.crt
if serial == "000000000000000000000000000000ee" {
return core.CertificateStatus{
Status: core.OCSPStatusGood,
}, nil
} else if serial == "000000000000000000000000000000b2" {
return core.CertificateStatus{
Status: core.OCSPStatusRevoked,
}, nil
} else {
return core.CertificateStatus{}, errors.New("No cert status")
}
}
func (sa *MockSA) AlreadyDeniedCSR([]string) (bool, error) {
@ -521,7 +550,7 @@ func TestChallenge(t *testing.T) {
RegistrationID: 1,
}
wfe.Challenge(authz, responseWriter, &http.Request{
wfe.challenge(authz, responseWriter, &http.Request{
Method: "POST",
URL: challengeURL,
Body: makeBody(signRequest(t, "{}", &wfe.nonceService)),
@ -665,6 +694,104 @@ func TestNewRegistration(t *testing.T) {
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Registration key is already in use\"}")
}
// Valid revocation request for existing, non-revoked cert
func TestRevokeCertificate(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/238.key")
test.AssertNotError(t, err, "Failed to load key")
key, err := jose.LoadPrivateKey(keyPemBytes)
test.AssertNotError(t, err, "Failed to load key")
rsaKey, ok := key.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
signer, err := jose.NewSigner("RS256", rsaKey)
test.AssertNotError(t, err, "Failed to make signer")
certPemBytes, err := ioutil.ReadFile("test/238.crt")
test.AssertNotError(t, err, "Failed to load cert")
certBlock, _ := pem.Decode(certPemBytes)
test.Assert(t, certBlock != nil, "Failed to decode PEM")
var revokeRequest struct {
CertificateDER core.JsonBuffer `json:"certificate"`
}
revokeRequest.CertificateDER = certBlock.Bytes
revokeRequestJSON, err := json.Marshal(revokeRequest)
test.AssertNotError(t, err, "Failed to marshal request")
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
wfe := setupWFE()
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
wfe.Stats, _ = statsd.NewNoopClient()
wfe.SubscriberAgreementURL = agreementURL
responseWriter := httptest.NewRecorder()
responseWriter.Body.Reset()
result, _ := signer.Sign(revokeRequestJSON, wfe.nonceService.Nonce())
wfe.RevokeCertificate(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(result.FullSerialize()),
})
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
// Try the revoke request again, signed by account key associated with cert.
// Should also succeed.
responseWriter.Body.Reset()
test1JWK, err := jose.LoadPrivateKey([]byte(test1KeyPrivatePEM))
test.AssertNotError(t, err, "Failed to load key")
test1Key, ok := test1JWK.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
accountKeySigner, err := jose.NewSigner("RS256", test1Key)
test.AssertNotError(t, err, "Failed to make signer")
result, _ = accountKeySigner.Sign(revokeRequestJSON, wfe.nonceService.Nonce())
wfe.RevokeCertificate(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(result.FullSerialize()),
})
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
}
// Valid revocation request for already-revoked cert
func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
keyPemBytes, err := ioutil.ReadFile("test/178.key")
test.AssertNotError(t, err, "Failed to load key")
key, err := jose.LoadPrivateKey(keyPemBytes)
test.AssertNotError(t, err, "Failed to load key")
rsaKey, ok := key.(*rsa.PrivateKey)
test.Assert(t, ok, "Couldn't load RSA key")
signer, err := jose.NewSigner("RS256", rsaKey)
test.AssertNotError(t, err, "Failed to make signer")
certPemBytes, err := ioutil.ReadFile("test/178.crt")
test.AssertNotError(t, err, "Failed to load cert")
certBlock, _ := pem.Decode(certPemBytes)
test.Assert(t, certBlock != nil, "Failed to decode PEM")
var revokeRequest struct {
CertificateDER core.JsonBuffer `json:"certificate"`
}
revokeRequest.CertificateDER = certBlock.Bytes
revokeRequestJSON, err := json.Marshal(revokeRequest)
test.AssertNotError(t, err, "Failed to marshal request")
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
wfe := setupWFE()
wfe.RA = &MockRegistrationAuthority{}
wfe.SA = &MockSA{}
wfe.Stats, _ = statsd.NewNoopClient()
wfe.SubscriberAgreementURL = agreementURL
responseWriter := httptest.NewRecorder()
responseWriter.Body.Reset()
result, _ := signer.Sign(revokeRequestJSON, wfe.nonceService.Nonce())
wfe.RevokeCertificate(responseWriter, &http.Request{
Method: "POST",
Body: makeBody(result.FullSerialize()),
})
test.AssertEquals(t, responseWriter.Code, 409)
test.AssertEquals(t, responseWriter.Body.String(),
"{\"type\":\"urn:acme:error:malformed\",\"detail\":\"Certificate already revoked\"}")
}
func TestAuthorization(t *testing.T) {
wfe := setupWFE()