Merge branch 'master' into google-ct

This commit is contained in:
Roland Shoemaker 2015-11-30 12:05:17 -08:00
commit 7e093c3ed4
26 changed files with 519 additions and 483 deletions

View File

@ -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.

View File

@ -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())

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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")

View File

@ -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

View File

@ -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)

30
probs/probs.go Normal file
View File

@ -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)
}

14
probs/probs_test.go Normal file
View File

@ -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")
}

View File

@ -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{}

View File

@ -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 {

View File

@ -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"),
},
{

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -63,6 +63,7 @@ def start(race_detection):
t.daemon = True
t.start()
progs = [
'activity-monitor',
'boulder-wfe',
'boulder-ra',
'boulder-sa',

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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) {