Merge branch 'master' into ocsp-unflake-integration
Conflicts: test/amqp-integration-test.py
This commit is contained in:
commit
305d7fad22
|
|
@ -17,8 +17,10 @@ import (
|
|||
"io/ioutil"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
|
@ -48,6 +50,15 @@ var badSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
|
|||
x509.ECDSAWithSHA1: true,
|
||||
}
|
||||
|
||||
// Metrics for CA statistics
|
||||
const (
|
||||
// Increments when CA observes an HSM fault
|
||||
metricHSMFaultObserved = "CA.OCSP.HSMFault.Observed"
|
||||
|
||||
// Increments when CA rejects a request due to an HSM fault
|
||||
metricHSMFaultRejected = "CA.OCSP.HSMFault.Rejected"
|
||||
)
|
||||
|
||||
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
|
||||
// OCSP responses.
|
||||
type CertificateAuthorityImpl struct {
|
||||
|
|
@ -59,10 +70,15 @@ type CertificateAuthorityImpl struct {
|
|||
Publisher core.Publisher
|
||||
Clk clock.Clock // TODO(jmhodges): should be private, like log
|
||||
log *blog.AuditLogger
|
||||
stats statsd.Statter
|
||||
Prefix int // Prepended to the serial number
|
||||
ValidityPeriod time.Duration
|
||||
NotAfter time.Time
|
||||
MaxNames int
|
||||
|
||||
hsmFaultLock sync.Mutex
|
||||
hsmFaultLastObserved time.Time
|
||||
hsmFaultTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewCertificateAuthorityImpl creates a CA that talks to a remote CFSSL
|
||||
|
|
@ -71,7 +87,7 @@ type CertificateAuthorityImpl struct {
|
|||
// using CFSSL's authenticated signature scheme. A CA created in this way
|
||||
// issues for a single profile on the remote signer, which is indicated
|
||||
// by name in this constructor.
|
||||
func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, issuerCert string) (*CertificateAuthorityImpl, error) {
|
||||
func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats statsd.Statter, issuerCert string) (*CertificateAuthorityImpl, error) {
|
||||
var ca *CertificateAuthorityImpl
|
||||
var err error
|
||||
logger := blog.GetAuditLogger()
|
||||
|
|
@ -125,13 +141,15 @@ func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, issuerCer
|
|||
}
|
||||
|
||||
ca = &CertificateAuthorityImpl{
|
||||
Signer: signer,
|
||||
OCSPSigner: ocspSigner,
|
||||
profile: config.Profile,
|
||||
Prefix: config.SerialPrefix,
|
||||
Clk: clk,
|
||||
log: logger,
|
||||
NotAfter: issuer.NotAfter,
|
||||
Signer: signer,
|
||||
OCSPSigner: ocspSigner,
|
||||
profile: config.Profile,
|
||||
Prefix: config.SerialPrefix,
|
||||
Clk: clk,
|
||||
log: logger,
|
||||
stats: stats,
|
||||
NotAfter: issuer.NotAfter,
|
||||
hsmFaultTimeout: config.HSMFaultTimeout.Duration,
|
||||
}
|
||||
|
||||
if config.Expiry == "" {
|
||||
|
|
@ -172,8 +190,48 @@ func loadKey(keyConfig cmd.KeyConfig) (priv crypto.Signer, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// checkHSMFault checks whether there has been an HSM fault observed within the
|
||||
// timeout window. CA methods that use the HSM should call this method right
|
||||
// away, to minimize the performance impact of HSM outages.
|
||||
func (ca *CertificateAuthorityImpl) checkHSMFault() error {
|
||||
ca.hsmFaultLock.Lock()
|
||||
defer ca.hsmFaultLock.Unlock()
|
||||
|
||||
// If no timeout is set, never gate on a fault
|
||||
if ca.hsmFaultTimeout == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := ca.Clk.Now()
|
||||
timeout := ca.hsmFaultLastObserved.Add(ca.hsmFaultTimeout)
|
||||
if now.Before(timeout) {
|
||||
err := core.ServiceUnavailableError("HSM is unavailable")
|
||||
ca.log.WarningErr(err)
|
||||
ca.stats.Inc(metricHSMFaultRejected, 1, 1.0)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// noteHSMFault updates the CA's state with regard to HSM faults. CA methods
|
||||
// that use an HSM should pass errors that might be HSM errors to this method.
|
||||
func (ca *CertificateAuthorityImpl) noteHSMFault(err error) {
|
||||
ca.hsmFaultLock.Lock()
|
||||
defer ca.hsmFaultLock.Unlock()
|
||||
|
||||
if err != nil {
|
||||
ca.stats.Inc(metricHSMFaultObserved, 1, 1.0)
|
||||
ca.hsmFaultLastObserved = ca.Clk.Now()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateOCSP produces a new OCSP response and returns it
|
||||
func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest) ([]byte, error) {
|
||||
if err := ca.checkHSMFault(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(xferObj.CertDER)
|
||||
if err != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
|
|
@ -189,6 +247,7 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
|
|||
}
|
||||
|
||||
ocspResponse, err := ca.OCSPSigner.Sign(signRequest)
|
||||
ca.noteHSMFault(err)
|
||||
return ocspResponse, err
|
||||
}
|
||||
|
||||
|
|
@ -204,6 +263,11 @@ func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string, reasonCode
|
|||
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
|
||||
emptyCert := core.Certificate{}
|
||||
var err error
|
||||
|
||||
if err := ca.checkHSMFault(); err != nil {
|
||||
return emptyCert, err
|
||||
}
|
||||
|
||||
key, ok := csr.PublicKey.(crypto.PublicKey)
|
||||
if !ok {
|
||||
err = core.MalformedRequestError("Invalid public key in CSR.")
|
||||
|
|
@ -309,6 +373,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
}
|
||||
|
||||
certPEM, err := ca.Signer.Sign(req)
|
||||
ca.noteHSMFault(err)
|
||||
if err != nil {
|
||||
err = core.InternalServerError(err.Error())
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ type testCtx struct {
|
|||
reg core.Registration
|
||||
pa core.PolicyAuthority
|
||||
fc clock.FakeClock
|
||||
stats *mocks.Statter
|
||||
cleanUp func()
|
||||
}
|
||||
|
||||
|
|
@ -150,9 +151,10 @@ func setup(t *testing.T) *testCtx {
|
|||
Key: cmd.KeyConfig{
|
||||
File: caKeyFile,
|
||||
},
|
||||
Expiry: "8760h",
|
||||
LifespanOCSP: "45m",
|
||||
MaxNames: 2,
|
||||
Expiry: "8760h",
|
||||
LifespanOCSP: "45m",
|
||||
MaxNames: 2,
|
||||
HSMFaultTimeout: cmd.ConfigDuration{Duration: 60 * time.Second},
|
||||
CFSSL: cfsslConfig.Config{
|
||||
Signing: &cfsslConfig.Signing{
|
||||
Profiles: map[string]*cfsslConfig.SigningProfile{
|
||||
|
|
@ -188,7 +190,10 @@ func setup(t *testing.T) *testCtx {
|
|||
},
|
||||
},
|
||||
}
|
||||
return &testCtx{ssa, caConfig, reg, pa, fc, cleanUp}
|
||||
|
||||
stats := mocks.NewStatter()
|
||||
|
||||
return &testCtx{ssa, caConfig, reg, pa, fc, &stats, cleanUp}
|
||||
}
|
||||
|
||||
func TestFailNoSerial(t *testing.T) {
|
||||
|
|
@ -196,14 +201,14 @@ func TestFailNoSerial(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
|
||||
ctx.caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
|
||||
}
|
||||
|
||||
func TestIssueCertificate(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
|
@ -280,7 +285,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
func TestRejectNoName(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
|
@ -297,7 +302,7 @@ func TestRejectNoName(t *testing.T) {
|
|||
func TestRejectTooManyNames(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
|
@ -314,7 +319,7 @@ func TestRejectTooManyNames(t *testing.T) {
|
|||
func TestDeduplication(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
|
@ -338,7 +343,7 @@ func TestDeduplication(t *testing.T) {
|
|||
func TestRejectValidityTooLong(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
|
@ -356,7 +361,7 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
func TestShortKey(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
|
@ -372,7 +377,7 @@ func TestShortKey(t *testing.T) {
|
|||
func TestRejectBadAlgorithm(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
|
@ -389,7 +394,7 @@ func TestCapitalizedLetters(t *testing.T) {
|
|||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ctx.caConfig.MaxNames = 3
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
|
@ -405,3 +410,70 @@ func TestCapitalizedLetters(t *testing.T) {
|
|||
expected := []string{"capitalizedletters.com", "evenmorecaps.com", "morecaps.com"}
|
||||
test.AssertDeepEquals(t, expected, parsedCert.DNSNames)
|
||||
}
|
||||
|
||||
func TestHSMFaultTimeout(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
// Issue a certificate so that we can use it later
|
||||
csr, _ := x509.ParseCertificateRequest(CNandSANCSR)
|
||||
cert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
ocspRequest := core.OCSPSigningRequest{
|
||||
CertDER: cert.DER,
|
||||
Status: "good",
|
||||
}
|
||||
|
||||
// Swap in a bad signer
|
||||
goodSigner := ca.Signer
|
||||
badHSMErrorMessage := "This is really serious. You should wait"
|
||||
badSigner := mocks.BadHSMSigner(badHSMErrorMessage)
|
||||
badOCSPSigner := mocks.BadHSMOCSPSigner(badHSMErrorMessage)
|
||||
|
||||
// Cause the CA to enter the HSM fault condition
|
||||
ca.Signer = badSigner
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertError(t, err, "CA failed to return HSM error")
|
||||
test.AssertEquals(t, err.Error(), badHSMErrorMessage)
|
||||
|
||||
// Check that the CA rejects the next call as the HSM being down
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertError(t, err, "CA failed to persist HSM fault")
|
||||
test.AssertEquals(t, err.Error(), "HSM is unavailable")
|
||||
|
||||
_, err = ca.GenerateOCSP(ocspRequest)
|
||||
test.AssertError(t, err, "CA failed to persist HSM fault")
|
||||
test.AssertEquals(t, err.Error(), "HSM is unavailable")
|
||||
|
||||
// Swap in a good signer and move the clock forward to clear the fault
|
||||
ca.Signer = goodSigner
|
||||
ctx.fc.Add(ca.hsmFaultTimeout)
|
||||
ctx.fc.Add(10 * time.Second)
|
||||
|
||||
// Check that the CA has recovered
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertNotError(t, err, "CA failed to recover from HSM fault")
|
||||
_, err = ca.GenerateOCSP(ocspRequest)
|
||||
|
||||
// Check that GenerateOCSP can also trigger an HSM failure, in the same way
|
||||
ca.OCSPSigner = badOCSPSigner
|
||||
_, err = ca.GenerateOCSP(ocspRequest)
|
||||
test.AssertError(t, err, "CA failed to return HSM error")
|
||||
test.AssertEquals(t, err.Error(), badHSMErrorMessage)
|
||||
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertError(t, err, "CA failed to persist HSM fault")
|
||||
test.AssertEquals(t, err.Error(), "HSM is unavailable")
|
||||
|
||||
_, err = ca.GenerateOCSP(ocspRequest)
|
||||
test.AssertError(t, err, "CA failed to persist HSM fault")
|
||||
test.AssertEquals(t, err.Error(), "HSM is unavailable")
|
||||
|
||||
// Verify that the appropriate stats got recorded for all this
|
||||
test.AssertEquals(t, ctx.stats.Counters[metricHSMFaultObserved], int64(2))
|
||||
test.AssertEquals(t, ctx.stats.Counters[metricHSMFaultRejected], int64(4))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func main() {
|
|||
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist)
|
||||
cmd.FailOnError(err, "Couldn't create PA")
|
||||
|
||||
cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), c.Common.IssuerCert)
|
||||
cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), stats, c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, "Failed to create CA impl")
|
||||
cai.PA = pa
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@ type CAConfig struct {
|
|||
|
||||
MaxConcurrentRPCServerRequests int64
|
||||
|
||||
HSMFaultTimeout ConfigDuration
|
||||
|
||||
// DebugAddr is the address to run the /debug handlers on.
|
||||
DebugAddr string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ type RateLimitedError string
|
|||
// limit
|
||||
type TooManyRPCRequestsError string
|
||||
|
||||
// ServiceUnavailableError indicates that a component is not available to
|
||||
// satisfy a request
|
||||
type ServiceUnavailableError string
|
||||
|
||||
func (e InternalServerError) Error() string { return string(e) }
|
||||
func (e NotSupportedError) Error() string { return string(e) }
|
||||
func (e MalformedRequestError) Error() string { return string(e) }
|
||||
|
|
@ -105,6 +109,7 @@ func (e CertificateIssuanceError) Error() string { return string(e) }
|
|||
func (e NoSuchRegistrationError) Error() string { return string(e) }
|
||||
func (e RateLimitedError) Error() string { return string(e) }
|
||||
func (e TooManyRPCRequestsError) Error() string { return string(e) }
|
||||
func (e ServiceUnavailableError) Error() string { return string(e) }
|
||||
|
||||
// Base64 functions
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
package mocks
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -14,6 +15,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
|
||||
|
|
@ -370,3 +376,61 @@ type Publisher struct {
|
|||
func (*Publisher) SubmitToCT([]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BadHSMSigner represents a CFSSL signer that always returns a PKCS#11 error.
|
||||
type BadHSMSigner string
|
||||
|
||||
// Info is a mock
|
||||
func (bhs BadHSMSigner) Info(info.Req) (*info.Resp, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Policy is a mock
|
||||
func (bhs BadHSMSigner) Policy() *config.Signing {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPolicy is a mock
|
||||
func (bhs BadHSMSigner) SetPolicy(*config.Signing) {
|
||||
return
|
||||
}
|
||||
|
||||
// SigAlgo is a mock
|
||||
func (bhs BadHSMSigner) SigAlgo() x509.SignatureAlgorithm {
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
||||
|
||||
// Sign always returns a PKCS#11 error, in the format used by
|
||||
// github.com/miekg/pkcs11
|
||||
func (bhs BadHSMSigner) Sign(req signer.SignRequest) (cert []byte, err error) {
|
||||
return nil, fmt.Errorf(string(bhs))
|
||||
}
|
||||
|
||||
// BadHSMOCSPSigner represents a CFSSL OCSP signer that always returns a
|
||||
// PKCS#11 error
|
||||
type BadHSMOCSPSigner string
|
||||
|
||||
// Sign always returns a PKCS#11 error, in the format used by
|
||||
// github.com/miekg/pkcs11
|
||||
func (bhos BadHSMOCSPSigner) Sign(ocsp.SignRequest) ([]byte, error) {
|
||||
return nil, fmt.Errorf(string(bhos))
|
||||
}
|
||||
|
||||
// Statter is a stat counter that is a no-op except for locally handling Inc
|
||||
// calls (which are most of what we use).
|
||||
type Statter struct {
|
||||
statsd.NoopClient
|
||||
Counters map[string]int64
|
||||
}
|
||||
|
||||
// Inc increments the indicated metric by the indicated value, in the Counters
|
||||
// map maintained by the statter
|
||||
func (s *Statter) Inc(metric string, value int64, rate float32) error {
|
||||
s.Counters[metric] += value
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStatter returns an empty statter with all counters zero
|
||||
func NewStatter() Statter {
|
||||
return Statter{statsd.NoopClient{}, map[string]int64{}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,9 +325,7 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
|
|||
// * IsCA is false
|
||||
// * ExtKeyUsage only contains ExtKeyUsageServerAuth & ExtKeyUsageClientAuth
|
||||
// * Subject only contains CommonName & Names
|
||||
func (ra *RegistrationAuthorityImpl) MatchesCSR(
|
||||
cert core.Certificate,
|
||||
csr *x509.CertificateRequest) (err error) {
|
||||
func (ra *RegistrationAuthorityImpl) MatchesCSR(cert core.Certificate, csr *x509.CertificateRequest) (err error) {
|
||||
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -345,7 +343,8 @@ func (ra *RegistrationAuthorityImpl) MatchesCSR(
|
|||
err = core.InternalServerError("Generated certificate public key doesn't match CSR public key")
|
||||
return
|
||||
}
|
||||
if len(csr.Subject.CommonName) > 0 && parsedCertificate.Subject.CommonName != csr.Subject.CommonName {
|
||||
if len(csr.Subject.CommonName) > 0 &&
|
||||
parsedCertificate.Subject.CommonName != strings.ToLower(csr.Subject.CommonName) {
|
||||
err = core.InternalServerError("Generated certificate CommonName doesn't match CSR CommonName")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -606,6 +606,8 @@ func TestNewCertificate(t *testing.T) {
|
|||
authzFinalWWW, _ = sa.NewPendingAuthorization(authzFinalWWW)
|
||||
sa.FinalizeAuthorization(authzFinalWWW)
|
||||
|
||||
// Check that we don't fail on case mismatches
|
||||
ExampleCSR.Subject.CommonName = "www.NOT-example.com"
|
||||
certRequest := core.CertificateRequest{
|
||||
CSR: ExampleCSR,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,8 @@ func wrapError(err error) (rpcError rpcError) {
|
|||
rpcError.Type = "TooManyRPCRequestsError"
|
||||
case core.RateLimitedError:
|
||||
rpcError.Type = "RateLimitedError"
|
||||
case core.ServiceUnavailableError:
|
||||
rpcError.Type = "ServiceUnavailableError"
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
@ -266,6 +268,8 @@ func unwrapError(rpcError rpcError) (err error) {
|
|||
err = core.TooManyRPCRequestsError(rpcError.Value)
|
||||
case "RateLimitedError":
|
||||
err = core.RateLimitedError(rpcError.Value)
|
||||
case "ServiceUnavailableError":
|
||||
err = core.ServiceUnavailableError(rpcError.Value)
|
||||
default:
|
||||
err = errors.New(rpcError.Value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ def run_node_test():
|
|||
# Issue the certificate and transform it from DER-encoded to PEM-encoded.
|
||||
if subprocess.Popen('''
|
||||
node test.js --email foo@letsencrypt.org --agree true \
|
||||
--domains www.%s.com --new-reg http://localhost:4000/acme/new-reg \
|
||||
--domains www.%s-TEST.com --new-reg http://localhost:4000/acme/new-reg \
|
||||
--certKey %s --cert %s && \
|
||||
openssl x509 -in %s -out %s -inform der -outform pem
|
||||
''' % (domain, key_file, cert_file, cert_file, cert_file_pem),
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"maxConcurrentRPCServerRequests": 16
|
||||
"maxConcurrentRPCServerRequests": 16,
|
||||
"hsmFaultTimeout": "300s"
|
||||
},
|
||||
|
||||
"pa": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package wfe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
type requestEvent struct {
|
||||
ID string `json:",omitempty"`
|
||||
RealIP string `json:",omitempty"`
|
||||
ClientAddr string `json:",omitempty"`
|
||||
Endpoint string `json:",omitempty"`
|
||||
Method string `json:",omitempty"`
|
||||
RequestTime time.Time `json:",omitempty"`
|
||||
ResponseTime time.Time `json:",omitempty"`
|
||||
Errors []string
|
||||
Requester int64 `json:",omitempty"`
|
||||
Contacts []*core.AcmeURL `json:",omitempty"`
|
||||
|
||||
Extra map[string]interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (e *requestEvent) AddError(msg string, args ...interface{}) {
|
||||
e.Errors = append(e.Errors, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
type wfeHandlerFunc func(*requestEvent, http.ResponseWriter, *http.Request)
|
||||
|
||||
func (f wfeHandlerFunc) ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request) {
|
||||
f(e, w, r)
|
||||
}
|
||||
|
||||
type wfeHandler interface {
|
||||
ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type topHandler struct {
|
||||
wfe wfeHandler
|
||||
log *blog.AuditLogger
|
||||
clk clock.Clock
|
||||
}
|
||||
|
||||
func (th *topHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
logEvent := &requestEvent{
|
||||
ID: core.NewToken(),
|
||||
RealIP: r.Header.Get("X-Real-IP"),
|
||||
ClientAddr: getClientAddr(r),
|
||||
Method: r.Method,
|
||||
RequestTime: time.Now(),
|
||||
Extra: make(map[string]interface{}, 0),
|
||||
}
|
||||
if r.URL != nil {
|
||||
logEvent.Endpoint = r.URL.String()
|
||||
}
|
||||
defer th.logEvent(logEvent)
|
||||
|
||||
th.wfe.ServeHTTP(logEvent, w, r)
|
||||
}
|
||||
|
||||
func (th *topHandler) logEvent(logEvent *requestEvent) {
|
||||
logEvent.ResponseTime = th.clk.Now()
|
||||
var msg string
|
||||
if len(logEvent.Errors) != 0 {
|
||||
msg = "Terminated request"
|
||||
} else {
|
||||
msg = "Successful request"
|
||||
}
|
||||
th.log.InfoObject(msg, logEvent)
|
||||
}
|
||||
|
||||
// Comma-separated list of HTTP clients involved in making this
|
||||
// request, starting with the original requestor and ending with the
|
||||
// remote end of our TCP connection (which is typically our own
|
||||
// proxy).
|
||||
func getClientAddr(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
return xff + "," + r.RemoteAddr
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -271,7 +271,7 @@ func TestHandleFunc(t *testing.T) {
|
|||
mux = http.NewServeMux()
|
||||
rw = httptest.NewRecorder()
|
||||
stubCalled = false
|
||||
wfe.HandleFunc(mux, "/test", func(http.ResponseWriter, *http.Request) {
|
||||
wfe.HandleFunc(mux, "/test", func(*requestEvent, http.ResponseWriter, *http.Request) {
|
||||
stubCalled = true
|
||||
}, allowed...)
|
||||
req.URL = mustParseURL("/test")
|
||||
|
|
@ -463,7 +463,7 @@ func TestIndexPOST(t *testing.T) {
|
|||
wfe, _ := setupWFE(t)
|
||||
responseWriter := httptest.NewRecorder()
|
||||
url, _ := url.Parse("/")
|
||||
wfe.Index(responseWriter, &http.Request{
|
||||
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
URL: url,
|
||||
})
|
||||
|
|
@ -474,7 +474,7 @@ func TestPOST404(t *testing.T) {
|
|||
wfe, _ := setupWFE(t)
|
||||
responseWriter := httptest.NewRecorder()
|
||||
url, _ := url.Parse("/foobar")
|
||||
wfe.Index(responseWriter, &http.Request{
|
||||
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
URL: url,
|
||||
})
|
||||
|
|
@ -488,7 +488,7 @@ func TestIndex(t *testing.T) {
|
|||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
url, _ := url.Parse("/")
|
||||
wfe.Index(responseWriter, &http.Request{
|
||||
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: url,
|
||||
})
|
||||
|
|
@ -501,7 +501,7 @@ func TestIndex(t *testing.T) {
|
|||
responseWriter.Body.Reset()
|
||||
responseWriter.Header().Del("Cache-Control")
|
||||
url, _ = url.Parse("/foo")
|
||||
wfe.Index(responseWriter, &http.Request{
|
||||
wfe.Index(newRequestEvent(), responseWriter, &http.Request{
|
||||
URL: url,
|
||||
})
|
||||
//test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
|
||||
|
|
@ -564,7 +564,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
|
||||
// POST, but no body.
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter, &http.Request{
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Header: map[string][]string{
|
||||
"Content-Length": []string{"0"},
|
||||
|
|
@ -576,14 +576,14 @@ func TestIssueCertificate(t *testing.T) {
|
|||
|
||||
// POST, but body that isn't valid JWS
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter, makePostRequest("hi"))
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter, makePostRequest("hi"))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, "foo", &wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
|
|
@ -591,7 +591,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
|
||||
// Valid, signed JWS body, payload is '{}'
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(
|
||||
signRequest(t, "{}", &wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
|
|
@ -600,7 +600,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
|
||||
// Valid, signed JWS body, payload is '{"resource":"new-cert"}'
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, `{"resource":"new-cert"}`, &wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
|
|
@ -611,7 +611,8 @@ func TestIssueCertificate(t *testing.T) {
|
|||
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=foo.com | \
|
||||
// sed 's/foo.com/fob.com/' | b64url
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(),
|
||||
responseWriter,
|
||||
makePostRequest(signRequest(t, `{
|
||||
"resource":"new-cert",
|
||||
"csr": "MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHZm9iLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKzHhqcMSTVjBu61vufGVmIYM4mMbWXgndHOUWnIqSKcNtFtPQ465tcZRT5ITIZWXGjsmgDrj31qvG3t5qLwyaF5hsTvFHK72nLMAQhdgM6481Qe9yaoaulWpkGr_9LVz4jQ9pGAaLVamXGpSxV-ipTOo79Sev4aZE8ksD9atEfWtcOD9w8_zj74vpWjTAHN49Q88chlChVqakn0zSfHPfS-jF8g0UTddBuF0Ti3sZChjxzbo6LwZ4182xX7XPnOLav3AGj0Su7j5XMl3OpenOrlWulWJeZIHq5itGW321j306XiGdbrdWH4K7JygICFds6oolwQRGBY6yinAtCgkTcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBxPiHOtKuBxtvecMNtLkTSuTyEkusQGnjoFDaKe5oqwGYQgy0YBii2-BbaPmqS4ZaDc-vDz_RLeKH5ZiH-NliYR1V_CRtpFLQi18g_2pLQnZLVO3ENs-SM37nU_nBGn9O93t2bkssoM3fZmtgp3R2W7I_wvx7Z8oWKa4boTeBAg_q9Gmi6QskZBddK7A4S_vOR0frU6QSPK_ksPhvovp9fwb6CVKrlJWf556UwRPWgbkW39hvTxK2KHhrUEg3oawNkWde2jZtnZ9e-9zpw8-_5O0X7-YN0ucbFTfQybce_ReuLlGepiHT5bvVavBZoIvqw1XOgSMvGgZFU8tAWMBlj"
|
||||
|
|
@ -624,7 +625,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=meep.com | b64url
|
||||
mockLog.Clear()
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, `{
|
||||
"resource":"new-cert",
|
||||
"csr": "MIICWDCCAUACAQAwEzERMA8GA1UEAwwIbWVlcC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaqzue57mgXEoGTZZoVkkCZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12HX01rUwhj41UuBxJvI1w4wvdstssTAaa9c9tsQ5-UED2bFRL1MsyBdbmCF_-pu3i-ZIYqWgiKbjVBe3nlAVbo77zizwp3Y4Tp1_TBOwTAuFkHePmkNT63uPm9My_hNzsSm1o-Q519Cf7ry-JQmOVgz_jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoXRUlQjXXkNTj23OxxdT_cVLcLJjytyG6e5izME2R2aCkDBWIc1a4_sRJ0R396auPXG6KhJ7o_AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEALu046p76aKgvoAEHFINkMTgKokPXf9mZ4IZx_BKz-qs1MPMxVtPIrQDVweBH6tYT7Hfj2naLry6SpZ3vUNP_FYeTFWgW1V03LiqacX-QQgbEYtn99Dt3ScGyzb7EH833ztb3vDJ_-ha_CJplIrg-kHBBrlLFWXhh-I9K1qLRTNpbhZ18ooFde4Sbhkw9o9fKivGhx9aYr7ZbjRsNtKit_DsG1nwEXz53TMJ2vB9IQY29coJv_n5NFLkvBfzbG5faRNiFcimPYBO2jFdaA2mWzfxltLtwMF_dBwzTXDpMo3TVT9zEdV8YpsWqr63igqGDZVpKenlkqvRTeGJVayVuMA"
|
||||
|
|
@ -637,7 +638,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
mockLog.Clear()
|
||||
responseWriter.Body.Reset()
|
||||
// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=not-an-example.com | b64url
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, `{
|
||||
"resource":"new-cert",
|
||||
"csr": "MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSbm90LWFuLWV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJAma2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1MIY-NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf_qbt4vmSGKloIim41QXt55QFW6O-84s8Kd2OE6df0wTsEwLhZB3j5pDU-t7j5vTMv4Tc7EptaPkOdfQn-68viUJjlYM_4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI115DU49tzscXU_3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd_emrj1xuioSe6PwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAE_T1nWU38XVYL28hNVSXU0rW5IBUKtbvr0qAkD4kda4HmQRTYkt-LNSuvxoZCC9lxijjgtJi-OJe_DCTdZZpYzewlVvcKToWSYHYQ6Wm1-fxxD_XzphvZOujpmBySchdiz7QSVWJmVZu34XD5RJbIcrmj_cjRt42J1hiTFjNMzQu9U6_HwIMmliDL-soFY2RTvvZf-dAFvOUQ-Wbxt97eM1PbbmxJNWRhbAmgEpe9PWDPTpqV5AK56VAa991cQ1P8ZVmPss5hvwGWhOtpnpTZVHN3toGNYFKqxWPboirqushQlfKiFqT9rpRgM3-mFjOHidGqsKEkTdmfSVlVEk3oo="
|
||||
|
|
@ -675,7 +676,7 @@ func TestGetChallenge(t *testing.T) {
|
|||
req, err := http.NewRequest(method, challengeURL, nil)
|
||||
test.AssertNotError(t, err, "Could not make NewRequest")
|
||||
|
||||
wfe.Challenge(resp, req)
|
||||
wfe.Challenge(newRequestEvent(), resp, req)
|
||||
test.AssertEquals(t,
|
||||
resp.Code,
|
||||
http.StatusAccepted)
|
||||
|
|
@ -714,7 +715,7 @@ func TestChallenge(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Could not unmarshal testing key")
|
||||
|
||||
challengeURL := "/acme/challenge/valid/23"
|
||||
wfe.Challenge(responseWriter,
|
||||
wfe.Challenge(newRequestEvent(), responseWriter,
|
||||
makePostRequestWithPath(challengeURL,
|
||||
signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService)))
|
||||
|
||||
|
|
@ -732,7 +733,7 @@ func TestChallenge(t *testing.T) {
|
|||
// Expired challenges should be inaccessible
|
||||
challengeURL = "/acme/challenge/expired/23"
|
||||
responseWriter = httptest.NewRecorder()
|
||||
wfe.Challenge(responseWriter,
|
||||
wfe.Challenge(newRequestEvent(), responseWriter,
|
||||
makePostRequestWithPath(challengeURL,
|
||||
signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService)))
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
|
||||
|
|
@ -839,7 +840,7 @@ func TestNewRegistration(t *testing.T) {
|
|||
nonce, err = wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce)
|
||||
wfe.NewRegistration(responseWriter,
|
||||
wfe.NewRegistration(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
|
||||
var reg core.Registration
|
||||
|
|
@ -875,7 +876,7 @@ func TestNewRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte(`{"resource":"new-reg","contact":["tel:123456789"],"agreement":"`+agreementURL+`"}`), nonce)
|
||||
|
||||
wfe.NewRegistration(responseWriter,
|
||||
wfe.NewRegistration(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
|
|
@ -943,7 +944,7 @@ func TestRevokeCertificateCertKey(t *testing.T) {
|
|||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, _ := signer.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter,
|
||||
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t, responseWriter.Code, 200)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), "")
|
||||
|
|
@ -967,7 +968,7 @@ func TestRevokeCertificateAccountKey(t *testing.T) {
|
|||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, _ := accountKeySigner.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter,
|
||||
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t, responseWriter.Code, 200)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), "")
|
||||
|
|
@ -991,7 +992,7 @@ func TestRevokeCertificateWrongKey(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Unable to create revoke request")
|
||||
|
||||
result, _ := accountKeySigner2.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter,
|
||||
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t, responseWriter.Code, 403)
|
||||
test.AssertEquals(t, responseWriter.Body.String(),
|
||||
|
|
@ -1033,7 +1034,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
|
|||
nonce, err := wfe.nonceService.Nonce()
|
||||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, _ := signer.Sign(revokeRequestJSON, nonce)
|
||||
wfe.RevokeCertificate(responseWriter,
|
||||
wfe.RevokeCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t, responseWriter.Code, 409)
|
||||
test.AssertEquals(t, responseWriter.Body.String(),
|
||||
|
|
@ -1056,7 +1057,7 @@ func TestAuthorization(t *testing.T) {
|
|||
|
||||
// POST, but no body.
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(responseWriter, &http.Request{
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "POST",
|
||||
Header: map[string][]string{
|
||||
"Content-Length": []string{"0"},
|
||||
|
|
@ -1066,12 +1067,12 @@ func TestAuthorization(t *testing.T) {
|
|||
|
||||
// POST, but body that isn't valid JWS
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(responseWriter, makePostRequest("hi"))
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest("hi"))
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(responseWriter,
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, "foo", &wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
|
|
@ -1080,7 +1081,7 @@ func TestAuthorization(t *testing.T) {
|
|||
// Same signed body, but payload modified by one byte, breaking signature.
|
||||
// should fail JWS verification.
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(responseWriter, makePostRequest(`
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "RS256",
|
||||
|
|
@ -1099,7 +1100,7 @@ func TestAuthorization(t *testing.T) {
|
|||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error"}`)
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(responseWriter,
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, `{"resource":"new-authz","identifier":{"type":"dns","value":"test.com"}}`, &wfe.nonceService)))
|
||||
|
||||
test.AssertEquals(
|
||||
|
|
@ -1118,7 +1119,7 @@ func TestAuthorization(t *testing.T) {
|
|||
// Expired authorizations should be inaccessible
|
||||
authzURL := "/acme/authz/expired"
|
||||
responseWriter = httptest.NewRecorder()
|
||||
wfe.Authorization(responseWriter, &http.Request{
|
||||
wfe.Authorization(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: mustParseURL(authzURL),
|
||||
})
|
||||
|
|
@ -1164,7 +1165,7 @@ func TestRegistration(t *testing.T) {
|
|||
responseWriter.Body.Reset()
|
||||
|
||||
// Test POST invalid JSON
|
||||
wfe.Registration(responseWriter, makePostRequestWithPath("/2", "invalid"))
|
||||
wfe.Registration(newRequestEvent(), responseWriter, makePostRequestWithPath("/2", "invalid"))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`)
|
||||
|
|
@ -1182,7 +1183,7 @@ func TestRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err := signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce)
|
||||
test.AssertNotError(t, err, "Unable to sign")
|
||||
wfe.Registration(responseWriter,
|
||||
wfe.Registration(newRequestEvent(), responseWriter,
|
||||
makePostRequestWithPath("/2", result.FullSerialize()))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
|
|
@ -1202,7 +1203,7 @@ func TestRegistration(t *testing.T) {
|
|||
result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"https://letsencrypt.org/im-bad"}`), nonce)
|
||||
|
||||
// Test POST valid JSON with registration up in the mock
|
||||
wfe.Registration(responseWriter,
|
||||
wfe.Registration(newRequestEvent(), responseWriter,
|
||||
makePostRequestWithPath("/1", result.FullSerialize()))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
|
|
@ -1214,7 +1215,7 @@ func TestRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Unable to create nonce")
|
||||
result, err = signer.Sign([]byte(`{"resource":"reg","agreement":"`+agreementURL+`"}`), nonce)
|
||||
test.AssertNotError(t, err, "Couldn't sign")
|
||||
wfe.Registration(responseWriter,
|
||||
wfe.Registration(newRequestEvent(), responseWriter,
|
||||
makePostRequestWithPath("/1", result.FullSerialize()))
|
||||
test.AssertNotContains(t, responseWriter.Body.String(), "urn:acme:error")
|
||||
links := responseWriter.Header()["Link"]
|
||||
|
|
@ -1229,7 +1230,7 @@ func TestTermsRedirect(t *testing.T) {
|
|||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
path, _ := url.Parse("/terms")
|
||||
wfe.Terms(responseWriter, &http.Request{
|
||||
wfe.Terms(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
|
|
@ -1246,7 +1247,7 @@ func TestIssuer(t *testing.T) {
|
|||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
wfe.Issuer(responseWriter, &http.Request{
|
||||
wfe.Issuer(newRequestEvent(), responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
|
|
@ -1256,6 +1257,9 @@ func TestIssuer(t *testing.T) {
|
|||
|
||||
func TestGetCertificate(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.CertCacheDuration = time.Second * 10
|
||||
wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7
|
||||
|
||||
|
|
@ -1270,12 +1274,13 @@ func TestGetCertificate(t *testing.T) {
|
|||
// Valid serial, cached
|
||||
req, _ := http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000b2", nil)
|
||||
req.RemoteAddr = "192.168.0.1"
|
||||
wfe.Certificate(responseWriter, req)
|
||||
mux.ServeHTTP(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 200)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert")
|
||||
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match")
|
||||
|
||||
t.Logf("UGH %#v", mockLog.GetAll()[0])
|
||||
reqlogs := mockLog.GetAllMatching(`Successful request`)
|
||||
test.AssertEquals(t, len(reqlogs), 1)
|
||||
test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO)
|
||||
|
|
@ -1287,7 +1292,7 @@ func TestGetCertificate(t *testing.T) {
|
|||
req, _ = http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000ff", nil)
|
||||
req.RemoteAddr = "192.168.0.1"
|
||||
req.Header.Set("X-Forwarded-For", "192.168.99.99")
|
||||
wfe.Certificate(responseWriter, req)
|
||||
mux.ServeHTTP(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
|
@ -1300,7 +1305,7 @@ func TestGetCertificate(t *testing.T) {
|
|||
// Invalid serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/acme/cert/nothex", nil)
|
||||
wfe.Certificate(responseWriter, req)
|
||||
mux.ServeHTTP(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
|
@ -1308,7 +1313,7 @@ func TestGetCertificate(t *testing.T) {
|
|||
// Invalid serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/acme/cert/00000000000000", nil)
|
||||
wfe.Certificate(responseWriter, req)
|
||||
mux.ServeHTTP(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
|
@ -1350,7 +1355,7 @@ func TestLogCsrPem(t *testing.T) {
|
|||
|
||||
func TestLengthRequired(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(&http.Request{
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), &http.Request{
|
||||
Method: "POST",
|
||||
URL: mustParseURL("/"),
|
||||
}, false, "resource")
|
||||
|
|
@ -1378,7 +1383,7 @@ func TestVerifyPOSTUsesStoredKey(t *testing.T) {
|
|||
wfe.SA = &mockSADifferentStoredKey{mocks.NewStorageAuthority(fc)}
|
||||
// signRequest signs with test1Key, but our special mock returns a
|
||||
// registration with test2Key
|
||||
_, _, _, err := wfe.verifyPOST(makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo")
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo")
|
||||
test.AssertError(t, err, "No error returned when provided key differed from stored key.")
|
||||
}
|
||||
|
||||
|
|
@ -1389,7 +1394,7 @@ func TestBadKeyCSR(t *testing.T) {
|
|||
// CSR with a bad (512 bit RSA) key.
|
||||
// openssl req -outform der -new -newkey rsa:512 -nodes -keyout foo.com.key
|
||||
// -subj /CN=foo.com | base64 -w0 | sed -e 's,+,-,g' -e 's,/,_,g'
|
||||
wfe.NewCertificate(responseWriter,
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter,
|
||||
makePostRequest(signRequest(t, `{
|
||||
"resource":"new-cert",
|
||||
"csr": "MIHLMHcCAQAwEjEQMA4GA1UEAwwHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDCZftp4x4owgjBnwOKfzihIPedT-BUmV2fuQPMqaUlc8yJUp13vcO5uxUlaBm8leM7Dj_sgTDP_JgykorlYo73AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAEaQ2QBhweK-kp1ejQCedUhMit_wG-uTBtKnc3M82f6_fztLkhg1vWQ782nmhbEI5orXp6QtNHgJYnBpqA9Ut00"
|
||||
|
|
@ -1422,3 +1427,7 @@ func TestStatusCodeFromError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRequestEvent() *requestEvent {
|
||||
return &requestEvent{Extra: make(map[string]interface{})}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue