Merge branch 'master' into google-ct
This commit is contained in:
		
						commit
						7e093c3ed4
					
				| 
						 | 
				
			
			@ -66,6 +66,9 @@ Set up a database:
 | 
			
		|||
    > cd $GOPATH/src/github.com/letsencrypt/boulder
 | 
			
		||||
    > ./test/create_db.sh
 | 
			
		||||
 | 
			
		||||
Set up RabbitMQ:
 | 
			
		||||
    > go run cmd/rabbitmq-setup/main.go -server amqp://localhost
 | 
			
		||||
 | 
			
		||||
**Note**: `create_db.sh` uses the root MariaDB user with the default
 | 
			
		||||
password, so if you have disabled that account or changed the password
 | 
			
		||||
you may have to adjust the file or recreate the commands.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ import (
 | 
			
		|||
	"encoding/pem"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +26,6 @@ import (
 | 
			
		|||
	blog "github.com/letsencrypt/boulder/log"
 | 
			
		||||
 | 
			
		||||
	cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
 | 
			
		||||
	"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/cloudflare/cfssl/signer/local"
 | 
			
		||||
| 
						 | 
				
			
			@ -63,18 +60,18 @@ const (
 | 
			
		|||
// OCSP responses.
 | 
			
		||||
type CertificateAuthorityImpl struct {
 | 
			
		||||
	profile        string
 | 
			
		||||
	Signer         signer.Signer
 | 
			
		||||
	OCSPSigner     ocsp.Signer
 | 
			
		||||
	signer         signer.Signer
 | 
			
		||||
	ocspSigner     ocsp.Signer
 | 
			
		||||
	SA             core.StorageAuthority
 | 
			
		||||
	PA             core.PolicyAuthority
 | 
			
		||||
	Publisher      core.Publisher
 | 
			
		||||
	Clk            clock.Clock // TODO(jmhodges): should be private, like log
 | 
			
		||||
	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
 | 
			
		||||
	prefix         int // Prepended to the serial number
 | 
			
		||||
	validityPeriod time.Duration
 | 
			
		||||
	notAfter       time.Time
 | 
			
		||||
	maxNames       int
 | 
			
		||||
 | 
			
		||||
	hsmFaultLock         sync.Mutex
 | 
			
		||||
	hsmFaultLastObserved time.Time
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +84,13 @@ 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, stats statsd.Statter, issuerCert string) (*CertificateAuthorityImpl, error) {
 | 
			
		||||
func NewCertificateAuthorityImpl(
 | 
			
		||||
	config cmd.CAConfig,
 | 
			
		||||
	clk clock.Clock,
 | 
			
		||||
	stats statsd.Statter,
 | 
			
		||||
	issuer *x509.Certificate,
 | 
			
		||||
	privateKey crypto.Signer,
 | 
			
		||||
) (*CertificateAuthorityImpl, error) {
 | 
			
		||||
	var ca *CertificateAuthorityImpl
 | 
			
		||||
	var err error
 | 
			
		||||
	logger := blog.GetAuditLogger()
 | 
			
		||||
| 
						 | 
				
			
			@ -109,18 +112,7 @@ func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats sta
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load the private key, which can be a file or a PKCS#11 key.
 | 
			
		||||
	priv, err := loadKey(config.Key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issuer, err := core.LoadCert(issuerCert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	signer, err := local.NewSigner(priv, issuer, x509.SHA256WithRSA, cfsslConfigObj.Signing)
 | 
			
		||||
	signer, err := local.NewSigner(privateKey, issuer, x509.SHA256WithRSA, cfsslConfigObj.Signing)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -135,61 +127,36 @@ func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats sta
 | 
			
		|||
 | 
			
		||||
	// Set up our OCSP signer. Note this calls for both the issuer cert and the
 | 
			
		||||
	// OCSP signing cert, which are the same in our case.
 | 
			
		||||
	ocspSigner, err := ocsp.NewSigner(issuer, issuer, priv, lifespanOCSP)
 | 
			
		||||
	ocspSigner, err := ocsp.NewSigner(issuer, issuer, privateKey, lifespanOCSP)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ca = &CertificateAuthorityImpl{
 | 
			
		||||
		Signer:          signer,
 | 
			
		||||
		OCSPSigner:      ocspSigner,
 | 
			
		||||
		signer:          signer,
 | 
			
		||||
		ocspSigner:      ocspSigner,
 | 
			
		||||
		profile:         config.Profile,
 | 
			
		||||
		Prefix:          config.SerialPrefix,
 | 
			
		||||
		Clk:             clk,
 | 
			
		||||
		prefix:          config.SerialPrefix,
 | 
			
		||||
		clk:             clk,
 | 
			
		||||
		log:             logger,
 | 
			
		||||
		stats:           stats,
 | 
			
		||||
		NotAfter:        issuer.NotAfter,
 | 
			
		||||
		notAfter:        issuer.NotAfter,
 | 
			
		||||
		hsmFaultTimeout: config.HSMFaultTimeout.Duration,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Expiry == "" {
 | 
			
		||||
		return nil, errors.New("Config must specify an expiry period.")
 | 
			
		||||
	}
 | 
			
		||||
	ca.ValidityPeriod, err = time.ParseDuration(config.Expiry)
 | 
			
		||||
	ca.validityPeriod, err = time.ParseDuration(config.Expiry)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ca.MaxNames = config.MaxNames
 | 
			
		||||
	ca.maxNames = config.MaxNames
 | 
			
		||||
 | 
			
		||||
	return ca, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadKey(keyConfig cmd.KeyConfig) (priv crypto.Signer, err error) {
 | 
			
		||||
	if keyConfig.File != "" {
 | 
			
		||||
		var keyBytes []byte
 | 
			
		||||
		keyBytes, err = ioutil.ReadFile(keyConfig.File)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Could not read key file %s", keyConfig.File)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		priv, err = helpers.ParsePrivateKeyPEM(keyBytes)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pkcs11Config := keyConfig.PKCS11
 | 
			
		||||
	if pkcs11Config.Module == "" ||
 | 
			
		||||
		pkcs11Config.TokenLabel == "" ||
 | 
			
		||||
		pkcs11Config.PIN == "" ||
 | 
			
		||||
		pkcs11Config.PrivateKeyLabel == "" {
 | 
			
		||||
		err = fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	priv, err = pkcs11key.New(pkcs11Config.Module,
 | 
			
		||||
		pkcs11Config.TokenLabel, pkcs11Config.PIN, pkcs11Config.PrivateKeyLabel)
 | 
			
		||||
	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.
 | 
			
		||||
| 
						 | 
				
			
			@ -202,7 +169,7 @@ func (ca *CertificateAuthorityImpl) checkHSMFault() error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	now := ca.Clk.Now()
 | 
			
		||||
	now := ca.clk.Now()
 | 
			
		||||
	timeout := ca.hsmFaultLastObserved.Add(ca.hsmFaultTimeout)
 | 
			
		||||
	if now.Before(timeout) {
 | 
			
		||||
		err := core.ServiceUnavailableError("HSM is unavailable")
 | 
			
		||||
| 
						 | 
				
			
			@ -221,7 +188,7 @@ func (ca *CertificateAuthorityImpl) noteHSMFault(err error) {
 | 
			
		|||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ca.stats.Inc(metricHSMFaultObserved, 1, 1.0)
 | 
			
		||||
		ca.hsmFaultLastObserved = ca.Clk.Now()
 | 
			
		||||
		ca.hsmFaultLastObserved = ca.clk.Now()
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +213,7 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
 | 
			
		|||
		RevokedAt:   xferObj.RevokedAt,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ocspResponse, err := ca.OCSPSigner.Sign(signRequest)
 | 
			
		||||
	ocspResponse, err := ca.ocspSigner.Sign(signRequest)
 | 
			
		||||
	ca.noteHSMFault(err)
 | 
			
		||||
	return ocspResponse, err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -307,8 +274,8 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
 | 
			
		|||
 | 
			
		||||
	// Collapse any duplicate names.  Note that this operation may re-order the names
 | 
			
		||||
	hostNames = core.UniqueLowerNames(hostNames)
 | 
			
		||||
	if ca.MaxNames > 0 && len(hostNames) > ca.MaxNames {
 | 
			
		||||
		err = core.MalformedRequestError(fmt.Sprintf("Certificate request has %d > %d names", len(hostNames), ca.MaxNames))
 | 
			
		||||
	if ca.maxNames > 0 && len(hostNames) > ca.maxNames {
 | 
			
		||||
		err = core.MalformedRequestError(fmt.Sprintf("Certificate request has %d names, maximum is %d.", len(hostNames), ca.maxNames))
 | 
			
		||||
		ca.log.WarningErr(err)
 | 
			
		||||
		return emptyCert, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -331,9 +298,9 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notAfter := ca.Clk.Now().Add(ca.ValidityPeriod)
 | 
			
		||||
	notAfter := ca.clk.Now().Add(ca.validityPeriod)
 | 
			
		||||
 | 
			
		||||
	if ca.NotAfter.Before(notAfter) {
 | 
			
		||||
	if ca.notAfter.Before(notAfter) {
 | 
			
		||||
		err = core.InternalServerError("Cannot issue a certificate that expires after the intermediate certificate.")
 | 
			
		||||
		// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
 | 
			
		||||
		ca.log.AuditErr(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -349,7 +316,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
 | 
			
		|||
	// We want 136 bits of random number, plus an 8-bit instance id prefix.
 | 
			
		||||
	const randBits = 136
 | 
			
		||||
	serialBytes := make([]byte, randBits/8+1)
 | 
			
		||||
	serialBytes[0] = byte(ca.Prefix)
 | 
			
		||||
	serialBytes[0] = byte(ca.prefix)
 | 
			
		||||
	_, err = rand.Read(serialBytes[1:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = core.InternalServerError(err.Error())
 | 
			
		||||
| 
						 | 
				
			
			@ -372,7 +339,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
 | 
			
		|||
		Serial: serialBigInt,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	certPEM, err := ca.Signer.Sign(req)
 | 
			
		||||
	certPEM, err := ca.signer.Sign(req)
 | 
			
		||||
	ca.noteHSMFault(err)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = core.InternalServerError(err.Error())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ package ca
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/asn1"
 | 
			
		||||
	"fmt"
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +17,7 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
 | 
			
		||||
	ocspConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp/config"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
 | 
			
		||||
	"github.com/letsencrypt/boulder/cmd"
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +118,21 @@ type testCtx struct {
 | 
			
		|||
	cleanUp  func()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var caKey crypto.Signer
 | 
			
		||||
var caCert *x509.Certificate
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	var err error
 | 
			
		||||
	caKey, err = helpers.ParsePrivateKeyPEM(mustRead(caKeyFile))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("Unable to parse %s: %s", caKeyFile, err))
 | 
			
		||||
	}
 | 
			
		||||
	caCert, err = core.LoadCert(caCertFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("Unable to parse %s: %s", caCertFile, err))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setup(t *testing.T) *testCtx {
 | 
			
		||||
	// Create an SA
 | 
			
		||||
	dbMap, err := sa.NewDbMap(vars.DBConnSA)
 | 
			
		||||
| 
						 | 
				
			
			@ -146,11 +163,8 @@ func setup(t *testing.T) *testCtx {
 | 
			
		|||
 | 
			
		||||
	// Create a CA
 | 
			
		||||
	caConfig := cmd.CAConfig{
 | 
			
		||||
		Profile:      profileName,
 | 
			
		||||
		SerialPrefix: 17,
 | 
			
		||||
		Key: cmd.KeyConfig{
 | 
			
		||||
			File: caKeyFile,
 | 
			
		||||
		},
 | 
			
		||||
		Profile:         profileName,
 | 
			
		||||
		SerialPrefix:    17,
 | 
			
		||||
		Expiry:          "8760h",
 | 
			
		||||
		LifespanOCSP:    "45m",
 | 
			
		||||
		MaxNames:        2,
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +207,15 @@ func setup(t *testing.T) *testCtx {
 | 
			
		|||
 | 
			
		||||
	stats := mocks.NewStatter()
 | 
			
		||||
 | 
			
		||||
	return &testCtx{ssa, caConfig, reg, pa, fc, &stats, cleanUp}
 | 
			
		||||
	return &testCtx{
 | 
			
		||||
		ssa,
 | 
			
		||||
		caConfig,
 | 
			
		||||
		reg,
 | 
			
		||||
		pa,
 | 
			
		||||
		fc,
 | 
			
		||||
		&stats,
 | 
			
		||||
		cleanUp,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFailNoSerial(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -201,14 +223,14 @@ func TestFailNoSerial(t *testing.T) {
 | 
			
		|||
	defer ctx.cleanUp()
 | 
			
		||||
 | 
			
		||||
	ctx.caConfig.SerialPrefix = 0
 | 
			
		||||
	_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	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, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	test.AssertNotError(t, err, "Failed to create CA")
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +307,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
func TestRejectNoName(t *testing.T) {
 | 
			
		||||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	test.AssertNotError(t, err, "Failed to create CA")
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
| 
						 | 
				
			
			@ -302,7 +324,7 @@ func TestRejectNoName(t *testing.T) {
 | 
			
		|||
func TestRejectTooManyNames(t *testing.T) {
 | 
			
		||||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	test.AssertNotError(t, err, "Failed to create CA")
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +341,7 @@ func TestRejectTooManyNames(t *testing.T) {
 | 
			
		|||
func TestDeduplication(t *testing.T) {
 | 
			
		||||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	test.AssertNotError(t, err, "Failed to create CA")
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
| 
						 | 
				
			
			@ -343,7 +365,7 @@ func TestDeduplication(t *testing.T) {
 | 
			
		|||
func TestRejectValidityTooLong(t *testing.T) {
 | 
			
		||||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	test.AssertNotError(t, err, "Failed to create CA")
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
| 
						 | 
				
			
			@ -351,7 +373,7 @@ func TestRejectValidityTooLong(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	// Test that the CA rejects CSRs that would expire after the intermediate cert
 | 
			
		||||
	csr, _ := x509.ParseCertificateRequest(NoCNCSR)
 | 
			
		||||
	ca.NotAfter = ctx.fc.Now()
 | 
			
		||||
	ca.notAfter = ctx.fc.Now()
 | 
			
		||||
	_, err = ca.IssueCertificate(*csr, 1)
 | 
			
		||||
	test.AssertEquals(t, err.Error(), "Cannot issue a certificate that expires after the intermediate certificate.")
 | 
			
		||||
	_, ok := err.(core.InternalServerError)
 | 
			
		||||
| 
						 | 
				
			
			@ -361,7 +383,7 @@ func TestRejectValidityTooLong(t *testing.T) {
 | 
			
		|||
func TestShortKey(t *testing.T) {
 | 
			
		||||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
	ca.SA = ctx.sa
 | 
			
		||||
| 
						 | 
				
			
			@ -377,7 +399,7 @@ func TestShortKey(t *testing.T) {
 | 
			
		|||
func TestRejectBadAlgorithm(t *testing.T) {
 | 
			
		||||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
	ca.SA = ctx.sa
 | 
			
		||||
| 
						 | 
				
			
			@ -394,7 +416,7 @@ func TestCapitalizedLetters(t *testing.T) {
 | 
			
		|||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
	ctx.caConfig.MaxNames = 3
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
	ca.SA = ctx.sa
 | 
			
		||||
| 
						 | 
				
			
			@ -415,7 +437,7 @@ func TestHSMFaultTimeout(t *testing.T) {
 | 
			
		|||
	ctx := setup(t)
 | 
			
		||||
	defer ctx.cleanUp()
 | 
			
		||||
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
 | 
			
		||||
	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	ca.PA = ctx.pa
 | 
			
		||||
	ca.SA = ctx.sa
 | 
			
		||||
| 
						 | 
				
			
			@ -429,13 +451,13 @@ func TestHSMFaultTimeout(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Swap in a bad signer
 | 
			
		||||
	goodSigner := ca.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
 | 
			
		||||
	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)
 | 
			
		||||
| 
						 | 
				
			
			@ -450,7 +472,7 @@ func TestHSMFaultTimeout(t *testing.T) {
 | 
			
		|||
	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
 | 
			
		||||
	ca.signer = goodSigner
 | 
			
		||||
	ctx.fc.Add(ca.hsmFaultTimeout)
 | 
			
		||||
	ctx.fc.Add(10 * time.Second)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -460,7 +482,7 @@ func TestHSMFaultTimeout(t *testing.T) {
 | 
			
		|||
	_, err = ca.GenerateOCSP(ocspRequest)
 | 
			
		||||
 | 
			
		||||
	// Check that GenerateOCSP can also trigger an HSM failure, in the same way
 | 
			
		||||
	ca.OCSPSigner = badOCSPSigner
 | 
			
		||||
	ca.ocspSigner = badOCSPSigner
 | 
			
		||||
	_, err = ca.GenerateOCSP(ocspRequest)
 | 
			
		||||
	test.AssertError(t, err, "CA failed to return HSM error")
 | 
			
		||||
	test.AssertEquals(t, err.Error(), badHSMErrorMessage)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,9 +10,6 @@ package main
 | 
			
		|||
// broker to look for anomalies.
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,106 +19,24 @@ import (
 | 
			
		|||
	"github.com/letsencrypt/boulder/rpc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Constants for AMQP
 | 
			
		||||
const (
 | 
			
		||||
	QueueName        = "Monitor"
 | 
			
		||||
	AmqpExchange     = "boulder"
 | 
			
		||||
	AmqpExchangeType = "topic"
 | 
			
		||||
	AmqpInternal     = false
 | 
			
		||||
	AmqpDurable      = false
 | 
			
		||||
	AmqpDeleteUnused = false
 | 
			
		||||
	AmqpExclusive    = false
 | 
			
		||||
	AmqpNoWait       = false
 | 
			
		||||
	AmqpNoLocal      = false
 | 
			
		||||
	AmqpAutoAck      = false
 | 
			
		||||
	AmqpMandatory    = false
 | 
			
		||||
	AmqpImmediate    = false
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func startMonitor(rpcCh *amqp.Channel, logger *blog.AuditLogger, stats statsd.Statter) {
 | 
			
		||||
	ae := analysisengine.NewLoggingAnalysisEngine()
 | 
			
		||||
 | 
			
		||||
	// For convenience at the broker, identifiy ourselves by hostname
 | 
			
		||||
	consumerTag, err := os.Hostname()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.FailOnError(err, "Could not determine hostname")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = rpcCh.QueueDeclarePassive(
 | 
			
		||||
		QueueName,
 | 
			
		||||
		AmqpDurable,
 | 
			
		||||
		AmqpDeleteUnused,
 | 
			
		||||
		AmqpExclusive,
 | 
			
		||||
		AmqpNoWait,
 | 
			
		||||
		nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Info(fmt.Sprintf("Queue %s does not exist on AMQP server, attempting to create.", QueueName))
 | 
			
		||||
 | 
			
		||||
		// Attempt to create the Queue if not exists
 | 
			
		||||
		_, err = rpcCh.QueueDeclare(
 | 
			
		||||
			QueueName,
 | 
			
		||||
			AmqpDurable,
 | 
			
		||||
			AmqpDeleteUnused,
 | 
			
		||||
			AmqpExclusive,
 | 
			
		||||
			AmqpNoWait,
 | 
			
		||||
			nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			cmd.FailOnError(err, "Could not declare queue")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		routingKey := "#" //wildcard
 | 
			
		||||
 | 
			
		||||
		err = rpcCh.QueueBind(
 | 
			
		||||
			QueueName,
 | 
			
		||||
			routingKey,
 | 
			
		||||
			AmqpExchange,
 | 
			
		||||
			false,
 | 
			
		||||
			nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			txt := fmt.Sprintf("Could not bind to queue [%s]. NOTE: You may need to delete %s to re-trigger the bind attempt after fixing permissions, or manually bind the queue to %s.", QueueName, QueueName, routingKey)
 | 
			
		||||
			cmd.FailOnError(err, txt)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deliveries, err := rpcCh.Consume(
 | 
			
		||||
		QueueName,
 | 
			
		||||
		consumerTag,
 | 
			
		||||
		AmqpAutoAck,
 | 
			
		||||
		AmqpExclusive,
 | 
			
		||||
		AmqpNoLocal,
 | 
			
		||||
		AmqpNoWait,
 | 
			
		||||
		nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.FailOnError(err, "Could not subscribe to queue")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Run forever.
 | 
			
		||||
	for d := range deliveries {
 | 
			
		||||
		// Pass each message to the Analysis Engine
 | 
			
		||||
		err = ae.ProcessMessage(d)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Alert(fmt.Sprintf("Could not process message: %s", err))
 | 
			
		||||
		} else {
 | 
			
		||||
			// Only ack the delivery we actually handled (ackMultiple=false)
 | 
			
		||||
			const ackMultiple = false
 | 
			
		||||
			d.Ack(ackMultiple)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	app := cmd.NewAppShell("activity-monitor", "RPC activity monitor")
 | 
			
		||||
 | 
			
		||||
	app.Action = func(c cmd.Config, stats statsd.Statter, auditlogger *blog.AuditLogger) {
 | 
			
		||||
		go cmd.DebugServer(c.ActivityMonitor.DebugAddr)
 | 
			
		||||
 | 
			
		||||
		ch, err := rpc.AmqpChannel(c.ActivityMonitor.AMQP)
 | 
			
		||||
 | 
			
		||||
		amqpConf := c.ActivityMonitor.AMQP
 | 
			
		||||
		server, err := rpc.NewAmqpRPCServer(amqpConf, 0, stats)
 | 
			
		||||
		cmd.FailOnError(err, "Could not connect to AMQP")
 | 
			
		||||
 | 
			
		||||
		ae := analysisengine.NewLoggingAnalysisEngine()
 | 
			
		||||
		server.HandleDeliveries(rpc.DeliveryHandler(func(d amqp.Delivery) {
 | 
			
		||||
			ae.ProcessMessage(d)
 | 
			
		||||
		}))
 | 
			
		||||
 | 
			
		||||
		go cmd.ProfileCmd("AM", stats)
 | 
			
		||||
 | 
			
		||||
		startMonitor(ch, auditlogger, stats)
 | 
			
		||||
		server.Start(amqpConf)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	app.Run()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,10 +6,18 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
 | 
			
		||||
	"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/crypto/pkcs11key"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
 | 
			
		||||
	"github.com/letsencrypt/boulder/ca"
 | 
			
		||||
	"github.com/letsencrypt/boulder/cmd"
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	blog "github.com/letsencrypt/boulder/log"
 | 
			
		||||
	"github.com/letsencrypt/boulder/policy"
 | 
			
		||||
	"github.com/letsencrypt/boulder/rpc"
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +26,40 @@ import (
 | 
			
		|||
 | 
			
		||||
const clientName = "CA"
 | 
			
		||||
 | 
			
		||||
func loadPrivateKey(keyConfig cmd.KeyConfig) (crypto.Signer, error) {
 | 
			
		||||
	if keyConfig.File != "" {
 | 
			
		||||
		keyBytes, err := ioutil.ReadFile(keyConfig.File)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Could not read key file %s", keyConfig.File)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return helpers.ParsePrivateKeyPEM(keyBytes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var pkcs11Config *pkcs11key.Config
 | 
			
		||||
	if keyConfig.ConfigFile != "" {
 | 
			
		||||
		contents, err := ioutil.ReadFile(keyConfig.ConfigFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		pkcs11Config = new(pkcs11key.Config)
 | 
			
		||||
		err = json.Unmarshal(contents, pkcs11Config)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		pkcs11Config = keyConfig.PKCS11
 | 
			
		||||
	}
 | 
			
		||||
	if pkcs11Config.Module == "" ||
 | 
			
		||||
		pkcs11Config.TokenLabel == "" ||
 | 
			
		||||
		pkcs11Config.PIN == "" ||
 | 
			
		||||
		pkcs11Config.PrivateKeyLabel == "" {
 | 
			
		||||
		return nil, fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
 | 
			
		||||
	}
 | 
			
		||||
	return pkcs11key.New(pkcs11Config.Module,
 | 
			
		||||
		pkcs11Config.TokenLabel, pkcs11Config.PIN, pkcs11Config.PrivateKeyLabel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	app := cmd.NewAppShell("boulder-ca", "Handles issuance operations")
 | 
			
		||||
	app.Action = func(c cmd.Config, stats statsd.Statter, auditlogger *blog.AuditLogger) {
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +79,18 @@ func main() {
 | 
			
		|||
		pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
 | 
			
		||||
		cmd.FailOnError(err, "Couldn't create PA")
 | 
			
		||||
 | 
			
		||||
		cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), stats, c.Common.IssuerCert)
 | 
			
		||||
		priv, err := loadPrivateKey(c.CA.Key)
 | 
			
		||||
		cmd.FailOnError(err, "Couldn't load private key")
 | 
			
		||||
 | 
			
		||||
		issuer, err := core.LoadCert(c.Common.IssuerCert)
 | 
			
		||||
		cmd.FailOnError(err, "Couldn't load issuer cert")
 | 
			
		||||
 | 
			
		||||
		cai, err := ca.NewCertificateAuthorityImpl(
 | 
			
		||||
			c.CA,
 | 
			
		||||
			clock.Default(),
 | 
			
		||||
			stats,
 | 
			
		||||
			issuer,
 | 
			
		||||
			priv)
 | 
			
		||||
		cmd.FailOnError(err, "Failed to create CA impl")
 | 
			
		||||
		cai.PA = pa
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	"github.com/letsencrypt/boulder/va"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -265,16 +266,10 @@ func (pc *PAConfig) SetDefaultChallengesIfEmpty() {
 | 
			
		|||
// KeyConfig should contain either a File path to a PEM-format private key,
 | 
			
		||||
// or a PKCS11Config defining how to load a module for an HSM.
 | 
			
		||||
type KeyConfig struct {
 | 
			
		||||
	File   string
 | 
			
		||||
	PKCS11 PKCS11Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PKCS11Config defines how to load a module for an HSM.
 | 
			
		||||
type PKCS11Config struct {
 | 
			
		||||
	Module          string
 | 
			
		||||
	TokenLabel      string
 | 
			
		||||
	PIN             string
 | 
			
		||||
	PrivateKeyLabel string
 | 
			
		||||
	// A file from which a pkcs11key.Config will be read and parsed, if present
 | 
			
		||||
	ConfigFile string
 | 
			
		||||
	File       string
 | 
			
		||||
	PKCS11     *pkcs11key.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TLSConfig reprents certificates and a key for authenticated TLS.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
// Copyright 2014 ISRG.  All rights reserved
 | 
			
		||||
// This Source Code Form is subject to the terms of the Mozilla Public
 | 
			
		||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
			
		||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
// This command does a one-time setup of the RabbitMQ exchange and the Activity
 | 
			
		||||
// Monitor queue, suitable for setting up a dev environment or Travis.
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/cmd"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var server = flag.String("server", "", "RabbitMQ Server URL")
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Constants for AMQP
 | 
			
		||||
const (
 | 
			
		||||
	monitorQueueName = "Monitor"
 | 
			
		||||
	amqpExchange     = "boulder"
 | 
			
		||||
	amqpExchangeType = "topic"
 | 
			
		||||
	amqpInternal     = false
 | 
			
		||||
	amqpDurable      = false
 | 
			
		||||
	amqpDeleteUnused = false
 | 
			
		||||
	amqpExclusive    = false
 | 
			
		||||
	amqpNoWait       = false
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	server := *server
 | 
			
		||||
	conn, err := amqp.Dial(server)
 | 
			
		||||
	cmd.FailOnError(err, "Could not connect to AMQP")
 | 
			
		||||
	ch, err := conn.Channel()
 | 
			
		||||
	cmd.FailOnError(err, "Could not connect to AMQP")
 | 
			
		||||
 | 
			
		||||
	err = ch.ExchangeDeclare(
 | 
			
		||||
		amqpExchange,
 | 
			
		||||
		amqpExchangeType,
 | 
			
		||||
		amqpDurable,
 | 
			
		||||
		amqpDeleteUnused,
 | 
			
		||||
		amqpInternal,
 | 
			
		||||
		amqpNoWait,
 | 
			
		||||
		nil)
 | 
			
		||||
	cmd.FailOnError(err, "Declaring exchange")
 | 
			
		||||
 | 
			
		||||
	_, err = ch.QueueDeclare(
 | 
			
		||||
		monitorQueueName,
 | 
			
		||||
		amqpDurable,
 | 
			
		||||
		amqpDeleteUnused,
 | 
			
		||||
		amqpExclusive,
 | 
			
		||||
		amqpNoWait,
 | 
			
		||||
		nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.FailOnError(err, "Could not declare queue")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	routingKey := "#" //wildcard
 | 
			
		||||
 | 
			
		||||
	err = ch.QueueBind(
 | 
			
		||||
		monitorQueueName,
 | 
			
		||||
		routingKey,
 | 
			
		||||
		amqpExchange,
 | 
			
		||||
		false,
 | 
			
		||||
		nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		txt := fmt.Sprintf("Could not bind to queue [%s]. NOTE: You may need to delete %s to re-trigger the bind attempt after fixing permissions, or manually bind the queue to %s.", monitorQueueName, monitorQueueName, routingKey)
 | 
			
		||||
		cmd.FailOnError(err, txt)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AcmeStatus defines the state of a given authorization
 | 
			
		||||
| 
						 | 
				
			
			@ -33,16 +34,6 @@ type IdentifierType string
 | 
			
		|||
// OCSPStatus defines the state of OCSP for a domain
 | 
			
		||||
type OCSPStatus string
 | 
			
		||||
 | 
			
		||||
// ProblemType defines the error types in the ACME protocol
 | 
			
		||||
type ProblemType string
 | 
			
		||||
 | 
			
		||||
// ProblemDetails objects represent problem documents
 | 
			
		||||
// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
 | 
			
		||||
type ProblemDetails struct {
 | 
			
		||||
	Type   ProblemType `json:"type,omitempty"`
 | 
			
		||||
	Detail string      `json:"detail,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// These statuses are the states of authorizations
 | 
			
		||||
const (
 | 
			
		||||
	StatusUnknown    = AcmeStatus("unknown")    // Unknown status; the default
 | 
			
		||||
| 
						 | 
				
			
			@ -74,18 +65,6 @@ const (
 | 
			
		|||
	OCSPStatusRevoked = OCSPStatus("revoked")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Error types that can be used in ACME payloads
 | 
			
		||||
const (
 | 
			
		||||
	ConnectionProblem     = ProblemType("urn:acme:error:connection")
 | 
			
		||||
	MalformedProblem      = ProblemType("urn:acme:error:malformed")
 | 
			
		||||
	ServerInternalProblem = ProblemType("urn:acme:error:serverInternal")
 | 
			
		||||
	TLSProblem            = ProblemType("urn:acme:error:tls")
 | 
			
		||||
	UnauthorizedProblem   = ProblemType("urn:acme:error:unauthorized")
 | 
			
		||||
	UnknownHostProblem    = ProblemType("urn:acme:error:unknownHost")
 | 
			
		||||
	RateLimitedProblem    = ProblemType("urn:acme:error:rateLimited")
 | 
			
		||||
	BadNonceProblem       = ProblemType("urn:acme:error:badNonce")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// These types are the available challenges
 | 
			
		||||
const (
 | 
			
		||||
	ChallengeTypeSimpleHTTP = "simpleHttp"
 | 
			
		||||
| 
						 | 
				
			
			@ -122,10 +101,6 @@ const TLSSNISuffix = "acme.invalid"
 | 
			
		|||
// DNSPrefix is attached to DNS names in DNS challenges
 | 
			
		||||
const DNSPrefix = "_acme-challenge"
 | 
			
		||||
 | 
			
		||||
func (pd *ProblemDetails) Error() string {
 | 
			
		||||
	return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// An AcmeIdentifier encodes an identifier that can
 | 
			
		||||
// be validated by ACME.  The protocol allows for different
 | 
			
		||||
// types of identifier to be supported (DNS names, IP
 | 
			
		||||
| 
						 | 
				
			
			@ -326,7 +301,7 @@ type Challenge struct {
 | 
			
		|||
	Status AcmeStatus `json:"status,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Contains the error that occured during challenge validation, if any
 | 
			
		||||
	Error *ProblemDetails `json:"error,omitempty"`
 | 
			
		||||
	Error *probs.ProblemDetails `json:"error,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// If successful, the time at which this challenge
 | 
			
		||||
	// was completed by the server.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,13 +17,6 @@ import (
 | 
			
		|||
	"github.com/letsencrypt/boulder/test"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestProblemDetails(t *testing.T) {
 | 
			
		||||
	pd := &ProblemDetails{
 | 
			
		||||
		Type:   MalformedProblem,
 | 
			
		||||
		Detail: "Wat? o.O"}
 | 
			
		||||
	test.AssertEquals(t, pd.Error(), "urn:acme:error:malformed :: Wat? o.O")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRegistrationUpdate(t *testing.T) {
 | 
			
		||||
	oldURL, _ := ParseAcmeURL("http://old.invalid")
 | 
			
		||||
	newURL, _ := ParseAcmeURL("http://new.invalid")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ package dns
 | 
			
		|||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const detailDNSTimeout = "DNS query timed out"
 | 
			
		||||
| 
						 | 
				
			
			@ -14,8 +14,8 @@ const detailServerFailure = "Server failure at resolver"
 | 
			
		|||
// methods and tests if the error was an underlying net.OpError or an error
 | 
			
		||||
// caused by resolver returning SERVFAIL or other invalid Rcodes and returns
 | 
			
		||||
// the relevant core.ProblemDetails.
 | 
			
		||||
func ProblemDetailsFromDNSError(err error) *core.ProblemDetails {
 | 
			
		||||
	problem := &core.ProblemDetails{Type: core.ConnectionProblem}
 | 
			
		||||
func ProblemDetailsFromDNSError(err error) *probs.ProblemDetails {
 | 
			
		||||
	problem := &probs.ProblemDetails{Type: probs.ConnectionProblem}
 | 
			
		||||
	if netErr, ok := err.(*net.OpError); ok {
 | 
			
		||||
		if netErr.Timeout() {
 | 
			
		||||
			problem.Detail = detailDNSTimeout
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,8 +5,8 @@ import (
 | 
			
		|||
	"net"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	"github.com/letsencrypt/boulder/mocks"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestProblemDetailsFromDNSError(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +27,8 @@ func TestProblemDetailsFromDNSError(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		err := ProblemDetailsFromDNSError(tc.err)
 | 
			
		||||
		if err.Type != core.ConnectionProblem {
 | 
			
		||||
			t.Errorf("ProblemDetailsFromDNSError(%q).Type = %q, expected %q", tc.err, err.Type, core.ConnectionProblem)
 | 
			
		||||
		if err.Type != probs.ConnectionProblem {
 | 
			
		||||
			t.Errorf("ProblemDetailsFromDNSError(%q).Type = %q, expected %q", tc.err, err.Type, probs.ConnectionProblem)
 | 
			
		||||
		}
 | 
			
		||||
		if err.Detail != tc.expected {
 | 
			
		||||
			t.Errorf("ProblemDetailsFromDNSError(%q).Detail = %q, expected %q", tc.err, err.Detail, tc.expected)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
package probs
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// Error types that can be used in ACME payloads
 | 
			
		||||
const (
 | 
			
		||||
	ConnectionProblem     = ProblemType("urn:acme:error:connection")
 | 
			
		||||
	MalformedProblem      = ProblemType("urn:acme:error:malformed")
 | 
			
		||||
	ServerInternalProblem = ProblemType("urn:acme:error:serverInternal")
 | 
			
		||||
	TLSProblem            = ProblemType("urn:acme:error:tls")
 | 
			
		||||
	UnauthorizedProblem   = ProblemType("urn:acme:error:unauthorized")
 | 
			
		||||
	UnknownHostProblem    = ProblemType("urn:acme:error:unknownHost")
 | 
			
		||||
	RateLimitedProblem    = ProblemType("urn:acme:error:rateLimited")
 | 
			
		||||
	BadNonceProblem       = ProblemType("urn:acme:error:badNonce")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ProblemType defines the error types in the ACME protocol
 | 
			
		||||
type ProblemType string
 | 
			
		||||
 | 
			
		||||
// ProblemDetails objects represent problem documents
 | 
			
		||||
// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
 | 
			
		||||
type ProblemDetails struct {
 | 
			
		||||
	Type       ProblemType `json:"type,omitempty"`
 | 
			
		||||
	Detail     string      `json:"detail,omitempty"`
 | 
			
		||||
	HTTPStatus int         `json:"status,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *ProblemDetails) Error() string {
 | 
			
		||||
	return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
package probs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/test"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestProblemDetails(t *testing.T) {
 | 
			
		||||
	pd := &ProblemDetails{
 | 
			
		||||
		Type:   MalformedProblem,
 | 
			
		||||
		Detail: "Wat? o.O"}
 | 
			
		||||
	test.AssertEquals(t, pd.Error(), "urn:acme:error:malformed :: Wat? o.O")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,8 +20,6 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
 | 
			
		||||
	cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
 | 
			
		||||
	jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
 | 
			
		||||
	"github.com/letsencrypt/boulder/ca"
 | 
			
		||||
| 
						 | 
				
			
			@ -180,20 +178,26 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
 | 
			
		|||
	caKey, _ := x509.ParsePKCS1PrivateKey(caKeyPEM.Bytes)
 | 
			
		||||
	caCertPEM, _ := pem.Decode([]byte(CAcertPEM))
 | 
			
		||||
	caCert, _ := x509.ParseCertificate(caCertPEM.Bytes)
 | 
			
		||||
	basicPolicy := &cfsslConfig.Signing{
 | 
			
		||||
		Default: &cfsslConfig.SigningProfile{
 | 
			
		||||
			Usage:  []string{"server auth", "client auth"},
 | 
			
		||||
			Expiry: 1 * time.Hour,
 | 
			
		||||
			CSRWhitelist: &cfsslConfig.CSRWhitelist{
 | 
			
		||||
				PublicKey:          true,
 | 
			
		||||
				PublicKeyAlgorithm: true,
 | 
			
		||||
				SignatureAlgorithm: true,
 | 
			
		||||
				DNSNames:           true,
 | 
			
		||||
	cfsslC := cfsslConfig.Config{
 | 
			
		||||
		Signing: &cfsslConfig.Signing{
 | 
			
		||||
			Default: &cfsslConfig.SigningProfile{
 | 
			
		||||
				Usage:        []string{"server auth", "client auth"},
 | 
			
		||||
				ExpiryString: "1h",
 | 
			
		||||
				CSRWhitelist: &cfsslConfig.CSRWhitelist{
 | 
			
		||||
					PublicKey:          true,
 | 
			
		||||
					PublicKeyAlgorithm: true,
 | 
			
		||||
					SignatureAlgorithm: true,
 | 
			
		||||
					DNSNames:           true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	signer, _ := local.NewSigner(caKey, caCert, x509.SHA256WithRSA, basicPolicy)
 | 
			
		||||
	ocspSigner, _ := ocsp.NewSigner(caCert, caCert, caKey, time.Hour)
 | 
			
		||||
	caConf := cmd.CAConfig{
 | 
			
		||||
		SerialPrefix: 10,
 | 
			
		||||
		LifespanOCSP: "1h",
 | 
			
		||||
		Expiry:       "1h",
 | 
			
		||||
		CFSSL:        cfsslC,
 | 
			
		||||
	}
 | 
			
		||||
	paDbMap, err := sa.NewDbMap(vars.DBConnPolicy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Failed to create dbMap: %s", err)
 | 
			
		||||
| 
						 | 
				
			
			@ -201,16 +205,19 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
 | 
			
		|||
	policyDBCleanUp := test.ResetPolicyTestDatabase(t)
 | 
			
		||||
	pa, err := policy.NewPolicyAuthorityImpl(paDbMap, false, SupportedChallenges)
 | 
			
		||||
	test.AssertNotError(t, err, "Couldn't create PA")
 | 
			
		||||
	ca := ca.CertificateAuthorityImpl{
 | 
			
		||||
		Signer:         signer,
 | 
			
		||||
		OCSPSigner:     ocspSigner,
 | 
			
		||||
		SA:             ssa,
 | 
			
		||||
		PA:             pa,
 | 
			
		||||
		ValidityPeriod: time.Hour * 2190,
 | 
			
		||||
		NotAfter:       time.Now().Add(time.Hour * 8761),
 | 
			
		||||
		Clk:            fc,
 | 
			
		||||
		Publisher:      &mocks.Publisher{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stats, _ := statsd.NewNoopClient()
 | 
			
		||||
 | 
			
		||||
	ca, err := ca.NewCertificateAuthorityImpl(
 | 
			
		||||
		caConf,
 | 
			
		||||
		fc,
 | 
			
		||||
		stats,
 | 
			
		||||
		caCert,
 | 
			
		||||
		caKey)
 | 
			
		||||
	test.AssertNotError(t, err, "Couldn't create CA")
 | 
			
		||||
	ca.SA = ssa
 | 
			
		||||
	ca.PA = pa
 | 
			
		||||
	ca.Publisher = &mocks.Publisher{}
 | 
			
		||||
	cleanUp := func() {
 | 
			
		||||
		saDBCleanUp()
 | 
			
		||||
		policyDBCleanUp()
 | 
			
		||||
| 
						 | 
				
			
			@ -224,7 +231,6 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
 | 
			
		|||
		InitialIP: net.ParseIP("3.2.3.3"),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	stats, _ := statsd.NewNoopClient()
 | 
			
		||||
	ra := NewRegistrationAuthorityImpl(fc,
 | 
			
		||||
		blog.GetAuditLogger(),
 | 
			
		||||
		stats,
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +243,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
 | 
			
		|||
		}, 1)
 | 
			
		||||
	ra.SA = ssa
 | 
			
		||||
	ra.VA = va
 | 
			
		||||
	ra.CA = &ca
 | 
			
		||||
	ra.CA = ca
 | 
			
		||||
	ra.PA = pa
 | 
			
		||||
	ra.DNSResolver = &mocks.DNSResolver{}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										121
									
								
								rpc/amqp-rpc.go
								
								
								
								
							
							
						
						
									
										121
									
								
								rpc/amqp-rpc.go
								
								
								
								
							| 
						 | 
				
			
			@ -25,6 +25,7 @@ import (
 | 
			
		|||
	"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/Godeps/_workspace/src/github.com/streadway/amqp"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/cmd"
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
| 
						 | 
				
			
			@ -57,59 +58,6 @@ const (
 | 
			
		|||
	consumerName     = "boulder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AMQPDeclareExchange attempts to declare the configured AMQP exchange,
 | 
			
		||||
// returning silently if already declared, erroring if nonexistant and
 | 
			
		||||
// unable to create.
 | 
			
		||||
func amqpDeclareExchange(conn *amqp.Connection) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var ch *amqp.Channel
 | 
			
		||||
	log := blog.GetAuditLogger()
 | 
			
		||||
 | 
			
		||||
	ch, err = conn.Channel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Crit(fmt.Sprintf("Could not connect Channel: %s", err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = ch.ExchangeDeclarePassive(
 | 
			
		||||
		AmqpExchange,
 | 
			
		||||
		AmqpExchangeType,
 | 
			
		||||
		AmqpDurable,
 | 
			
		||||
		AmqpDeleteUnused,
 | 
			
		||||
		AmqpInternal,
 | 
			
		||||
		AmqpNoWait,
 | 
			
		||||
		nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Info(fmt.Sprintf("Exchange %s does not exist on AMQP server, creating.", AmqpExchange))
 | 
			
		||||
 | 
			
		||||
		// Channel is invalid at this point, so recreate
 | 
			
		||||
		ch.Close()
 | 
			
		||||
		ch, err = conn.Channel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Crit(fmt.Sprintf("Could not connect Channel: %s", err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = ch.ExchangeDeclare(
 | 
			
		||||
			AmqpExchange,
 | 
			
		||||
			AmqpExchangeType,
 | 
			
		||||
			AmqpDurable,
 | 
			
		||||
			AmqpDeleteUnused,
 | 
			
		||||
			AmqpInternal,
 | 
			
		||||
			AmqpNoWait,
 | 
			
		||||
			nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Crit(fmt.Sprintf("Could not declare exchange: %s", err))
 | 
			
		||||
			ch.Close()
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Info(fmt.Sprintf("Created exchange %s.", AmqpExchange))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ch.Close()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A simplified way to declare and subscribe to an AMQP queue
 | 
			
		||||
func amqpSubscribe(ch amqpChannel, name string) (<-chan amqp.Delivery, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
| 
						 | 
				
			
			@ -135,8 +83,8 @@ func amqpSubscribe(ch amqpChannel, name string) (<-chan amqp.Delivery, error) {
 | 
			
		|||
		nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf(
 | 
			
		||||
			"Could not bind to queue [%s]. NOTE: You may need to delete %s to re-trigger the bind attempt after fixing permissions, or manually bind the queue to %s.",
 | 
			
		||||
			name, name, routingKey)
 | 
			
		||||
			"Could not bind to queue %s: %s. NOTE: You may need to delete it to re-trigger the bind attempt after fixing permissions, or manually bind the queue to %s.",
 | 
			
		||||
			err, name, routingKey)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -155,6 +103,10 @@ func amqpSubscribe(ch amqpChannel, name string) (<-chan amqp.Delivery, error) {
 | 
			
		|||
	return msgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeliveryHandler is a function that will process an amqp.DeliveryHandler
 | 
			
		||||
type DeliveryHandler func(amqp.Delivery)
 | 
			
		||||
type messageHandler func([]byte) ([]byte, error)
 | 
			
		||||
 | 
			
		||||
// AmqpRPCServer listens on a specified queue within an AMQP channel.
 | 
			
		||||
// When messages arrive on that queue, it dispatches them based on type,
 | 
			
		||||
// and returns the response to the ReplyTo queue.
 | 
			
		||||
| 
						 | 
				
			
			@ -162,10 +114,13 @@ func amqpSubscribe(ch amqpChannel, name string) (<-chan amqp.Delivery, error) {
 | 
			
		|||
// To implement specific functionality, using code should use the Handle
 | 
			
		||||
// method to add specific actions.
 | 
			
		||||
type AmqpRPCServer struct {
 | 
			
		||||
	serverQueue                    string
 | 
			
		||||
	connection                     *amqpConnector
 | 
			
		||||
	log                            *blog.AuditLogger
 | 
			
		||||
	dispatchTable                  map[string]func([]byte) ([]byte, error)
 | 
			
		||||
	serverQueue    string
 | 
			
		||||
	connection     *amqpConnector
 | 
			
		||||
	log            *blog.AuditLogger
 | 
			
		||||
	handleDelivery DeliveryHandler
 | 
			
		||||
	// Servers that just care about messages (method + body) add entries to
 | 
			
		||||
	// dispatchTable
 | 
			
		||||
	dispatchTable                  map[string]messageHandler
 | 
			
		||||
	connected                      bool
 | 
			
		||||
	done                           bool
 | 
			
		||||
	mu                             sync.RWMutex
 | 
			
		||||
| 
						 | 
				
			
			@ -194,7 +149,7 @@ func NewAmqpRPCServer(amqpConf *cmd.AMQPConfig, maxConcurrentRPCServerRequests i
 | 
			
		|||
		serverQueue:                    amqpConf.ServiceQueue,
 | 
			
		||||
		connection:                     newAMQPConnector(amqpConf.ServiceQueue, reconnectBase, reconnectMax),
 | 
			
		||||
		log:                            log,
 | 
			
		||||
		dispatchTable:                  make(map[string]func([]byte) ([]byte, error)),
 | 
			
		||||
		dispatchTable:                  make(map[string]messageHandler),
 | 
			
		||||
		maxConcurrentRPCServerRequests: maxConcurrentRPCServerRequests,
 | 
			
		||||
		clk:   clock.Default(),
 | 
			
		||||
		stats: stats,
 | 
			
		||||
| 
						 | 
				
			
			@ -202,15 +157,27 @@ func NewAmqpRPCServer(amqpConf *cmd.AMQPConfig, maxConcurrentRPCServerRequests i
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Handle registers a function to handle a particular method.
 | 
			
		||||
func (rpc *AmqpRPCServer) Handle(method string, handler func([]byte) ([]byte, error)) {
 | 
			
		||||
func (rpc *AmqpRPCServer) Handle(method string, handler messageHandler) {
 | 
			
		||||
	rpc.mu.Lock()
 | 
			
		||||
	rpc.dispatchTable[method] = handler
 | 
			
		||||
	rpc.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HandleDeliveries allows a server to receive amqp.Delivery directly (e.g.
 | 
			
		||||
// ActivityMonitor), it can provide one of these. Otherwise processMessage is
 | 
			
		||||
// used by default.
 | 
			
		||||
func (rpc *AmqpRPCServer) HandleDeliveries(handler DeliveryHandler) {
 | 
			
		||||
	rpc.mu.Lock()
 | 
			
		||||
	rpc.handleDelivery = handler
 | 
			
		||||
	rpc.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// rpcError is a JSON wrapper for error as it cannot be un/marshalled
 | 
			
		||||
// due to type interface{}.
 | 
			
		||||
type rpcError struct {
 | 
			
		||||
	Value string `json:"value"`
 | 
			
		||||
	Type  string `json:"type,omitempty"`
 | 
			
		||||
	Value      string `json:"value"`
 | 
			
		||||
	Type       string `json:"type,omitempty"`
 | 
			
		||||
	HTTPStatus int    `json:"status,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wraps a error in a rpcError so it can be marshalled to
 | 
			
		||||
| 
						 | 
				
			
			@ -245,10 +212,10 @@ func wrapError(err error) *rpcError {
 | 
			
		|||
			wrapped.Type = "RateLimitedError"
 | 
			
		||||
		case core.ServiceUnavailableError:
 | 
			
		||||
			wrapped.Type = "ServiceUnavailableError"
 | 
			
		||||
		case *core.ProblemDetails:
 | 
			
		||||
		case *probs.ProblemDetails:
 | 
			
		||||
			wrapped.Type = string(terr.Type)
 | 
			
		||||
			wrapped.Value = terr.Detail
 | 
			
		||||
 | 
			
		||||
			wrapped.HTTPStatus = terr.HTTPStatus
 | 
			
		||||
		}
 | 
			
		||||
		return wrapped
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -285,9 +252,10 @@ func unwrapError(rpcError *rpcError) error {
 | 
			
		|||
			return core.ServiceUnavailableError(rpcError.Value)
 | 
			
		||||
		default:
 | 
			
		||||
			if strings.HasPrefix(rpcError.Type, "urn:") {
 | 
			
		||||
				return &core.ProblemDetails{
 | 
			
		||||
					Type:   core.ProblemType(rpcError.Type),
 | 
			
		||||
					Detail: rpcError.Value,
 | 
			
		||||
				return &probs.ProblemDetails{
 | 
			
		||||
					Type:       probs.ProblemType(rpcError.Type),
 | 
			
		||||
					Detail:     rpcError.Value,
 | 
			
		||||
					HTTPStatus: rpcError.HTTPStatus,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return errors.New(rpcError.Value)
 | 
			
		||||
| 
						 | 
				
			
			@ -319,11 +287,11 @@ func (r rpcResponse) debugString() string {
 | 
			
		|||
	if r.Error == nil {
 | 
			
		||||
		return ret
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s, RPCERR: %s", ret, r.Error)
 | 
			
		||||
	return fmt.Sprintf("%s, RPCERR: %v", ret, r.Error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AmqpChannel sets a AMQP connection up using SSL if configuration is provided
 | 
			
		||||
func AmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
 | 
			
		||||
// makeAmqpChannel sets a AMQP connection up using SSL if configuration is provided
 | 
			
		||||
func makeAmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
 | 
			
		||||
	var conn *amqp.Connection
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -387,11 +355,6 @@ func AmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = amqpDeclareExchange(conn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return conn.Channel()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -472,7 +435,11 @@ func (rpc *AmqpRPCServer) Start(c *cmd.AMQPConfig) error {
 | 
			
		|||
					atomic.AddInt64(&rpc.currentGoroutines, 1)
 | 
			
		||||
					defer atomic.AddInt64(&rpc.currentGoroutines, -1)
 | 
			
		||||
					startedProcessing := rpc.clk.Now()
 | 
			
		||||
					rpc.processMessage(msg)
 | 
			
		||||
					if rpc.handleDelivery != nil {
 | 
			
		||||
						rpc.handleDelivery(msg)
 | 
			
		||||
					} else {
 | 
			
		||||
						rpc.processMessage(msg)
 | 
			
		||||
					}
 | 
			
		||||
					rpc.stats.TimingDuration(fmt.Sprintf("RPC.ServerProcessingLatency.%s", msg.Type), time.Since(startedProcessing), 1.0)
 | 
			
		||||
				}()
 | 
			
		||||
			} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ import (
 | 
			
		|||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
	"github.com/letsencrypt/boulder/test"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,17 +44,19 @@ func TestWrapError(t *testing.T) {
 | 
			
		|||
		expected error
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			&core.ProblemDetails{
 | 
			
		||||
				Type:   core.ConnectionProblem,
 | 
			
		||||
				Detail: "whoops",
 | 
			
		||||
			&probs.ProblemDetails{
 | 
			
		||||
				Type:       probs.ConnectionProblem,
 | 
			
		||||
				Detail:     "whoops",
 | 
			
		||||
				HTTPStatus: 417,
 | 
			
		||||
			},
 | 
			
		||||
			&core.ProblemDetails{
 | 
			
		||||
				Type:   core.ConnectionProblem,
 | 
			
		||||
				Detail: "whoops",
 | 
			
		||||
			&probs.ProblemDetails{
 | 
			
		||||
				Type:       probs.ConnectionProblem,
 | 
			
		||||
				Detail:     "whoops",
 | 
			
		||||
				HTTPStatus: 417,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			&core.ProblemDetails{Type: "invalid", Detail: "hm"},
 | 
			
		||||
			&probs.ProblemDetails{Type: "invalid", Detail: "hm"},
 | 
			
		||||
			errors.New("hm"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ type channelMaker interface {
 | 
			
		|||
type defaultChannelMaker struct{}
 | 
			
		||||
 | 
			
		||||
func (d defaultChannelMaker) makeChannel(conf *cmd.AMQPConfig) (amqpChannel, error) {
 | 
			
		||||
	return AmqpChannel(conf)
 | 
			
		||||
	return makeAmqpChannel(conf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// amqpConnector encapsulates an AMQP channel and a subscription to a specific
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,5 +12,5 @@ type Client interface {
 | 
			
		|||
 | 
			
		||||
// Server describes the functions an RPC Server performs
 | 
			
		||||
type Server interface {
 | 
			
		||||
	Handle(string, func([]byte) ([]byte, error))
 | 
			
		||||
	Handle(string, messageHandler)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var mediumBlobSize = int(math.Pow(2, 24))
 | 
			
		||||
| 
						 | 
				
			
			@ -170,7 +171,7 @@ func modelToChallenge(cm *challModel) (core.Challenge, error) {
 | 
			
		|||
		c.KeyAuthorization = &ka
 | 
			
		||||
	}
 | 
			
		||||
	if len(cm.Error) > 0 {
 | 
			
		||||
		var problem core.ProblemDetails
 | 
			
		||||
		var problem probs.ProblemDetails
 | 
			
		||||
		err := json.Unmarshal(cm.Error, &problem)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return core.Challenge{}, err
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,7 @@
 | 
			
		|||
            "issuer_urls": [
 | 
			
		||||
              "http://127.0.0.1:4000/acme/issuer-cert"
 | 
			
		||||
            ],
 | 
			
		||||
            "ocsp_url": "http://127.0.0.1:4002/ocsp",
 | 
			
		||||
            "ocsp_url": "http://127.0.0.1:4002/",
 | 
			
		||||
            "crl_url": "http://example.com/crl",
 | 
			
		||||
            "policies": [
 | 
			
		||||
              {
 | 
			
		||||
| 
						 | 
				
			
			@ -243,6 +243,7 @@
 | 
			
		|||
  "activityMonitor": {
 | 
			
		||||
    "debugAddr": "localhost:8007",
 | 
			
		||||
    "amqp": {
 | 
			
		||||
      "serviceQueue": "Monitor",
 | 
			
		||||
      "server": "amqp://guest:guest@localhost:5673",
 | 
			
		||||
      "insecure": true
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,6 +63,7 @@ def start(race_detection):
 | 
			
		|||
    t.daemon = True
 | 
			
		||||
    t.start()
 | 
			
		||||
    progs = [
 | 
			
		||||
        'activity-monitor',
 | 
			
		||||
        'boulder-wfe',
 | 
			
		||||
        'boulder-ra',
 | 
			
		||||
        'boulder-sa',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,30 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
set -o xtrace
 | 
			
		||||
 | 
			
		||||
# Travis does shallow clones, so there is no master branch present.
 | 
			
		||||
# But test-no-outdated-migrations.sh needs to check diffs against master.
 | 
			
		||||
# Fetch just the master branch from origin.
 | 
			
		||||
( git fetch origin master
 | 
			
		||||
git branch master FETCH_HEAD ) &
 | 
			
		||||
# Github-PR-Status secret
 | 
			
		||||
if [ -n "$encrypted_53b2630f0fb4_key" ]; then
 | 
			
		||||
  openssl aes-256-cbc \
 | 
			
		||||
    -K $encrypted_53b2630f0fb4_key -iv $encrypted_53b2630f0fb4_iv \
 | 
			
		||||
    -in test/github-secret.json.enc -out /tmp/github-secret.json -d
 | 
			
		||||
if [ "${TRAVIS}" == "true" ]; then
 | 
			
		||||
  # Boulder consists of multiple Go packages, which
 | 
			
		||||
  # refer to each other by their absolute GitHub path,
 | 
			
		||||
  # e.g. github.com/letsencrypt/boulder/analysis. That means, by default, if
 | 
			
		||||
  # someone forks the repo, Travis won't pass on their own repo. To fix that,
 | 
			
		||||
  # we add a symlink.
 | 
			
		||||
  mkdir -p $TRAVIS_BUILD_DIR $GOPATH/src/github.com/letsencrypt
 | 
			
		||||
  if [ ! -d $GOPATH/src/github.com/letsencrypt/boulder ] ; then
 | 
			
		||||
    ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/letsencrypt/boulder
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # Travis does shallow clones, so there is no master branch present.
 | 
			
		||||
  # But test-no-outdated-migrations.sh needs to check diffs against master.
 | 
			
		||||
  # Fetch just the master branch from origin.
 | 
			
		||||
  ( git fetch origin master
 | 
			
		||||
  git branch master FETCH_HEAD ) &
 | 
			
		||||
  # Github-PR-Status secret
 | 
			
		||||
  if [ -n "$encrypted_53b2630f0fb4_key" ]; then
 | 
			
		||||
    openssl aes-256-cbc \
 | 
			
		||||
      -K $encrypted_53b2630f0fb4_key -iv $encrypted_53b2630f0fb4_iv \
 | 
			
		||||
      -in test/github-secret.json.enc -out /tmp/github-secret.json -d
 | 
			
		||||
  fi
 | 
			
		||||
else
 | 
			
		||||
  alias travis_retry=""
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
travis_retry go get \
 | 
			
		||||
| 
						 | 
				
			
			@ -27,17 +41,10 @@ travis_retry go get \
 | 
			
		|||
 zcat goose.gz > $GOPATH/bin/goose &&
 | 
			
		||||
 chmod +x $GOPATH/bin/goose) &
 | 
			
		||||
 | 
			
		||||
# Set up rabbitmq exchange and activity monitor queue
 | 
			
		||||
go run cmd/rabbitmq-setup/main.go -server amqp://localhost &
 | 
			
		||||
 | 
			
		||||
# Wait for all the background commands to finish.
 | 
			
		||||
wait
 | 
			
		||||
 | 
			
		||||
# Boulder consists of multiple Go packages, which
 | 
			
		||||
# refer to each other by their absolute GitHub path,
 | 
			
		||||
# e.g. github.com/letsencrypt/boulder/analysis. That means, by default, if
 | 
			
		||||
# someone forks the repo, Travis won't pass on their own repo. To fix that,
 | 
			
		||||
# we add a symlink.
 | 
			
		||||
mkdir -p $TRAVIS_BUILD_DIR $GOPATH/src/github.com/letsencrypt
 | 
			
		||||
if [ ! -d $GOPATH/src/github.com/letsencrypt/boulder ] ; then
 | 
			
		||||
  ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/letsencrypt/boulder
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
set +o xtrace
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@ import (
 | 
			
		|||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/net/publicsuffix"
 | 
			
		||||
	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	bdns "github.com/letsencrypt/boulder/dns"
 | 
			
		||||
| 
						 | 
				
			
			@ -125,7 +126,7 @@ func verifyValidationJWS(validation *jose.JsonWebSignature, accountKey *jose.Jso
 | 
			
		|||
// This is the same choice made by the Go internal resolution library used by
 | 
			
		||||
// net/http, except we only send A queries and accept IPv4 addresses.
 | 
			
		||||
// TODO(#593): Add IPv6 support
 | 
			
		||||
func (va ValidationAuthorityImpl) getAddr(hostname string) (addr net.IP, addrs []net.IP, problem *core.ProblemDetails) {
 | 
			
		||||
func (va ValidationAuthorityImpl) getAddr(hostname string) (addr net.IP, addrs []net.IP, problem *probs.ProblemDetails) {
 | 
			
		||||
	addrs, rtt, err := va.DNSResolver.LookupHost(hostname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		problem = bdns.ProblemDetailsFromDNSError(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -136,8 +137,8 @@ func (va ValidationAuthorityImpl) getAddr(hostname string) (addr net.IP, addrs [
 | 
			
		|||
	va.stats.Inc("VA.DNS.Rate", 1, 1.0)
 | 
			
		||||
 | 
			
		||||
	if len(addrs) == 0 {
 | 
			
		||||
		problem = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnknownHostProblem,
 | 
			
		||||
		problem = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnknownHostProblem,
 | 
			
		||||
			Detail: fmt.Sprintf("No IPv4 addresses found for %s", hostname),
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			@ -158,7 +159,7 @@ func (d *dialer) Dial(_, _ string) (net.Conn, error) {
 | 
			
		|||
 | 
			
		||||
// resolveAndConstructDialer gets the prefered address using va.getAddr and returns
 | 
			
		||||
// the chosen address and dialer for that address and correct port.
 | 
			
		||||
func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name string, port int) (dialer, *core.ProblemDetails) {
 | 
			
		||||
func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name string, port int) (dialer, *probs.ProblemDetails) {
 | 
			
		||||
	d := dialer{
 | 
			
		||||
		record: core.ValidationRecord{
 | 
			
		||||
			Hostname: name,
 | 
			
		||||
| 
						 | 
				
			
			@ -205,8 +206,8 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
 | 
			
		|||
	va.log.Audit(fmt.Sprintf("Attempting to validate %s for %s", challenge.Type, url))
 | 
			
		||||
	httpRequest, err := http.NewRequest("GET", url.String(), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.MalformedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.MalformedProblem,
 | 
			
		||||
			Detail: "URL provided for HTTP was invalid",
 | 
			
		||||
		}
 | 
			
		||||
		va.log.Debug(fmt.Sprintf("%s [%s] HTTP failure: %s", challenge.Type, identifier, err))
 | 
			
		||||
| 
						 | 
				
			
			@ -297,7 +298,7 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
 | 
			
		|||
	httpResponse, err := client.Do(httpRequest)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   parseHTTPConnError(err),
 | 
			
		||||
			Detail: fmt.Sprintf("Could not connect to %s", url),
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -307,8 +308,8 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
 | 
			
		|||
 | 
			
		||||
	if httpResponse.StatusCode != 200 {
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type: core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type: probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: fmt.Sprintf("Invalid response from %s [%s]: %d",
 | 
			
		||||
				url.String(), dialer.record.AddressUsed, httpResponse.StatusCode),
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -320,8 +321,8 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
 | 
			
		|||
	body, err := ioutil.ReadAll(httpResponse.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: fmt.Sprintf("Error reading HTTP response body: %v", err),
 | 
			
		||||
		}
 | 
			
		||||
		return emptyBody, challenge, err
 | 
			
		||||
| 
						 | 
				
			
			@ -359,7 +360,7 @@ func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIden
 | 
			
		|||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   parseHTTPConnError(err),
 | 
			
		||||
			Detail: "Failed to connect to host for DVSNI challenge",
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -371,8 +372,8 @@ func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIden
 | 
			
		|||
	// Check that zName is a dNSName SAN in the server's certificate
 | 
			
		||||
	certs := conn.ConnectionState().PeerCertificates
 | 
			
		||||
	if len(certs) == 0 {
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: "No certs presented for TLS SNI challenge",
 | 
			
		||||
		}
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
| 
						 | 
				
			
			@ -385,9 +386,10 @@ func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIden
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	challenge.Error = &core.ProblemDetails{
 | 
			
		||||
		Type:   core.UnauthorizedProblem,
 | 
			
		||||
		Detail: "Correct zName not found for TLS SNI challenge",
 | 
			
		||||
	challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
		Type: probs.UnauthorizedProblem,
 | 
			
		||||
		Detail: fmt.Sprintf("Correct zName not found for TLS SNI challenge. Found %s",
 | 
			
		||||
			strings.Join(certs[0].DNSNames, ", ")),
 | 
			
		||||
	}
 | 
			
		||||
	challenge.Status = core.StatusInvalid
 | 
			
		||||
	return challenge, challenge.Error
 | 
			
		||||
| 
						 | 
				
			
			@ -399,8 +401,8 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
 | 
			
		|||
 | 
			
		||||
	if identifier.Type != core.IdentifierDNS {
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.MalformedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.MalformedProblem,
 | 
			
		||||
			Detail: "Identifier type for SimpleHTTP was not DNS",
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -424,8 +426,8 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
 | 
			
		|||
		err = fmt.Errorf("Validation response failed to parse as JWS: %s", err.Error())
 | 
			
		||||
		va.log.Debug(err.Error())
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: err.Error(),
 | 
			
		||||
		}
 | 
			
		||||
		return challenge, err
 | 
			
		||||
| 
						 | 
				
			
			@ -444,8 +446,8 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		va.log.Debug(err.Error())
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: err.Error(),
 | 
			
		||||
		}
 | 
			
		||||
		return challenge, err
 | 
			
		||||
| 
						 | 
				
			
			@ -460,8 +462,8 @@ func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
 | 
			
		|||
	challenge := input
 | 
			
		||||
 | 
			
		||||
	if identifier.Type != "dns" {
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.MalformedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.MalformedProblem,
 | 
			
		||||
			Detail: "Identifier type for DVSNI was not DNS",
 | 
			
		||||
		}
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
| 
						 | 
				
			
			@ -480,8 +482,8 @@ func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		va.log.Debug(err.Error())
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: err.Error(),
 | 
			
		||||
		}
 | 
			
		||||
		return challenge, err
 | 
			
		||||
| 
						 | 
				
			
			@ -502,8 +504,8 @@ func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier
 | 
			
		|||
 | 
			
		||||
	if identifier.Type != core.IdentifierDNS {
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.MalformedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.MalformedProblem,
 | 
			
		||||
			Detail: "Identifier type for HTTP validation was not DNS",
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -526,8 +528,8 @@ func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier
 | 
			
		|||
		err = fmt.Errorf("Error parsing key authorization file: %s", err.Error())
 | 
			
		||||
		va.log.Debug(err.Error())
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: err.Error(),
 | 
			
		||||
		}
 | 
			
		||||
		return challenge, err
 | 
			
		||||
| 
						 | 
				
			
			@ -539,8 +541,8 @@ func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier
 | 
			
		|||
			challenge.KeyAuthorization.String(), string(body))
 | 
			
		||||
		va.log.Debug(err.Error())
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.UnauthorizedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.UnauthorizedProblem,
 | 
			
		||||
			Detail: err.Error(),
 | 
			
		||||
		}
 | 
			
		||||
		return challenge, err
 | 
			
		||||
| 
						 | 
				
			
			@ -554,8 +556,8 @@ func (va *ValidationAuthorityImpl) validateTLSSNI01(identifier core.AcmeIdentifi
 | 
			
		|||
	challenge := input
 | 
			
		||||
 | 
			
		||||
	if identifier.Type != "dns" {
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.MalformedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.MalformedProblem,
 | 
			
		||||
			Detail: "Identifier type for TLS-SNI was not DNS",
 | 
			
		||||
		}
 | 
			
		||||
		challenge.Status = core.StatusInvalid
 | 
			
		||||
| 
						 | 
				
			
			@ -574,7 +576,7 @@ func (va *ValidationAuthorityImpl) validateTLSSNI01(identifier core.AcmeIdentifi
 | 
			
		|||
 | 
			
		||||
// parseHTTPConnError returns the ACME ProblemType corresponding to an error
 | 
			
		||||
// that occurred during domain validation.
 | 
			
		||||
func parseHTTPConnError(err error) core.ProblemType {
 | 
			
		||||
func parseHTTPConnError(err error) probs.ProblemType {
 | 
			
		||||
	if urlErr, ok := err.(*url.Error); ok {
 | 
			
		||||
		err = urlErr.Err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -585,21 +587,21 @@ func parseHTTPConnError(err error) core.ProblemType {
 | 
			
		|||
	if netErr, ok := err.(*net.OpError); ok {
 | 
			
		||||
		dnsErr, ok := netErr.Err.(*net.DNSError)
 | 
			
		||||
		if ok && !dnsErr.Timeout() && !dnsErr.Temporary() {
 | 
			
		||||
			return core.UnknownHostProblem
 | 
			
		||||
			return probs.UnknownHostProblem
 | 
			
		||||
		} else if fmt.Sprintf("%T", netErr.Err) == "tls.alert" {
 | 
			
		||||
			return core.TLSProblem
 | 
			
		||||
			return probs.TLSProblem
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return core.ConnectionProblem
 | 
			
		||||
	return probs.ConnectionProblem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
 | 
			
		||||
	challenge := input
 | 
			
		||||
 | 
			
		||||
	if identifier.Type != core.IdentifierDNS {
 | 
			
		||||
		challenge.Error = &core.ProblemDetails{
 | 
			
		||||
			Type:   core.MalformedProblem,
 | 
			
		||||
		challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.MalformedProblem,
 | 
			
		||||
			Detail: "Identifier type for DNS was not itself DNS",
 | 
			
		||||
		}
 | 
			
		||||
		va.log.Debug(fmt.Sprintf("DNS [%s] Identifier failure", identifier))
 | 
			
		||||
| 
						 | 
				
			
			@ -632,15 +634,15 @@ func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier,
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	challenge.Error = &core.ProblemDetails{
 | 
			
		||||
		Type:   core.UnauthorizedProblem,
 | 
			
		||||
	challenge.Error = &probs.ProblemDetails{
 | 
			
		||||
		Type:   probs.UnauthorizedProblem,
 | 
			
		||||
		Detail: "Correct value not found for DNS challenge",
 | 
			
		||||
	}
 | 
			
		||||
	challenge.Status = core.StatusInvalid
 | 
			
		||||
	return challenge, challenge.Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (va *ValidationAuthorityImpl) checkCAA(identifier core.AcmeIdentifier, regID int64) *core.ProblemDetails {
 | 
			
		||||
func (va *ValidationAuthorityImpl) checkCAA(identifier core.AcmeIdentifier, regID int64) *probs.ProblemDetails {
 | 
			
		||||
	// Check CAA records for the requested identifier
 | 
			
		||||
	present, valid, err := va.CheckCAARecords(identifier)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -650,8 +652,8 @@ func (va *ValidationAuthorityImpl) checkCAA(identifier core.AcmeIdentifier, regI
 | 
			
		|||
	// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
 | 
			
		||||
	va.log.Audit(fmt.Sprintf("Checked CAA records for %s, registration ID %d [Present: %t, Valid for issuance: %t]", identifier.Value, regID, present, valid))
 | 
			
		||||
	if !valid {
 | 
			
		||||
		return &core.ProblemDetails{
 | 
			
		||||
			Type:   core.ConnectionProblem,
 | 
			
		||||
		return &probs.ProblemDetails{
 | 
			
		||||
			Type:   probs.ConnectionProblem,
 | 
			
		||||
			Detail: "CAA check for identifier failed",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -669,7 +671,7 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI
 | 
			
		|||
	if !authz.Challenges[challengeIndex].IsSane(true) {
 | 
			
		||||
		chall := &authz.Challenges[challengeIndex]
 | 
			
		||||
		chall.Status = core.StatusInvalid
 | 
			
		||||
		chall.Error = &core.ProblemDetails{Type: core.MalformedProblem,
 | 
			
		||||
		chall.Error = &probs.ProblemDetails{Type: probs.MalformedProblem,
 | 
			
		||||
			Detail: fmt.Sprintf("Challenge failed sanity check.")}
 | 
			
		||||
		logEvent.Challenge = *chall
 | 
			
		||||
		logEvent.Error = chall.Error.Detail
 | 
			
		||||
| 
						 | 
				
			
			@ -685,7 +687,7 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI
 | 
			
		|||
		} else if !authz.Challenges[challengeIndex].RecordsSane() {
 | 
			
		||||
			chall := &authz.Challenges[challengeIndex]
 | 
			
		||||
			chall.Status = core.StatusInvalid
 | 
			
		||||
			chall.Error = &core.ProblemDetails{Type: core.ServerInternalProblem,
 | 
			
		||||
			chall.Error = &probs.ProblemDetails{Type: probs.ServerInternalProblem,
 | 
			
		||||
				Detail: "Records for validation failed sanity check"}
 | 
			
		||||
			logEvent.Error = chall.Error.Detail
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@ import (
 | 
			
		|||
	"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/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	"github.com/letsencrypt/boulder/mocks"
 | 
			
		||||
| 
						 | 
				
			
			@ -303,7 +304,7 @@ func TestSimpleHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err := va.validateSimpleHTTP(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
 | 
			
		||||
	va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, nil, stats, clock.Default())
 | 
			
		||||
	va.DNSResolver = &mocks.DNSResolver{}
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +320,7 @@ func TestSimpleHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateSimpleHTTP(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Should have found a 404 for the challenge.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
 | 
			
		||||
 | 
			
		||||
	log.Clear()
 | 
			
		||||
| 
						 | 
				
			
			@ -329,7 +330,7 @@ func TestSimpleHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateSimpleHTTP(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Should have found the wrong token value.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
 | 
			
		||||
 | 
			
		||||
	log.Clear()
 | 
			
		||||
| 
						 | 
				
			
			@ -351,12 +352,12 @@ func TestSimpleHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateSimpleHTTP(ipIdentifier, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.MalformedProblem)
 | 
			
		||||
 | 
			
		||||
	invalidChall, err = va.validateSimpleHTTP(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Domain name is invalid.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnknownHostProblem)
 | 
			
		||||
 | 
			
		||||
	chall.Token = "wait-long"
 | 
			
		||||
	started := time.Now()
 | 
			
		||||
| 
						 | 
				
			
			@ -367,7 +368,7 @@ func TestSimpleHttp(t *testing.T) {
 | 
			
		|||
	test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Connection should've timed out")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | 
			
		||||
| 
						 | 
				
			
			@ -483,13 +484,13 @@ func TestDvsni(t *testing.T) {
 | 
			
		|||
	}, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.MalformedProblem)
 | 
			
		||||
 | 
			
		||||
	log.Clear()
 | 
			
		||||
	invalidChall, err = va.validateDvsni(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Domain name was supposed to be invalid.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnknownHostProblem)
 | 
			
		||||
 | 
			
		||||
	// Need to re-sign to get an unknown SNI (from the signature value)
 | 
			
		||||
	chall.Token = core.NewToken()
 | 
			
		||||
| 
						 | 
				
			
			@ -509,7 +510,7 @@ func TestDvsni(t *testing.T) {
 | 
			
		|||
	test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Connection should've timed out")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
 | 
			
		||||
 | 
			
		||||
	// Take down DVSNI validation server and check that validation fails.
 | 
			
		||||
| 
						 | 
				
			
			@ -517,7 +518,7 @@ func TestDvsni(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateDvsni(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDVSNIWithTLSError(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -533,7 +534,7 @@ func TestDVSNIWithTLSError(t *testing.T) {
 | 
			
		|||
	invalidChall, err := va.validateDvsni(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "What cert was used?")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.TLSProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpSrv(t *testing.T, token string) *httptest.Server {
 | 
			
		||||
| 
						 | 
				
			
			@ -681,7 +682,7 @@ func TestHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err := va.validateHTTP01(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
 | 
			
		||||
	va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, nil, stats, clock.Default())
 | 
			
		||||
	va.DNSResolver = &mocks.DNSResolver{}
 | 
			
		||||
| 
						 | 
				
			
			@ -698,7 +699,7 @@ func TestHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateHTTP01(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Should have found a 404 for the challenge.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
 | 
			
		||||
 | 
			
		||||
	log.Clear()
 | 
			
		||||
| 
						 | 
				
			
			@ -708,7 +709,7 @@ func TestHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateHTTP01(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Should have found the wrong token value.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
 | 
			
		||||
 | 
			
		||||
	log.Clear()
 | 
			
		||||
| 
						 | 
				
			
			@ -730,12 +731,12 @@ func TestHttp(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateHTTP01(ipIdentifier, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.MalformedProblem)
 | 
			
		||||
 | 
			
		||||
	invalidChall, err = va.validateHTTP01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Domain name is invalid.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnknownHostProblem)
 | 
			
		||||
 | 
			
		||||
	setChallengeToken(&chall, pathWaitLong)
 | 
			
		||||
	started := time.Now()
 | 
			
		||||
| 
						 | 
				
			
			@ -746,7 +747,7 @@ func TestHttp(t *testing.T) {
 | 
			
		|||
	test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Connection should've timed out")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHTTPRedirectLookup(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -891,13 +892,13 @@ func TestTLSSNI(t *testing.T) {
 | 
			
		|||
	}, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.MalformedProblem)
 | 
			
		||||
 | 
			
		||||
	log.Clear()
 | 
			
		||||
	invalidChall, err = va.validateTLSSNI01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Domain name was supposed to be invalid.")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.UnknownHostProblem)
 | 
			
		||||
 | 
			
		||||
	// Need to create a new authorized keys object to get an unknown SNI (from the signature value)
 | 
			
		||||
	chall.Token = core.NewToken()
 | 
			
		||||
| 
						 | 
				
			
			@ -913,7 +914,7 @@ func TestTLSSNI(t *testing.T) {
 | 
			
		|||
	test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Connection should've timed out")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
 | 
			
		||||
 | 
			
		||||
	// Take down validation server and check that validation fails.
 | 
			
		||||
| 
						 | 
				
			
			@ -921,7 +922,7 @@ func TestTLSSNI(t *testing.T) {
 | 
			
		|||
	invalidChall, err = va.validateTLSSNI01(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func brokenTLSSrv() *httptest.Server {
 | 
			
		||||
| 
						 | 
				
			
			@ -948,7 +949,7 @@ func TestTLSError(t *testing.T) {
 | 
			
		|||
	invalidChall, err := va.validateTLSSNI01(ident, chall)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
 | 
			
		||||
	test.AssertError(t, err, "What cert was used?")
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem)
 | 
			
		||||
	test.AssertEquals(t, invalidChall.Error.Type, probs.TLSProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidateHTTP(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1096,8 +1097,8 @@ func TestCAATimeout(t *testing.T) {
 | 
			
		|||
	va.DNSResolver = &mocks.DNSResolver{}
 | 
			
		||||
	va.IssuerDomain = "letsencrypt.org"
 | 
			
		||||
	err := va.checkCAA(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "caa-timeout.com"}, 101)
 | 
			
		||||
	if err.Type != core.ConnectionProblem {
 | 
			
		||||
		t.Errorf("Expected timeout error type %s, got %s", core.ConnectionProblem, err.Type)
 | 
			
		||||
	if err.Type != probs.ConnectionProblem {
 | 
			
		||||
		t.Errorf("Expected timeout error type %s, got %s", probs.ConnectionProblem, err.Type)
 | 
			
		||||
	}
 | 
			
		||||
	expected := "DNS query timed out"
 | 
			
		||||
	if err.Detail != expected {
 | 
			
		||||
| 
						 | 
				
			
			@ -1173,7 +1174,7 @@ func TestDNSValidationFailure(t *testing.T) {
 | 
			
		|||
	t.Logf("Resulting Authz: %+v", authz)
 | 
			
		||||
	test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
 | 
			
		||||
	test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, core.UnauthorizedProblem)
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, probs.UnauthorizedProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDNSValidationInvalid(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1201,7 +1202,7 @@ func TestDNSValidationInvalid(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
 | 
			
		||||
	test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, core.MalformedProblem)
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, probs.MalformedProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDNSValidationNotSane(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1231,7 +1232,7 @@ func TestDNSValidationNotSane(t *testing.T) {
 | 
			
		|||
	for i := 0; i < len(authz.Challenges); i++ {
 | 
			
		||||
		va.validate(authz, i)
 | 
			
		||||
		test.AssertEquals(t, authz.Challenges[i].Status, core.StatusInvalid)
 | 
			
		||||
		test.AssertEquals(t, authz.Challenges[i].Error.Type, core.MalformedProblem)
 | 
			
		||||
		test.AssertEquals(t, authz.Challenges[i].Error.Type, probs.MalformedProblem)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1258,7 +1259,7 @@ func TestDNSValidationServFail(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
 | 
			
		||||
	test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, probs.ConnectionProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDNSValidationNoServer(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1280,7 +1281,7 @@ func TestDNSValidationNoServer(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
 | 
			
		||||
	test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem)
 | 
			
		||||
	test.AssertEquals(t, authz.Challenges[0].Error.Type, probs.ConnectionProblem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestDNSValidationLive is an integration test, depending on
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ import (
 | 
			
		|||
	jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
 | 
			
		||||
	"github.com/letsencrypt/boulder/core"
 | 
			
		||||
	blog "github.com/letsencrypt/boulder/log"
 | 
			
		||||
	"github.com/letsencrypt/boulder/probs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Paths are the ACME-spec identified URL path-segments for various methods
 | 
			
		||||
| 
						 | 
				
			
			@ -486,12 +487,12 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
 | 
			
		|||
 | 
			
		||||
// Notify the client of an error condition and log it for audit purposes.
 | 
			
		||||
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, msg string, detail error, code int) {
 | 
			
		||||
	problem := core.ProblemDetails{Detail: msg}
 | 
			
		||||
	problem := probs.ProblemDetails{Detail: msg, HTTPStatus: code}
 | 
			
		||||
	switch code {
 | 
			
		||||
	case http.StatusPreconditionFailed:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case http.StatusForbidden:
 | 
			
		||||
		problem.Type = core.UnauthorizedProblem
 | 
			
		||||
		problem.Type = probs.UnauthorizedProblem
 | 
			
		||||
	case http.StatusConflict:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case http.StatusMethodNotAllowed:
 | 
			
		||||
| 
						 | 
				
			
			@ -501,14 +502,15 @@ func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *re
 | 
			
		|||
	case http.StatusBadRequest:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case http.StatusLengthRequired:
 | 
			
		||||
		problem.Type = core.MalformedProblem
 | 
			
		||||
		problem.Type = probs.MalformedProblem
 | 
			
		||||
	case StatusRateLimited:
 | 
			
		||||
		problem.Type = core.RateLimitedProblem
 | 
			
		||||
		problem.Type = probs.RateLimitedProblem
 | 
			
		||||
	case statusBadNonce:
 | 
			
		||||
		problem.Type = core.BadNonceProblem
 | 
			
		||||
		problem.Type = probs.BadNonceProblem
 | 
			
		||||
		problem.HTTPStatus = http.StatusBadRequest
 | 
			
		||||
		code = http.StatusBadRequest
 | 
			
		||||
	default: // Either http.StatusInternalServerError or an unexpected code
 | 
			
		||||
		problem.Type = core.ServerInternalProblem
 | 
			
		||||
		problem.Type = probs.ServerInternalProblem
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Record details to the log event
 | 
			
		||||
| 
						 | 
				
			
			@ -516,7 +518,7 @@ func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *re
 | 
			
		|||
 | 
			
		||||
	// Only audit log internal errors so users cannot purposefully cause
 | 
			
		||||
	// auditable events.
 | 
			
		||||
	if problem.Type == core.ServerInternalProblem {
 | 
			
		||||
	if problem.Type == probs.ServerInternalProblem {
 | 
			
		||||
		// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
 | 
			
		||||
		wfe.log.Audit(fmt.Sprintf("Internal error - %s - %s", msg, detail))
 | 
			
		||||
	} else if statusCodeFromError(detail) != http.StatusInternalServerError {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -302,7 +302,7 @@ func TestHandleFunc(t *testing.T) {
 | 
			
		|||
			test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), sortHeader(strings.Join(addHeadIfGet(c.allowed), ", ")))
 | 
			
		||||
			test.AssertEquals(t,
 | 
			
		||||
				rw.Body.String(),
 | 
			
		||||
				`{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`)
 | 
			
		||||
				`{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`)
 | 
			
		||||
		}
 | 
			
		||||
		nonce := rw.Header().Get("Replay-Nonce")
 | 
			
		||||
		test.AssertNotEquals(t, nonce, lastNonce)
 | 
			
		||||
| 
						 | 
				
			
			@ -313,7 +313,7 @@ func TestHandleFunc(t *testing.T) {
 | 
			
		|||
	// Disallowed method returns error JSON in body
 | 
			
		||||
	runWrappedHandler(&http.Request{Method: "PUT"}, "GET", "POST")
 | 
			
		||||
	test.AssertEquals(t, rw.Header().Get("Content-Type"), "application/problem+json")
 | 
			
		||||
	test.AssertEquals(t, rw.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`)
 | 
			
		||||
	test.AssertEquals(t, rw.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`)
 | 
			
		||||
	test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), "GET, HEAD, POST")
 | 
			
		||||
 | 
			
		||||
	// Disallowed method special case: response to HEAD has got no body
 | 
			
		||||
| 
						 | 
				
			
			@ -559,7 +559,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
	})
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`)
 | 
			
		||||
 | 
			
		||||
	// POST, but no body.
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
| 
						 | 
				
			
			@ -571,14 +571,14 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
	})
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// POST, but body that isn't valid JWS
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
	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"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
| 
						 | 
				
			
			@ -586,7 +586,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
		makePostRequest(signRequest(t, "foo", wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// Valid, signed JWS body, payload is '{}'
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
| 
						 | 
				
			
			@ -595,7 +595,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
			signRequest(t, "{}", wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload does not specify a resource"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload does not specify a resource","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// Valid, signed JWS body, payload is '{"resource":"new-cert"}'
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
| 
						 | 
				
			
			@ -603,7 +603,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
		makePostRequest(signRequest(t, `{"resource":"new-cert"}`, wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Error unmarshaling certificate request"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Error unmarshaling certificate request","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// Valid, signed JWS body, payload has a invalid signature on CSR and no authorizations:
 | 
			
		||||
	// alias b64url="base64 -w0 | sed -e 's,+,-,g' -e 's,/,_,g'"
 | 
			
		||||
| 
						 | 
				
			
			@ -618,7 +618,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
    }`, wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"Error creating new cert :: Invalid signature on CSR"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"Error creating new cert :: Invalid signature on CSR","status":403}`)
 | 
			
		||||
 | 
			
		||||
	// Valid, signed JWS body, payload has a valid CSR but no authorizations:
 | 
			
		||||
	// openssl req -outform der -new -nodes -key wfe/test/178.key -subj /CN=meep.com | b64url
 | 
			
		||||
| 
						 | 
				
			
			@ -631,7 +631,7 @@ func TestIssueCertificate(t *testing.T) {
 | 
			
		|||
		}`, wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"Error creating new cert :: Authorizations for these names not found or expired: meep.com"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"Error creating new cert :: Authorizations for these names not found or expired: meep.com","status":403}`)
 | 
			
		||||
	assertCsrLogged(t, mockLog)
 | 
			
		||||
 | 
			
		||||
	mockLog.Clear()
 | 
			
		||||
| 
						 | 
				
			
			@ -737,7 +737,7 @@ func TestChallenge(t *testing.T) {
 | 
			
		|||
			signRequest(t, `{"resource":"challenge"}`, wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Expired authorization"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Expired authorization","status":404}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBadNonce(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -755,7 +755,7 @@ func TestBadNonce(t *testing.T) {
 | 
			
		|||
	test.AssertNotError(t, err, "Failed to sign body")
 | 
			
		||||
	wfe.NewRegistration(newRequestEvent(), responseWriter,
 | 
			
		||||
		makePostRequest(result.FullSerialize()))
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:badNonce","detail":"Unable to read/verify body :: JWS has no anti-replay nonce"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:badNonce","detail":"Unable to read/verify body :: JWS has no anti-replay nonce","status":400}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewRegistration(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -787,7 +787,7 @@ func TestNewRegistration(t *testing.T) {
 | 
			
		|||
				Method: "GET",
 | 
			
		||||
				URL:    mustParseURL(NewRegPath),
 | 
			
		||||
			},
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`,
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// POST, but no body.
 | 
			
		||||
| 
						 | 
				
			
			@ -799,19 +799,19 @@ func TestNewRegistration(t *testing.T) {
 | 
			
		|||
					"Content-Length": []string{"0"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST"}`,
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// POST, but body that isn't valid JWS
 | 
			
		||||
		{
 | 
			
		||||
			makePostRequestWithPath(NewRegPath, "hi"),
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS"}`,
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
 | 
			
		||||
		{
 | 
			
		||||
			makePostRequestWithPath(NewRegPath, fooBody.FullSerialize()),
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON"}`,
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// Same signed body, but payload modified by one byte, breaking signature.
 | 
			
		||||
| 
						 | 
				
			
			@ -831,11 +831,11 @@ func TestNewRegistration(t *testing.T) {
 | 
			
		|||
				"signature": "RjUQ679fxJgeAJlxqgvDP_sfGZnJ-1RgWF2qmcbnBWljs6h1qp63pLnJOl13u81bP_bCSjaWkelGG8Ymx_X-aQ"
 | 
			
		||||
			}
 | 
			
		||||
		`),
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error"}`,
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			makePostRequestWithPath(NewRegPath, wrongAgreementBody.FullSerialize()),
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Provided agreement URL [https://letsencrypt.org/im-bad] does not match current agreement URL [` + agreementURL + `]"}`,
 | 
			
		||||
			`{"type":"urn:acme:error:malformed","detail":"Provided agreement URL [https://letsencrypt.org/im-bad] does not match current agreement URL [` + agreementURL + `]","status":400}`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, rt := range regErrTests {
 | 
			
		||||
| 
						 | 
				
			
			@ -885,7 +885,7 @@ func TestNewRegistration(t *testing.T) {
 | 
			
		|||
		makePostRequest(result.FullSerialize()))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Registration key is already in use"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Registration key is already in use","status":409}`)
 | 
			
		||||
	test.AssertEquals(
 | 
			
		||||
		t, responseWriter.Header().Get("Location"),
 | 
			
		||||
		"/acme/reg/1")
 | 
			
		||||
| 
						 | 
				
			
			@ -996,7 +996,7 @@ func TestRevokeCertificateWrongKey(t *testing.T) {
 | 
			
		|||
		makePostRequest(result.FullSerialize()))
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Code, 403)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."}`)
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.","status":403}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Valid revocation request for already-revoked cert
 | 
			
		||||
| 
						 | 
				
			
			@ -1037,7 +1037,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
 | 
			
		|||
		makePostRequest(result.FullSerialize()))
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Code, 409)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Certificate already revoked"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Certificate already revoked","status":409}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAuthorization(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1052,7 +1052,7 @@ func TestAuthorization(t *testing.T) {
 | 
			
		|||
		Method: "GET",
 | 
			
		||||
		URL:    mustParseURL(NewAuthzPath),
 | 
			
		||||
	})
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`)
 | 
			
		||||
 | 
			
		||||
	// POST, but no body.
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
| 
						 | 
				
			
			@ -1062,12 +1062,12 @@ func TestAuthorization(t *testing.T) {
 | 
			
		|||
			"Content-Length": []string{"0"},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// POST, but body that isn't valid JWS
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
	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"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
| 
						 | 
				
			
			@ -1075,7 +1075,7 @@ func TestAuthorization(t *testing.T) {
 | 
			
		|||
		makePostRequest(signRequest(t, "foo", wfe.nonceService)))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`)
 | 
			
		||||
 | 
			
		||||
	// Same signed body, but payload modified by one byte, breaking signature.
 | 
			
		||||
	// should fail JWS verification.
 | 
			
		||||
| 
						 | 
				
			
			@ -1096,7 +1096,7 @@ func TestAuthorization(t *testing.T) {
 | 
			
		|||
		`))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}`)
 | 
			
		||||
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
	wfe.NewAuthorization(newRequestEvent(), responseWriter,
 | 
			
		||||
| 
						 | 
				
			
			@ -1124,7 +1124,7 @@ func TestAuthorization(t *testing.T) {
 | 
			
		|||
	})
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Expired authorization"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Expired authorization","status":404}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func contains(s []string, e string) bool {
 | 
			
		||||
| 
						 | 
				
			
			@ -1150,7 +1150,7 @@ func TestRegistration(t *testing.T) {
 | 
			
		|||
	})
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`)
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
 | 
			
		||||
	// Test GET proper entry returns 405
 | 
			
		||||
| 
						 | 
				
			
			@ -1160,14 +1160,14 @@ func TestRegistration(t *testing.T) {
 | 
			
		|||
	})
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Method not allowed"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Method not allowed","status":405}`)
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
 | 
			
		||||
	// Test POST invalid JSON
 | 
			
		||||
	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"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
 | 
			
		||||
	key, err := jose.LoadPrivateKey([]byte(test2KeyPrivatePEM))
 | 
			
		||||
| 
						 | 
				
			
			@ -1185,7 +1185,7 @@ func TestRegistration(t *testing.T) {
 | 
			
		|||
		makePostRequestWithPath("/2", result.FullSerialize()))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"No registration exists matching provided key"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:unauthorized","detail":"No registration exists matching provided key","status":403}`)
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
 | 
			
		||||
	key, err = jose.LoadPrivateKey([]byte(test1KeyPrivatePEM))
 | 
			
		||||
| 
						 | 
				
			
			@ -1204,7 +1204,7 @@ func TestRegistration(t *testing.T) {
 | 
			
		|||
		makePostRequestWithPath("/1", result.FullSerialize()))
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Provided agreement URL [https://letsencrypt.org/im-bad] does not match current agreement URL [`+agreementURL+`]"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Provided agreement URL [https://letsencrypt.org/im-bad] does not match current agreement URL [`+agreementURL+`]","status":400}`)
 | 
			
		||||
	responseWriter.Body.Reset()
 | 
			
		||||
 | 
			
		||||
	// Test POST valid JSON with registration up in the mock (with correct agreement URL)
 | 
			
		||||
| 
						 | 
				
			
			@ -1290,7 +1290,7 @@ func TestGetCertificate(t *testing.T) {
 | 
			
		|||
	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"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found","status":404}`)
 | 
			
		||||
 | 
			
		||||
	reqlogs = mockLog.GetAllMatching(`Terminated request`)
 | 
			
		||||
	test.AssertEquals(t, len(reqlogs), 1)
 | 
			
		||||
| 
						 | 
				
			
			@ -1303,7 +1303,7 @@ func TestGetCertificate(t *testing.T) {
 | 
			
		|||
	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"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found","status":404}`)
 | 
			
		||||
 | 
			
		||||
	// Invalid serial, no cache
 | 
			
		||||
	responseWriter = httptest.NewRecorder()
 | 
			
		||||
| 
						 | 
				
			
			@ -1311,7 +1311,7 @@ func TestGetCertificate(t *testing.T) {
 | 
			
		|||
	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"}`)
 | 
			
		||||
	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found","status":404}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertCsrLogged(t *testing.T, mockLog *mocks.SyslogWriter) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1397,7 +1397,7 @@ func TestBadKeyCSR(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	test.AssertEquals(t,
 | 
			
		||||
		responseWriter.Body.String(),
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Invalid key in certificate request :: Key too small: 512"}`)
 | 
			
		||||
		`{"type":"urn:acme:error:malformed","detail":"Invalid key in certificate request :: Key too small: 512","status":400}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStatusCodeFromError(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue