Merge branch 'master' into test_wrong_signature
This commit is contained in:
commit
1ce57b6f60
|
|
@ -39,7 +39,7 @@ branches:
|
|||
- /^test-.*$/
|
||||
|
||||
before_install:
|
||||
- source test/travis-before-install.sh
|
||||
- travis_retry test/travis-before-install.sh
|
||||
|
||||
# Override default Travis install command to prevent it from adding
|
||||
# Godeps/_workspace to GOPATH. When that happens, it hides failures that should
|
||||
|
|
|
|||
47
README.md
47
README.md
|
|
@ -3,7 +3,6 @@ Boulder - An ACME CA
|
|||
|
||||
This is an initial implementation of an ACME-based CA. The [ACME protocol](https://github.com/letsencrypt/acme-spec/) allows the CA to automatically verify that an applicant for a certificate actually controls an identifier, and allows domain holders to issue and revoke certificates for their domains.
|
||||
|
||||
|
||||
[](https://travis-ci.org/letsencrypt/boulder)
|
||||
[](https://coveralls.io/r/letsencrypt/boulder)
|
||||
|
||||
|
|
@ -54,29 +53,18 @@ or
|
|||
|
||||
(On OS X, using port, you will have to add `CGO_CFLAGS="-I/opt/local/include" CGO_LDFLAGS="-L/opt/local/lib"` to your environment or `go` invocations.)
|
||||
|
||||
Resolve Go-dependencies:
|
||||
Resolve Go-dependencies, set up a database and RabbitMQ:
|
||||
|
||||
> go get bitbucket.org/liamstask/goose/cmd/goose
|
||||
> go get github.com/jsha/listenbuddy
|
||||
> go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files
|
||||
> go get -u github.com/golang/lint/golint
|
||||
> ./test/setup.sh
|
||||
|
||||
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.
|
||||
**Note**: `setup.sh` calls `create_db.sh`, which 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.
|
||||
|
||||
Start each boulder component with test configs (Ctrl-C kills all):
|
||||
|
||||
|
||||
> ./start.py
|
||||
|
||||
|
||||
Run tests:
|
||||
|
||||
> ./test.sh
|
||||
|
|
@ -108,27 +96,21 @@ client <-checks-> VA ---+
|
|||
|
||||
```
|
||||
|
||||
In Boulder, these components are represented by Go interfaces. This allows us to have two operational modes: Consolidated and distributed. In consolidated mode, the objects representing the different components interact directly, through function calls. In distributed mode, each component runs in a separate process (possibly on a separate machine), and sees the other components' methods by way of a messaging layer.
|
||||
|
||||
Internally, the logic of the system is based around two types of objects, authorizations and certificates, mapping directly to the resources of the same name in ACME.
|
||||
Internally, the logic of the system is based around four types of objects: registrations, authorizations, challenges, and certificates, mapping directly to the resources of the same name in ACME.
|
||||
|
||||
Requests from ACME clients result in new objects and changes to objects. The Storage Authority maintains persistent copies of the current set of objects.
|
||||
|
||||
Objects are also passed from one component to another on change events. For example, when a client provides a successful response to a validation challenge, it results in a change to the corresponding validation object. The Validation Authority forward the new validation object to the Storage Authority for storage, and to the Registration Authority for any updates to a related Authorization object.
|
||||
Objects are also passed from one component to another on change events. For example, when a client provides a successful response to a validation challenge, it results in a change to the corresponding validation object. The Validation Authority forwards the new validation object to the Storage Authority for storage, and to the Registration Authority for any updates to a related Authorization object.
|
||||
|
||||
Boulder supports distributed operation using AMQP as a message bus (e.g., via RabbitMQ). For components that you want to be remote, it is necessary to instantiate a "client" and "server" for that component. The client implements the component's Go interface, while the server has the actual logic for the component. More details in `amqp-rpc.go`.
|
||||
Boulder uses AMQP as a message bus. For components that you want to be remote, it is necessary to instantiate a "client" and "server" for that component. The client implements the component's Go interface, while the server has the actual logic for the component. More details in `amqp-rpc.go`.
|
||||
|
||||
The full details of how the various ACME operations happen in Boulder are laid out in [DESIGN.md](https://github.com/letsencrypt/boulder/blob/master/DESIGN.md)
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
All Go dependencies are vendorized under the Godeps directory,
|
||||
both to [make dependency management
|
||||
easier](https://groups.google.com/forum/m/#!topic/golang-dev/nMWoEAG55v8)
|
||||
and to [avoid insecure fallback in go
|
||||
get](https://github.com/golang/go/issues/9637).
|
||||
to [make dependency management easier](https://groups.google.com/forum/m/#!topic/golang-dev/nMWoEAG55v8).
|
||||
|
||||
Local development also requires a RabbitMQ installation and MariaDB
|
||||
10 installation (see above). MariaDB should be run on port 3306 for the
|
||||
|
|
@ -137,10 +119,10 @@ default integration tests.
|
|||
To update the Go dependencies:
|
||||
|
||||
```
|
||||
# Disable insecure fallback by blocking port 80.
|
||||
sudo /sbin/iptables -A OUTPUT -p tcp --dport 80 -j DROP
|
||||
# Fetch godep
|
||||
go get -u https://github.com/tools/godep
|
||||
# Check out the currently vendorized version of each dependency.
|
||||
godep restore
|
||||
# Update to the latest version of a dependency. Alternately you can cd to the
|
||||
# directory under GOPATH and check out a specific revision. Here's an example
|
||||
# using cfssl:
|
||||
|
|
@ -152,11 +134,8 @@ godep update github.com/cloudflare/cfssl/...
|
|||
godep save -r ./...
|
||||
git add Godeps
|
||||
git commit
|
||||
# Assuming you had no other iptables rules, re-enable port 80.
|
||||
sudo iptables -D OUTPUT 1
|
||||
```
|
||||
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -120,6 +122,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)
|
||||
|
|
@ -150,11 +167,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,
|
||||
|
|
@ -197,7 +211,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) {
|
||||
|
|
@ -205,14 +227,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
|
||||
|
|
@ -289,7 +311,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
|
||||
|
|
@ -306,7 +328,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
|
||||
|
|
@ -323,7 +345,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
|
||||
|
|
@ -347,7 +369,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
|
||||
|
|
@ -355,7 +377,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)
|
||||
|
|
@ -365,7 +387,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
|
||||
|
|
@ -381,7 +403,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
|
||||
|
|
@ -398,7 +420,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
|
||||
|
|
@ -437,7 +459,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
|
||||
|
|
@ -451,13 +473,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)
|
||||
|
|
@ -472,7 +494,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)
|
||||
|
||||
|
|
@ -482,7 +504,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)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ func main() {
|
|||
|
||||
go cmd.ProfileCmd("AM", stats)
|
||||
|
||||
server.Start(amqpConf)
|
||||
err = server.Start(amqpConf)
|
||||
cmd.FailOnError(err, "Unable to run Activity Monitor")
|
||||
}
|
||||
|
||||
app.Run()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@ func setupContext(context *cli.Context) (rpc.RegistrationAuthorityClient, *blog.
|
|||
rac, err := rpc.NewRegistrationAuthorityClient(clientName, amqpConf, stats)
|
||||
cmd.FailOnError(err, "Unable to create CA client")
|
||||
|
||||
dbMap, err := sa.NewDbMap(c.Revoker.DBConnect)
|
||||
dbURL, err := c.Revoker.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Couldn't setup database connection")
|
||||
|
||||
sac, err := rpc.NewStorageAuthorityClient(clientName, amqpConf, stats)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -32,12 +74,25 @@ func main() {
|
|||
|
||||
go cmd.DebugServer(c.CA.DebugAddr)
|
||||
|
||||
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
|
||||
dbURL, err := c.PA.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
paDbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Couldn't connect to policy database")
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ func main() {
|
|||
|
||||
go cmd.DebugServer(c.RA.DebugAddr)
|
||||
|
||||
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
|
||||
dbURL, err := c.PA.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
paDbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Couldn't connect to policy database")
|
||||
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
|
||||
cmd.FailOnError(err, "Couldn't create PA")
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ func main() {
|
|||
saConf := c.SA
|
||||
go cmd.DebugServer(saConf.DebugAddr)
|
||||
|
||||
dbMap, err := sa.NewDbMap(saConf.DBConnect)
|
||||
dbURL, err := saConf.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Couldn't connect to SA database")
|
||||
|
||||
sai, err := sa.NewSQLStorageAuthority(dbMap, clock.Default())
|
||||
|
|
|
|||
|
|
@ -239,10 +239,14 @@ func main() {
|
|||
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
|
||||
c.PA.SetDefaultChallengesIfEmpty()
|
||||
|
||||
saDbMap, err := sa.NewDbMap(c.CertChecker.DBConnect)
|
||||
saDbURL, err := c.CertChecker.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
saDbMap, err := sa.NewDbMap(saDbURL)
|
||||
cmd.FailOnError(err, "Could not connect to database")
|
||||
|
||||
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
|
||||
paDbURL, err := c.PA.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
paDbMap, err := sa.NewDbMap(paDbURL)
|
||||
cmd.FailOnError(err, "Could not connect to policy database")
|
||||
|
||||
checker := newChecker(saDbMap, paDbMap, clock.Default(), c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
|
||||
|
|
|
|||
|
|
@ -9,9 +9,12 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"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/publisher"
|
||||
"github.com/letsencrypt/boulder/va"
|
||||
|
|
@ -64,8 +67,7 @@ type Config struct {
|
|||
|
||||
SA struct {
|
||||
ServiceConfig
|
||||
|
||||
DBConnect string
|
||||
DBConfig
|
||||
|
||||
MaxConcurrentRPCServerRequests int64
|
||||
}
|
||||
|
|
@ -91,7 +93,7 @@ type Config struct {
|
|||
Syslog SyslogConfig
|
||||
|
||||
Revoker struct {
|
||||
DBConnect string
|
||||
DBConfig
|
||||
// The revoker isn't a long running service, so doesn't get a full
|
||||
// ServiceConfig, just an AMQPConfig.
|
||||
AMQP *AMQPConfig
|
||||
|
|
@ -99,14 +101,13 @@ type Config struct {
|
|||
|
||||
Mailer struct {
|
||||
ServiceConfig
|
||||
DBConfig
|
||||
|
||||
Server string
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
|
||||
DBConnect string
|
||||
|
||||
CertLimit int
|
||||
NagTimes []string
|
||||
// How much earlier (than configured nag intervals) to
|
||||
|
|
@ -119,10 +120,12 @@ type Config struct {
|
|||
|
||||
OCSPResponder struct {
|
||||
ServiceConfig
|
||||
DBConfig
|
||||
|
||||
// Source indicates the source of pre-signed OCSP responses to be used. It
|
||||
// can be a DBConnect string or a file URL. The file URL style is used
|
||||
// when responding from a static file for intermediates and roots.
|
||||
// If DBConfig has non-empty fields, it takes precedence over this.
|
||||
Source string
|
||||
|
||||
Path string
|
||||
|
|
@ -164,9 +167,10 @@ type Config struct {
|
|||
}
|
||||
|
||||
CertChecker struct {
|
||||
DBConfig
|
||||
|
||||
Workers int
|
||||
ReportDirectoryPath string
|
||||
DBConnect string
|
||||
}
|
||||
|
||||
SubscriberAgreementURL string
|
||||
|
|
@ -180,9 +184,32 @@ type ServiceConfig struct {
|
|||
AMQP *AMQPConfig
|
||||
}
|
||||
|
||||
// DBConfig defines how to connect to a database. The connect string may be
|
||||
// stored in a file separate from the config, because it can contain a password,
|
||||
// which we want to keep out of configs.
|
||||
type DBConfig struct {
|
||||
DBConnect string
|
||||
// A file containing a connect URL for the DB.
|
||||
DBConnectFile string
|
||||
}
|
||||
|
||||
// URL returns the DBConnect URL represented by this DBConfig object, either
|
||||
// loading it from disk or returning a default value.
|
||||
func (d *DBConfig) URL() (string, error) {
|
||||
if d.DBConnectFile != "" {
|
||||
url, err := ioutil.ReadFile(d.DBConnectFile)
|
||||
return string(url), err
|
||||
}
|
||||
return d.DBConnect, nil
|
||||
}
|
||||
|
||||
// AMQPConfig describes how to connect to AMQP, and how to speak to each of the
|
||||
// RPC services we offer via AMQP.
|
||||
type AMQPConfig struct {
|
||||
// A file from which the AMQP Server URL will be read. This allows secret
|
||||
// values (like the password) to be stored separately from the main config.
|
||||
ServerURLFile string
|
||||
// AMQP server URL, including username and password.
|
||||
Server string
|
||||
Insecure bool
|
||||
RA *RPCServerConfig
|
||||
|
|
@ -200,15 +227,28 @@ type AMQPConfig struct {
|
|||
}
|
||||
}
|
||||
|
||||
// ServerURL returns the appropriate server URL for this object, which may
|
||||
// involve reading from a file.
|
||||
func (a *AMQPConfig) ServerURL() (string, error) {
|
||||
if a.ServerURLFile != "" {
|
||||
url, err := ioutil.ReadFile(a.ServerURLFile)
|
||||
return strings.TrimRight(string(url), "\n"), err
|
||||
}
|
||||
if a.Server == "" {
|
||||
return "", fmt.Errorf("Missing AMQP server URL")
|
||||
}
|
||||
return a.Server, nil
|
||||
}
|
||||
|
||||
// CAConfig structs have configuration information for the certificate
|
||||
// authority, including database parameters as well as controls for
|
||||
// issued certificates.
|
||||
type CAConfig struct {
|
||||
ServiceConfig
|
||||
DBConfig
|
||||
|
||||
Profile string
|
||||
TestMode bool
|
||||
DBConnect string
|
||||
SerialPrefix int
|
||||
Key KeyConfig
|
||||
// LifespanOCSP is how long OCSP responses are valid for; It should be longer
|
||||
|
|
@ -230,7 +270,7 @@ type CAConfig struct {
|
|||
// database, what policies it should enforce, and what challenges
|
||||
// it should offer.
|
||||
type PAConfig struct {
|
||||
DBConnect string
|
||||
DBConfig
|
||||
EnforcePolicyWhitelist bool
|
||||
Challenges map[string]bool
|
||||
}
|
||||
|
|
@ -263,16 +303,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.
|
||||
|
|
@ -293,7 +327,7 @@ type RPCServerConfig struct {
|
|||
// for the OCSP (and SCT) updater
|
||||
type OCSPUpdaterConfig struct {
|
||||
ServiceConfig
|
||||
DBConnect string
|
||||
DBConfig
|
||||
|
||||
NewCertificateWindow ConfigDuration
|
||||
OldOCSPWindow ConfigDuration
|
||||
|
|
|
|||
|
|
@ -231,10 +231,12 @@ func main() {
|
|||
go cmd.DebugServer(c.Mailer.DebugAddr)
|
||||
|
||||
// Configure DB
|
||||
dbMap, err := sa.NewDbMap(c.Mailer.DBConnect)
|
||||
dbURL, err := c.Mailer.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Could not connect to database")
|
||||
|
||||
amqpConf := c.SA.AMQP
|
||||
amqpConf := c.Mailer.AMQP
|
||||
sac, err := rpc.NewStorageAuthorityClient(clientName, amqpConf, stats)
|
||||
cmd.FailOnError(err, "Failed to create SA client")
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,9 @@ func main() {
|
|||
|
||||
app.Action = func(c cmd.Config, stats statsd.Statter, auditlogger *blog.AuditLogger) {
|
||||
// Configure DB
|
||||
dbMap, err := sa.NewDbMap(c.PA.DBConnect)
|
||||
dbURL, err := c.PA.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Could not connect to database")
|
||||
|
||||
dbMap.AddTableWithName(core.ExternalCert{}, "externalCerts").SetKeys(false, "SHA1")
|
||||
|
|
|
|||
|
|
@ -141,7 +141,14 @@ func main() {
|
|||
|
||||
config := c.OCSPResponder
|
||||
var source cfocsp.Source
|
||||
url, err := url.Parse(config.Source)
|
||||
|
||||
// DBConfig takes precedence over Source, if present.
|
||||
dbConnect, err := config.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Reading DB config")
|
||||
if dbConnect == "" {
|
||||
dbConnect = config.Source
|
||||
}
|
||||
url, err := url.Parse(dbConnect)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Source was not a URL: %s", config.Source))
|
||||
|
||||
if url.Scheme == "mysql+tcp" {
|
||||
|
|
|
|||
|
|
@ -560,7 +560,9 @@ func main() {
|
|||
go cmd.ProfileCmd("OCSP-Updater", stats)
|
||||
|
||||
// Configure DB
|
||||
dbMap, err := sa.NewDbMap(conf.DBConnect)
|
||||
dbURL, err := conf.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Could not connect to database")
|
||||
|
||||
cac, pubc, sac := setupClients(conf, stats)
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@ func setupFromContext(context *cli.Context) (*policy.PolicyAuthorityDatabaseImpl
|
|||
err = json.Unmarshal(configJSON, &c)
|
||||
cmd.FailOnError(err, "Couldn't unmarshal configuration object")
|
||||
|
||||
dbMap, err := sa.NewDbMap(c.PA.DBConnect)
|
||||
dbURL, err := c.PA.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL)
|
||||
cmd.FailOnError(err, "Failed to create DB map")
|
||||
|
||||
padb, err := policy.NewPolicyAuthorityDatabaseImpl(dbMap)
|
||||
|
|
|
|||
|
|
@ -25,14 +25,15 @@ func init() {
|
|||
|
||||
// Constants for AMQP
|
||||
const (
|
||||
monitorQueueName = "Monitor"
|
||||
amqpExchange = "boulder"
|
||||
amqpExchangeType = "topic"
|
||||
amqpInternal = false
|
||||
amqpDurable = false
|
||||
amqpDeleteUnused = false
|
||||
amqpExclusive = false
|
||||
amqpNoWait = false
|
||||
monitorQueueName = "Monitor"
|
||||
amqpExchange = "boulder"
|
||||
amqpExchangeType = "topic"
|
||||
amqpInternal = false
|
||||
amqpExchangeDurable = true
|
||||
amqpQueueDurable = false
|
||||
amqpDeleteUnused = false
|
||||
amqpExclusive = false
|
||||
amqpNoWait = false
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -45,7 +46,7 @@ func main() {
|
|||
err = ch.ExchangeDeclare(
|
||||
amqpExchange,
|
||||
amqpExchangeType,
|
||||
amqpDurable,
|
||||
amqpExchangeDurable,
|
||||
amqpDeleteUnused,
|
||||
amqpInternal,
|
||||
amqpNoWait,
|
||||
|
|
@ -54,7 +55,7 @@ func main() {
|
|||
|
||||
_, err = ch.QueueDeclare(
|
||||
monitorQueueName,
|
||||
amqpDurable,
|
||||
amqpQueueDurable,
|
||||
amqpDeleteUnused,
|
||||
amqpExclusive,
|
||||
amqpNoWait,
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,14 +297,19 @@ func makeAmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
|
|||
|
||||
log := blog.GetAuditLogger()
|
||||
|
||||
serverURL, err := conf.ServerURL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.Insecure == true {
|
||||
// If the Insecure flag is true, then just go ahead and connect
|
||||
conn, err = amqp.Dial(conf.Server)
|
||||
conn, err = amqp.Dial(serverURL)
|
||||
} else {
|
||||
// The insecure flag is false or not set, so we need to load up the options
|
||||
log.Info("AMQPS: Loading TLS Options.")
|
||||
|
||||
if strings.HasPrefix(conf.Server, "amqps") == false {
|
||||
if strings.HasPrefix(serverURL, "amqps") == false {
|
||||
err = fmt.Errorf("AMQPS: Not using an AMQPS URL. To use AMQP instead of AMQPS, set insecure=true")
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -348,7 +353,7 @@ func makeAmqpChannel(conf *cmd.AMQPConfig) (*amqp.Channel, error) {
|
|||
log.Info("AMQPS: Configured CA certificate for AMQPS.")
|
||||
}
|
||||
|
||||
conn, err = amqp.DialTLS(conf.Server, cfg)
|
||||
conn, err = amqp.DialTLS(serverURL, cfg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
7
test.sh
7
test.sh
|
|
@ -217,13 +217,6 @@ if [[ "$RUN" =~ "migrations" ]] ; then
|
|||
end_context #"migrations"
|
||||
fi
|
||||
|
||||
#
|
||||
# Prepare the database for unittests and integration tests
|
||||
#
|
||||
if [[ "${TRAVIS}" == "true" ]] ; then
|
||||
./test/create_db.sh || die "unable to create the boulder database with test/create_db.sh"
|
||||
fi
|
||||
|
||||
#
|
||||
# Unit Tests.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
"maxConcurrentRPCServerRequests": 16,
|
||||
"hsmFaultTimeout": "300s",
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"serviceQueue": "CA.server",
|
||||
"SA": {
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
"maxContactsPerRegistration": 100,
|
||||
"debugAddr": "localhost:8002",
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"serviceQueue": "RA.server",
|
||||
"VA": {
|
||||
|
|
@ -147,11 +147,11 @@
|
|||
},
|
||||
|
||||
"sa": {
|
||||
"dbConnect": "mysql+tcp://sa@localhost:3306/boulder_sa_integration",
|
||||
"dbConnectFile": "test/secrets/sa_dburl",
|
||||
"maxConcurrentRPCServerRequests": 16,
|
||||
"debugAddr": "localhost:8003",
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"serviceQueue": "SA.server"
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
},
|
||||
"maxConcurrentRPCServerRequests": 16,
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"serviceQueue": "VA.server",
|
||||
"RA": {
|
||||
|
|
@ -182,9 +182,9 @@
|
|||
},
|
||||
|
||||
"revoker": {
|
||||
"dbConnect": "mysql+tcp://revoker@localhost:3306/boulder_sa_integration",
|
||||
"dbConnectFile": "test/secrets/revoker_dburl",
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"RA": {
|
||||
"server": "RA.server",
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
},
|
||||
|
||||
"ocspUpdater": {
|
||||
"dbConnect": "mysql+tcp://ocsp_update@localhost:3306/boulder_sa_integration",
|
||||
"dbConnectFile": "test/secrets/ocsp_updater_dburl",
|
||||
"newCertificateWindow": "1s",
|
||||
"oldOCSPWindow": "2s",
|
||||
"missingSCTWindow": "1m",
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
"signFailureBackoffMax": "30m",
|
||||
"debugAddr": "localhost:8006",
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"SA": {
|
||||
"server": "SA.server",
|
||||
|
|
@ -244,7 +244,7 @@
|
|||
"debugAddr": "localhost:8007",
|
||||
"amqp": {
|
||||
"serviceQueue": "Monitor",
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true
|
||||
}
|
||||
},
|
||||
|
|
@ -254,19 +254,27 @@
|
|||
"port": "25",
|
||||
"username": "cert-master@example.com",
|
||||
"password": "password",
|
||||
"dbConnect": "mysql+tcp://mailer@localhost:3306/boulder_sa_integration",
|
||||
"dbConnectFile": "test/secrets/mailer_dburl",
|
||||
"messageLimit": 0,
|
||||
"nagTimes": ["24h", "72h", "168h", "336h"],
|
||||
"nagCheckInterval": "24h",
|
||||
"emailTemplate": "test/example-expiration-template",
|
||||
"debugAddr": "localhost:8008"
|
||||
"debugAddr": "localhost:8008",
|
||||
"amqp": {
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"SA": {
|
||||
"server": "SA.server",
|
||||
"rpcTimeout": "15s"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"publisher": {
|
||||
"maxConcurrentRPCServerRequests": 16,
|
||||
"debugAddr": "localhost:8009",
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5673",
|
||||
"serverURLFile": "test/secrets/amqp_url",
|
||||
"insecure": true,
|
||||
"serviceQueue": "Publisher.server",
|
||||
"SA": {
|
||||
|
|
@ -296,7 +304,7 @@
|
|||
},
|
||||
|
||||
"certChecker": {
|
||||
"dbConnect": "mysql+tcp://cert_checker@localhost:3306/boulder_sa_integration"
|
||||
"dbConnectFile": "test/secrets/cert_checker_dburl"
|
||||
},
|
||||
|
||||
"subscriberAgreementURL": "http://127.0.0.1:4001/terms/v1"
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
amqp://guest:guest@localhost:5673
|
||||
|
|
@ -0,0 +1 @@
|
|||
mysql+tcp://cert_checker@localhost:3306/boulder_sa_integration
|
||||
|
|
@ -0,0 +1 @@
|
|||
mysql+tcp://mailer@localhost:3306/boulder_sa_integration
|
||||
|
|
@ -0,0 +1 @@
|
|||
mysql+tcp://ocsp_update@localhost:3306/boulder_sa_integration
|
||||
|
|
@ -0,0 +1 @@
|
|||
mysql+tcp://policy@localhost:3306/boulder_policy_integration
|
||||
|
|
@ -0,0 +1 @@
|
|||
mysql+tcp://revoker@localhost:3306/boulder_sa_integration
|
||||
|
|
@ -0,0 +1 @@
|
|||
mysql+tcp://sa@localhost:3306/boulder_sa_integration
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Fetch dependencies of Boulderthat are necessary for development or testing,
|
||||
# and configure database and RabbitMQ.
|
||||
#
|
||||
|
||||
go get \
|
||||
golang.org/x/tools/cmd/vet \
|
||||
golang.org/x/tools/cmd/cover \
|
||||
github.com/golang/lint/golint \
|
||||
github.com/mattn/goveralls \
|
||||
github.com/modocache/gover \
|
||||
github.com/jcjones/github-pr-status \
|
||||
github.com/jsha/listenbuddy &
|
||||
|
||||
(wget https://github.com/jsha/boulder-tools/raw/master/goose.gz &&
|
||||
mkdir -p $GOPATH/bin &&
|
||||
zcat goose.gz > $GOPATH/bin/goose &&
|
||||
chmod +x $GOPATH/bin/goose &&
|
||||
./test/create_db.sh) &
|
||||
|
||||
# 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
|
||||
|
|
@ -1,50 +1,28 @@
|
|||
#!/bin/bash
|
||||
set -o xtrace
|
||||
|
||||
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=""
|
||||
# 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_retry go get \
|
||||
golang.org/x/tools/cmd/vet \
|
||||
golang.org/x/tools/cmd/cover \
|
||||
github.com/golang/lint/golint \
|
||||
github.com/mattn/goveralls \
|
||||
github.com/modocache/gover \
|
||||
github.com/jcjones/github-pr-status \
|
||||
github.com/jsha/listenbuddy &
|
||||
# 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
|
||||
|
||||
(wget https://github.com/jsha/boulder-tools/raw/master/goose.gz &&
|
||||
mkdir -p $GOPATH/bin &&
|
||||
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
|
||||
./test/setup.sh
|
||||
|
||||
set +o xtrace
|
||||
|
|
|
|||
|
|
@ -387,8 +387,9 @@ func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIden
|
|||
}
|
||||
|
||||
challenge.Error = &probs.ProblemDetails{
|
||||
Type: probs.UnauthorizedProblem,
|
||||
Detail: "Correct zName not found for TLS SNI challenge",
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue