Merge master
This commit is contained in:
commit
c552984784
|
|
@ -43,6 +43,7 @@ before_install:
|
|||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/modocache/gover
|
||||
- go get github.com/jcjones/github-pr-status
|
||||
- go get bitbucket.org/liamstask/goose/cmd/goose
|
||||
|
||||
# Boulder consists of multiple Go packages, which
|
||||
# refer to each other by their absolute GitHub path,
|
||||
|
|
|
|||
33
README.md
33
README.md
|
|
@ -45,34 +45,41 @@ To run a single module, specifying the AMQP server, you might use something more
|
|||
Quickstart
|
||||
----------
|
||||
|
||||
Install RabbitMQ from https://rabbitmq.com/download.html. It's required to run
|
||||
tests.
|
||||
|
||||
Install libtool-ltdl dev libraries, which are required for Boulder's PKCS11
|
||||
support.
|
||||
Boulder requires an installation of RabbitMQ, libtool-ltdl, and
|
||||
MariaDB 10 to work correctly. On Ubuntu and CentOS, you may have to
|
||||
install RabbitMQ from https://rabbitmq.com/download.html to get a
|
||||
recent version.
|
||||
|
||||
Ubuntu:
|
||||
`sudo apt-get install libltdl3-dev`
|
||||
|
||||
sudo apt-get install libltdl3-dev mariadb-server rabbitmq-server
|
||||
|
||||
CentOS:
|
||||
`sudo yum install libtool-ltdl-devel`
|
||||
|
||||
sudo yum install libtool-ltdl-devel MariaDB-server MariaDB-client rabbitmq-server
|
||||
|
||||
OS X:
|
||||
`sudo port install libtool` or `brew install libtool`
|
||||
|
||||
brew install libtool mariadb rabbitmq
|
||||
|
||||
or
|
||||
|
||||
sudo port install libtool mariadb-server rabbitmq-server
|
||||
|
||||
(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.)
|
||||
|
||||
```
|
||||
> go get github.com/letsencrypt/boulder # Ignore errors about no buildable files
|
||||
> go get github.com/letsencrypt/boulder/ # Ignore errors about no buildable files
|
||||
> cd $GOPATH/src/github.com/letsencrypt/boulder
|
||||
# This starts each Boulder component with test configs. Ctrl-C kills all.
|
||||
> python ./start.py
|
||||
> cd test/js
|
||||
> npm install
|
||||
> nodejs test.js
|
||||
> ./test.sh
|
||||
```
|
||||
|
||||
The databases that boulder requires to operate in development and
|
||||
testing can be created using test/create\_db.sh. It uses the root
|
||||
MariaDB user, so if you have disabled that account you may have to
|
||||
adjust the file or recreate the commands.
|
||||
|
||||
You can also check out the official client from
|
||||
https://github.com/letsencrypt/lets-encrypt-preview/ and follow the setup
|
||||
instructions there.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
development:
|
||||
driver: mysql
|
||||
open: boulder@tcp(localhost:3306)/boulder_ca_development
|
||||
test:
|
||||
driver: mysql
|
||||
open: boulder@tcp(localhost:3306)/boulder_ca_test
|
||||
integration:
|
||||
driver: mysql
|
||||
open: boulder@tcp(localhost:3306)/boulder_ca_integration
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
CREATE TABLE `serialNumber` (
|
||||
`id` int(11) DEFAULT NULL,
|
||||
`number` int(11) DEFAULT NULL,
|
||||
`lastUpdated` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO `serialNumber`
|
||||
(`id`,
|
||||
`number`,
|
||||
`lastUpdated`)
|
||||
VALUES (1,
|
||||
1,
|
||||
now()
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
DROP TABLE `serialNumber`
|
||||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
||||
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
|
|
@ -31,7 +30,7 @@ type SerialNumber struct {
|
|||
|
||||
// NewCertificateAuthorityDatabaseImpl constructs a Database for the
|
||||
// Certificate Authority.
|
||||
func NewCertificateAuthorityDatabaseImpl(dbMap *gorp.DbMap) (cadb core.CertificateAuthorityDatabase, err error) {
|
||||
func NewCertificateAuthorityDatabaseImpl(dbMap *gorp.DbMap) (cadb *CertificateAuthorityDatabaseImpl, err error) {
|
||||
logger := blog.GetAuditLogger()
|
||||
|
||||
dbMap.AddTableWithName(SerialNumber{}, "serialNumber").SetKeys(true, "ID")
|
||||
|
|
@ -43,21 +42,6 @@ func NewCertificateAuthorityDatabaseImpl(dbMap *gorp.DbMap) (cadb core.Certifica
|
|||
return cadb, nil
|
||||
}
|
||||
|
||||
// CreateTablesIfNotExists builds the database tables and inserts the initial
|
||||
// state, if the tables do not already exist. It is not an error for the tables
|
||||
// to already exist.
|
||||
func (cadb *CertificateAuthorityDatabaseImpl) CreateTablesIfNotExists() (err error) {
|
||||
// Create serial number table
|
||||
err = cadb.dbMap.CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the serial number
|
||||
err = cadb.dbMap.Insert(&SerialNumber{ID: 1, Number: 1, LastUpdated: time.Now()})
|
||||
return
|
||||
}
|
||||
|
||||
// Begin starts a transaction at the GORP wrapper.
|
||||
func (cadb *CertificateAuthorityDatabaseImpl) Begin() (*gorp.Transaction, error) {
|
||||
return cadb.dbMap.Begin()
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ package ca
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
|
@ -46,8 +46,8 @@ func TestGetSetSequenceNumber(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Could not commit")
|
||||
}
|
||||
|
||||
func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
|
||||
dbMap, err := sa.NewDbMap(dbConnStr)
|
||||
func caDBImpl(t *testing.T) (*CertificateAuthorityDatabaseImpl, func()) {
|
||||
dbMap, err := sa.NewDbMap(caDBConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not construct dbMap: %s", err)
|
||||
}
|
||||
|
|
@ -57,33 +57,16 @@ func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
|
|||
t.Fatalf("Could not construct CA DB: %s", err)
|
||||
}
|
||||
|
||||
// We intentionally call CreateTablesIfNotExists twice before
|
||||
// returning because of the weird insert inside it. The
|
||||
// CADatabaseImpl code expects the existence of a single row in
|
||||
// its serialIds table or else it errors. CreateTablesIfNotExists
|
||||
// currently inserts that row and TruncateTables will remove
|
||||
// it. But we need to make sure the tables exist before
|
||||
// TruncateTables can be called to reset the table. So, two calls
|
||||
// to CreateTablesIfNotExists.
|
||||
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
|
||||
err = cadb.CreateTablesIfNotExists()
|
||||
// This row is required to exist for caDBImpl to work correctly. We
|
||||
// can no longer use dbMap.Insert(&SerialNumber{...}) for this
|
||||
// because gorp will ignore the ID and insert a new row at a new
|
||||
// autoincrement id.
|
||||
// TODO(jmhodges): gen ids flickr-style, no row needed a head of time
|
||||
_, err = dbMap.Db.Exec("insert into serialNumber (id, number, lastUpdated) VALUES (?, ?, ?)", 1, 1, time.Now())
|
||||
if err != nil {
|
||||
t.Fatalf("Could not construct tables: %s", err)
|
||||
t.Fatalf("unable to create the serial number row: %s", err)
|
||||
}
|
||||
err = dbMap.TruncateTables()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not truncate tables: %s", err)
|
||||
}
|
||||
err = cadb.CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not construct tables: %s", err)
|
||||
}
|
||||
cleanUp := func() {
|
||||
if err := dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Could not truncate tables after the test: %s", err)
|
||||
}
|
||||
dbMap.Db.Close()
|
||||
}
|
||||
|
||||
return cadb, cleanUp
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
ocspConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp/config"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
"github.com/letsencrypt/boulder/sa/satest"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
|
|
@ -337,12 +338,22 @@ const profileName = "ee"
|
|||
const caKeyFile = "../test/test-ca.key"
|
||||
const caCertFile = "../test/test-ca.pem"
|
||||
|
||||
// TODO(jmhodges): change this to boulder_ca_test database
|
||||
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
const (
|
||||
caDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_ca_test"
|
||||
saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthority, Config, func()) {
|
||||
type testCtx struct {
|
||||
caDB core.CertificateAuthorityDatabase
|
||||
sa core.StorageAuthority
|
||||
caConfig Config
|
||||
reg core.Registration
|
||||
cleanUp func()
|
||||
}
|
||||
|
||||
func setup(t *testing.T) *testCtx {
|
||||
// Create an SA
|
||||
dbMap, err := sa.NewDbMap(dbConnStr)
|
||||
dbMap, err := sa.NewDbMap(saDBConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create dbMap: %s", err)
|
||||
}
|
||||
|
|
@ -350,22 +361,16 @@ func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthori
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create SA: %s", err)
|
||||
}
|
||||
if err = ssa.CreateTablesIfNotExists(); err != nil {
|
||||
t.Fatalf("Failed to create tables: %s", err)
|
||||
}
|
||||
if err = dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Failed to truncate tables: %s", err)
|
||||
}
|
||||
|
||||
saDBCleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
cadb, caDBCleanUp := caDBImpl(t)
|
||||
cleanUp := func() {
|
||||
if err = dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Failed to truncate tables after the test: %s", err)
|
||||
}
|
||||
dbMap.Db.Close()
|
||||
saDBCleanUp()
|
||||
caDBCleanUp()
|
||||
}
|
||||
|
||||
// TODO(jmhodges): use of this pkg here is a bug caused by using a real SA
|
||||
reg := satest.CreateWorkingRegistration(t, ssa)
|
||||
|
||||
// Create a CA
|
||||
caConfig := Config{
|
||||
Profile: profileName,
|
||||
|
|
@ -412,32 +417,32 @@ func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthori
|
|||
},
|
||||
},
|
||||
}
|
||||
return cadb, ssa, caConfig, cleanUp
|
||||
return &testCtx{cadb, ssa, caConfig, reg, cleanUp}
|
||||
}
|
||||
|
||||
func TestFailNoSerial(t *testing.T) {
|
||||
cadb, _, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
|
||||
caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx.caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
|
||||
}
|
||||
|
||||
func TestRevoke(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
csrDER, _ := hex.DecodeString(CNandSANCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
certObj, err := ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
certObj, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.AssertNotError(t, err, "Failed to sign certificate")
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -448,7 +453,7 @@ func TestRevoke(t *testing.T) {
|
|||
err = ca.RevokeCertificate(serialString, 0)
|
||||
test.AssertNotError(t, err, "Revocation failed")
|
||||
|
||||
status, err := storageAuthority.GetCertificateStatus(serialString)
|
||||
status, err := ctx.sa.GetCertificateStatus(serialString)
|
||||
test.AssertNotError(t, err, "Failed to get cert status")
|
||||
|
||||
test.AssertEquals(t, status.Status, core.OCSPStatusRevoked)
|
||||
|
|
@ -458,11 +463,11 @@ func TestRevoke(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIssueCertificate(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
/*
|
||||
|
|
@ -480,7 +485,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
|
||||
// Sign CSR
|
||||
issuedCert, err := ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
issuedCert, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.AssertNotError(t, err, "Failed to sign certificate")
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
@ -521,12 +526,12 @@ func TestIssueCertificate(t *testing.T) {
|
|||
|
||||
// Verify that the cert got stored in the DB
|
||||
serialString := core.SerialToString(cert.SerialNumber)
|
||||
storedCert, err := storageAuthority.GetCertificate(serialString)
|
||||
storedCert, err := ctx.sa.GetCertificate(serialString)
|
||||
test.AssertNotError(t, err,
|
||||
fmt.Sprintf("Certificate %s not found in database", serialString))
|
||||
test.Assert(t, bytes.Equal(issuedCert.DER, storedCert.DER), "Retrieved cert not equal to issued cert.")
|
||||
|
||||
certStatus, err := storageAuthority.GetCertificateStatus(serialString)
|
||||
certStatus, err := ctx.sa.GetCertificateStatus(serialString)
|
||||
test.AssertNotError(t, err,
|
||||
fmt.Sprintf("Error fetching status for certificate %s", serialString))
|
||||
test.Assert(t, certStatus.Status == core.OCSPStatusGood, "Certificate status was not good")
|
||||
|
|
@ -535,48 +540,48 @@ func TestIssueCertificate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRejectNoName(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
// Test that the CA rejects CSRs with no names
|
||||
csrDER, _ := hex.DecodeString(NoNameCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
if err == nil {
|
||||
t.Errorf("CA improperly agreed to create a certificate with no name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectTooManyNames(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
|
||||
// Test that the CA rejects a CSR with too many names
|
||||
csrDER, _ := hex.DecodeString(TooManyNameCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.Assert(t, err != nil, "Issued certificate with too many names")
|
||||
}
|
||||
|
||||
func TestDeduplication(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
// Test that the CA collapses duplicate names
|
||||
csrDER, _ := hex.DecodeString(DupeNameCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
cert, err := ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
cert, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with duplicate names")
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -596,17 +601,17 @@ func TestDeduplication(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRejectValidityTooLong(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csrDER, _ := hex.DecodeString(NoCNCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarPast)
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarPast)
|
||||
test.Assert(t, err == nil, "Can issue a certificate that expires after the underlying authorization.")
|
||||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
|
|
@ -618,29 +623,29 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShortKey(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ca.SA = storageAuthority
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csrDER, _ := hex.DecodeString(ShortKeyCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.Assert(t, err != nil, "Issued a certificate with too short a key.")
|
||||
}
|
||||
|
||||
func TestRejectBadAlgorithm(t *testing.T) {
|
||||
cadb, storageAuthority, caConfig, cleanUp := setup(t)
|
||||
defer cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(cadb, caConfig, caCertFile)
|
||||
ca.SA = storageAuthority
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csrDER, _ := hex.DecodeString(BadAlgorithmCSRhex)
|
||||
csr, _ := x509.ParseCertificateRequest(csrDER)
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.Assert(t, err != nil, "Issued a certificate based on a CSR with a weak algorithm.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,11 +37,6 @@ func main() {
|
|||
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(dbMap)
|
||||
cmd.FailOnError(err, "Failed to create CA database")
|
||||
|
||||
if c.SQL.CreateTables {
|
||||
err = cadb.CreateTablesIfNotExists()
|
||||
cmd.FailOnError(err, "Failed to create CA tables")
|
||||
}
|
||||
|
||||
cai, err := ca.NewCertificateAuthorityImpl(cadb, c.CA, c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, "Failed to create CA impl")
|
||||
cai.MaxKeySize = c.Common.MaxKeySize
|
||||
|
|
|
|||
|
|
@ -38,11 +38,6 @@ func main() {
|
|||
cmd.FailOnError(err, "Failed to create SA impl")
|
||||
sai.SetSQLDebug(c.SQL.SQLDebug)
|
||||
|
||||
if c.SQL.CreateTables {
|
||||
err = sai.CreateTablesIfNotExists()
|
||||
cmd.FailOnError(err, "Failed to create tables")
|
||||
}
|
||||
|
||||
go cmd.ProfileCmd("SA", stats)
|
||||
|
||||
connectionHandler := func(*rpc.AmqpRPCServer) {}
|
||||
|
|
|
|||
|
|
@ -140,42 +140,30 @@ var testKey = rsa.PrivateKey{
|
|||
Primes: []*big.Int{p, q},
|
||||
}
|
||||
|
||||
// TODO(jmhodges): Turn this into boulder_sa_test
|
||||
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
const dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
|
||||
|
||||
func TestFindExpiringCertificates(t *testing.T) {
|
||||
dbMap, err := sa.NewDbMap(dbConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't connect the database: %s", err)
|
||||
}
|
||||
err = dbMap.CreateTablesIfNotExists()
|
||||
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tables: %s", err)
|
||||
t.Fatalf("unable to create SQLStorageAuthority: %s", err)
|
||||
}
|
||||
err = dbMap.TruncateTables()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't truncate tables: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err = dbMap.TruncateTables()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't truncate tables after the test: %s", err)
|
||||
}
|
||||
dbMap.Db.Close()
|
||||
}()
|
||||
|
||||
defer cleanUp()
|
||||
tmpl, err := template.New("expiry-email").Parse(testTmpl)
|
||||
test.AssertNotError(t, err, "Couldn't parse test email template")
|
||||
stats, _ := statsd.NewNoopClient(nil)
|
||||
mc := mockMail{}
|
||||
rs := newFakeRegStore()
|
||||
m := mailer{
|
||||
log: blog.GetAuditLogger(),
|
||||
stats: stats,
|
||||
mailer: &mc,
|
||||
emailTemplate: tmpl,
|
||||
dbMap: dbMap,
|
||||
rs: rs,
|
||||
rs: ssa,
|
||||
nagTimes: []time.Duration{time.Hour * 24, time.Hour * 24 * 4, time.Hour * 24 * 7},
|
||||
limit: 100,
|
||||
}
|
||||
|
|
@ -208,6 +196,15 @@ func TestFindExpiringCertificates(t *testing.T) {
|
|||
},
|
||||
Key: keyB,
|
||||
}
|
||||
regA, err = ssa.NewRegistration(regA)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't store regA: %s", err)
|
||||
}
|
||||
regB, err = ssa.NewRegistration(regB)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't store regB: %s", err)
|
||||
}
|
||||
|
||||
rawCertA := x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "happy A",
|
||||
|
|
@ -218,7 +215,7 @@ func TestFindExpiringCertificates(t *testing.T) {
|
|||
}
|
||||
certDerA, _ := x509.CreateCertificate(rand.Reader, &rawCertA, &rawCertA, &testKey.PublicKey, &testKey)
|
||||
certA := &core.Certificate{
|
||||
RegistrationID: 1,
|
||||
RegistrationID: regA.ID,
|
||||
Status: core.StatusValid,
|
||||
Serial: "001",
|
||||
Expires: time.Now().AddDate(0, 0, 1),
|
||||
|
|
@ -236,7 +233,7 @@ func TestFindExpiringCertificates(t *testing.T) {
|
|||
}
|
||||
certDerB, _ := x509.CreateCertificate(rand.Reader, &rawCertB, &rawCertB, &testKey.PublicKey, &testKey)
|
||||
certB := &core.Certificate{
|
||||
RegistrationID: 1,
|
||||
RegistrationID: regA.ID,
|
||||
Status: core.StatusValid,
|
||||
Serial: "002",
|
||||
Expires: time.Now().AddDate(0, 0, 3),
|
||||
|
|
@ -254,15 +251,13 @@ func TestFindExpiringCertificates(t *testing.T) {
|
|||
}
|
||||
certDerC, _ := x509.CreateCertificate(rand.Reader, &rawCertC, &rawCertC, &testKey.PublicKey, &testKey)
|
||||
certC := &core.Certificate{
|
||||
RegistrationID: 2,
|
||||
RegistrationID: regB.ID,
|
||||
Status: core.StatusValid,
|
||||
Serial: "003",
|
||||
Expires: time.Now().AddDate(0, 0, 7),
|
||||
DER: certDerC,
|
||||
}
|
||||
certStatusC := &core.CertificateStatus{Serial: "003"}
|
||||
rs.RegById[regA.ID] = regA
|
||||
rs.RegById[regB.ID] = regB
|
||||
|
||||
err = dbMap.Insert(certA)
|
||||
test.AssertNotError(t, err, "Couldn't add certA")
|
||||
|
|
|
|||
|
|
@ -164,8 +164,6 @@ func main() {
|
|||
|
||||
dbMap.AddTableWithName(core.ExternalCert{}, "externalCerts").SetKeys(false, "SHA1")
|
||||
dbMap.AddTableWithName(core.IdentifierData{}, "identifierData").SetKeys(false, "CertSHA1")
|
||||
err = dbMap.CreateTablesIfNotExists()
|
||||
cmd.FailOnError(err, "Could not create the tables")
|
||||
|
||||
// Note that this order of operations is intentional: we first add
|
||||
// new certs to the database. Then, since certs are identified by
|
||||
|
|
|
|||
|
|
@ -104,8 +104,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
SQL struct {
|
||||
CreateTables bool
|
||||
SQLDebug bool
|
||||
SQLDebug bool
|
||||
}
|
||||
|
||||
Statsd struct {
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ type StorageAuthority interface {
|
|||
|
||||
// CertificateAuthorityDatabase represents an atomic sequence source
|
||||
type CertificateAuthorityDatabase interface {
|
||||
CreateTablesIfNotExists() error
|
||||
IncrementAndGetSerial(*gorp.Transaction) (int64, error)
|
||||
Begin() (*gorp.Transaction, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
These `.sql` files define the table layout, indicies, relationships, and users default to Boulder. Implementors should use these as starting points for their own configuration.
|
||||
The `sql` files here define the database user relationships between
|
||||
the various databases and services. Implementors should use these as
|
||||
starting points for their own configuration. The actual schemas are
|
||||
managed by [goose](https://bitbucket.org/liamstask/goose) and can be
|
||||
found in `./ca/_db` and `./sa/_db`.
|
||||
|
||||
The currently supported database is MariaDB 10.
|
||||
|
||||
mysql -u root -e "create database boulder_test; create database boulder_development; grant all privileges on boulder_test.* to 'boulder'@'localhost';"
|
||||
The databases that boulder requires to operate in development and
|
||||
testing can be created using test/create\_db.sh. It uses the root
|
||||
MariaDB user, so if you have disabled that account you may have to
|
||||
adjust the file or recreate the commands.
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
--
|
||||
-- Copyright 2015 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/.
|
||||
--
|
||||
-- This file defines the table schema, foreign keys and indicies of the
|
||||
-- Certificate Authority database, which is logically separate from that which
|
||||
-- is utilized by the Storage Authority and administrator tools.
|
||||
--
|
||||
|
||||
|
||||
CREATE TABLE `serialNumber` (
|
||||
`id` int(11) DEFAULT NULL,
|
||||
`number` int(11) DEFAULT NULL,
|
||||
`lastUpdated` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO `serialNumber`
|
||||
(`id`,
|
||||
`number`,
|
||||
`lastUpdated`)
|
||||
VALUES
|
||||
(1,
|
||||
1,
|
||||
now() );
|
||||
|
|
@ -122,12 +122,14 @@ var (
|
|||
AuthzFinal = core.Authorization{}
|
||||
|
||||
log = mocks.UseMockLog()
|
||||
|
||||
// TODO(jmhodges): Turn this into boulder_sa_test
|
||||
dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
)
|
||||
|
||||
func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, func()) {
|
||||
const (
|
||||
caDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_ca_test"
|
||||
saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
|
||||
)
|
||||
|
||||
func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, func()) {
|
||||
err := json.Unmarshal(AccountKeyJSONA, &AccountKeyA)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal public JWK")
|
||||
err = json.Unmarshal(AccountKeyJSONB, &AccountKeyB)
|
||||
|
|
@ -141,7 +143,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
|
|||
err = json.Unmarshal(ShortKeyJSON, &ShortKey)
|
||||
test.AssertNotError(t, err, "Failed to unmarshall JWK")
|
||||
|
||||
dbMap, err := sa.NewDbMap(dbConnStr)
|
||||
dbMap, err := sa.NewDbMap(saDBConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create dbMap: %s", err)
|
||||
}
|
||||
|
|
@ -150,14 +152,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
|
|||
t.Fatalf("Failed to create SA: %s", err)
|
||||
}
|
||||
|
||||
err = ssa.CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SA tables: %s", err)
|
||||
}
|
||||
|
||||
if err = dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Failed to truncate SA tables: %s", err)
|
||||
}
|
||||
saDBCleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
|
||||
va := &DummyValidationAuthority{}
|
||||
|
||||
|
|
@ -193,17 +188,13 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
|
|||
MaxKeySize: 4096,
|
||||
}
|
||||
cleanUp := func() {
|
||||
if err = dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Failed to truncate tables after the test: %s", err)
|
||||
}
|
||||
dbMap.Db.Close()
|
||||
saDBCleanUp()
|
||||
caDBCleanUp()
|
||||
}
|
||||
|
||||
csrDER, _ := hex.DecodeString(CSRhex)
|
||||
ExampleCSR, _ = x509.ParseCertificateRequest(csrDER)
|
||||
|
||||
// This registration implicitly gets ID = 1
|
||||
Registration, _ = ssa.NewRegistration(core.Registration{Key: AccountKeyA})
|
||||
|
||||
ra := NewRegistrationAuthorityImpl()
|
||||
|
|
@ -225,7 +216,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
|
|||
AuthzFinal.Expires = &exp
|
||||
AuthzFinal.Challenges[0].Status = "valid"
|
||||
|
||||
return &ca, va, ssa, &ra, cleanUp
|
||||
return va, ssa, &ra, cleanUp
|
||||
}
|
||||
|
||||
// This is an unfortunate bit of tech debt that is being taken on in
|
||||
|
|
@ -235,7 +226,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
|
|||
// CertificateAuthorityClient, so this is only marginally worse.
|
||||
// TODO(Issue #628): use a CAClient fake instead of a CAImpl instance
|
||||
func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
|
||||
dbMap, err := sa.NewDbMap(dbConnStr)
|
||||
dbMap, err := sa.NewDbMap(caDBConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not construct dbMap: %s", err)
|
||||
}
|
||||
|
|
@ -245,34 +236,17 @@ func caDBImpl(t *testing.T) (core.CertificateAuthorityDatabase, func()) {
|
|||
t.Fatalf("Could not construct CA DB: %s", err)
|
||||
}
|
||||
|
||||
// We intentionally call CreateTablesIfNotExists twice before
|
||||
// returning because of the weird insert inside it. The
|
||||
// CADatabaseImpl code expects the existence of a single row in
|
||||
// its serialIds table or else it errors. CreateTablesIfNotExists
|
||||
// currently inserts that row and TruncateTables will remove
|
||||
// it. But we need to make sure the tables exist before
|
||||
// TruncateTables can be called to reset the table. So, two calls
|
||||
// to CreateTablesIfNotExists.
|
||||
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
|
||||
err = cadb.CreateTablesIfNotExists()
|
||||
// This row is required to exist for caDBImpl to work
|
||||
// correctly. We can no longer use
|
||||
// dbMap.Insert(&SerialNumber{...}) for this because gorp will
|
||||
// ignore the ID and insert a new row at a new autoincrement id.
|
||||
// TODO(jmhodges): gen ids flickr-style, no row needed a head of time
|
||||
_, err = dbMap.Db.Exec("insert into serialNumber (id, number, lastUpdated) VALUES (?, ?, ?)", 1, 1, time.Now())
|
||||
if err != nil {
|
||||
t.Fatalf("Could not construct tables: %s", err)
|
||||
t.Fatalf("unable to create the serial number row: %s", err)
|
||||
}
|
||||
err = dbMap.TruncateTables()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not truncate tables: %s", err)
|
||||
}
|
||||
err = cadb.CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not construct tables: %s", err)
|
||||
}
|
||||
cleanUp := func() {
|
||||
if err := dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Could not truncate tables after the test: %s", err)
|
||||
}
|
||||
dbMap.Db.Close()
|
||||
}
|
||||
|
||||
return cadb, cleanUp
|
||||
}
|
||||
|
||||
|
|
@ -327,7 +301,7 @@ func TestValidateEmail(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewRegistration(t *testing.T) {
|
||||
_, _, sa, ra, cleanUp := initAuthorities(t)
|
||||
_, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
|
||||
input := core.Registration{
|
||||
|
|
@ -352,7 +326,7 @@ func TestNewRegistration(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
|
||||
_, _, _, ra, cleanUp := initAuthorities(t)
|
||||
_, _, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
|
||||
input := core.Registration{
|
||||
|
|
@ -380,7 +354,7 @@ func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewRegistrationBadKey(t *testing.T) {
|
||||
_, _, _, ra, cleanUp := initAuthorities(t)
|
||||
_, _, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
mailto, _ := core.ParseAcmeURL("mailto:foo@letsencrypt.org")
|
||||
input := core.Registration{
|
||||
|
|
@ -393,12 +367,12 @@ func TestNewRegistrationBadKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewAuthorization(t *testing.T) {
|
||||
_, _, sa, ra, cleanUp := initAuthorities(t)
|
||||
_, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
_, err := ra.NewAuthorization(AuthzRequest, 0)
|
||||
test.AssertError(t, err, "Authorization cannot have registrationID == 0")
|
||||
|
||||
authz, err := ra.NewAuthorization(AuthzRequest, 1)
|
||||
authz, err := ra.NewAuthorization(AuthzRequest, Registration.ID)
|
||||
test.AssertNotError(t, err, "NewAuthorization failed")
|
||||
|
||||
// Verify that returned authz same as DB
|
||||
|
|
@ -407,7 +381,7 @@ func TestNewAuthorization(t *testing.T) {
|
|||
assertAuthzEqual(t, authz, dbAuthz)
|
||||
|
||||
// Verify that the returned authz has the right information
|
||||
test.Assert(t, authz.RegistrationID == 1, "Initial authz did not get the right registration ID")
|
||||
test.Assert(t, authz.RegistrationID == Registration.ID, "Initial authz did not get the right registration ID")
|
||||
test.Assert(t, authz.Identifier == AuthzRequest.Identifier, "Initial authz had wrong identifier")
|
||||
test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending")
|
||||
|
||||
|
|
@ -424,7 +398,7 @@ func TestNewAuthorization(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateAuthorization(t *testing.T) {
|
||||
_, va, sa, ra, cleanUp := initAuthorities(t)
|
||||
va, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
|
||||
// We know this is OK because of TestNewAuthorization
|
||||
|
|
@ -472,7 +446,7 @@ func TestUpdateAuthorizationReject(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOnValidationUpdate(t *testing.T) {
|
||||
_, _, sa, ra, cleanUp := initAuthorities(t)
|
||||
_, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
AuthzUpdated, _ = sa.NewPendingAuthorization(AuthzUpdated)
|
||||
sa.UpdatePendingAuthorization(AuthzUpdated)
|
||||
|
|
@ -495,7 +469,7 @@ func TestOnValidationUpdate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
|
||||
_, _, sa, ra, cleanUp := initAuthorities(t)
|
||||
_, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
authz := core.Authorization{}
|
||||
authz, _ = sa.NewPendingAuthorization(authz)
|
||||
|
|
@ -518,8 +492,8 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
|
|||
CSR: parsedCSR,
|
||||
}
|
||||
|
||||
// Registration id 1 has key == AccountKeyA
|
||||
_, err = ra.NewCertificate(certRequest, 1)
|
||||
// Registration has key == AccountKeyA
|
||||
_, err = ra.NewCertificate(certRequest, Registration.ID)
|
||||
test.AssertError(t, err, "Should have rejected cert with key = account key")
|
||||
test.AssertEquals(t, err.Error(), "Certificate public key must be different than account key")
|
||||
|
||||
|
|
@ -527,7 +501,7 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAuthorizationRequired(t *testing.T) {
|
||||
_, _, sa, ra, cleanUp := initAuthorities(t)
|
||||
_, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
AuthzFinal.RegistrationID = 1
|
||||
AuthzFinal, _ = sa.NewPendingAuthorization(AuthzFinal)
|
||||
|
|
@ -547,9 +521,9 @@ func TestAuthorizationRequired(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewCertificate(t *testing.T) {
|
||||
_, _, sa, ra, cleanUp := initAuthorities(t)
|
||||
_, sa, ra, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
AuthzFinal.RegistrationID = 1
|
||||
AuthzFinal.RegistrationID = Registration.ID
|
||||
AuthzFinal, _ = sa.NewPendingAuthorization(AuthzFinal)
|
||||
sa.UpdatePendingAuthorization(AuthzFinal)
|
||||
sa.FinalizeAuthorization(AuthzFinal)
|
||||
|
|
@ -564,7 +538,7 @@ func TestNewCertificate(t *testing.T) {
|
|||
CSR: ExampleCSR,
|
||||
}
|
||||
|
||||
cert, err := ra.NewCertificate(certRequest, 1)
|
||||
cert, err := ra.NewCertificate(certRequest, Registration.ID)
|
||||
test.AssertNotError(t, err, "Failed to issue certificate")
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
development:
|
||||
driver: mysql
|
||||
open: boulder@tcp(localhost:3306)/boulder_sa_development
|
||||
test:
|
||||
driver: mysql
|
||||
open: boulder@tcp(localhost:3306)/boulder_sa_test
|
||||
integration:
|
||||
driver: mysql
|
||||
open: boulder@tcp(localhost:3306)/boulder_sa_integration
|
||||
|
|
@ -1,13 +1,6 @@
|
|||
--
|
||||
-- Copyright 2015 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/.
|
||||
--
|
||||
-- This file defines the table schema, foreign keys and indicies of the
|
||||
-- primary database, used by all the parts of Boulder except the Certificate
|
||||
-- Authority module, which utilizes its own database.
|
||||
--
|
||||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
CREATE TABLE `registrations` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
|
|
@ -127,3 +120,21 @@ CREATE TABLE `externalCerts` (
|
|||
`rawDERCert` blob DEFAULT NULL,
|
||||
UNIQUE INDEX (sha1)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
ALTER TABLE `pending_authz` DROP FOREIGN KEY `regId_pending_authz`;
|
||||
ALTER TABLE `certificates` DROP FOREIGN KEY `regId_certificates`;
|
||||
ALTER TABLE `authz` DROP FOREIGN KEY `regId_authz`;
|
||||
DROP TABLE `registrations`;
|
||||
DROP TABLE `authz`;
|
||||
DROP TABLE `certificates`;
|
||||
DROP TABLE `certificateStatus`;
|
||||
DROP TABLE `crls`;
|
||||
DROP TABLE `deniedCSRs`;
|
||||
DROP TABLE `ocspResponses`;
|
||||
DROP TABLE `pending_authz`;
|
||||
DROP TABLE `identifierData`;
|
||||
DROP TABLE `externalCerts`;
|
||||
|
|
@ -85,6 +85,15 @@ func recombineURLForDB(dbConnect string) (string, error) {
|
|||
// instead of the number of rows changed by the UPDATE.
|
||||
dsnVals.Set("clientFoundRows", "true")
|
||||
|
||||
// Ensures that MySQL/MariaDB warnings are treated as errors. This
|
||||
// avoids a number of nasty edge conditions we could wander
|
||||
// into. Common things this discovers includes places where data
|
||||
// being sent had a different type than what is in the schema,
|
||||
// strings being truncated, writing null to a NOT NULL column, and
|
||||
// so on. See
|
||||
// <https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-strict>.
|
||||
dsnVals.Set("strict", "true")
|
||||
|
||||
user := dbURL.User.Username()
|
||||
passwd, hasPass := dbURL.User.Password()
|
||||
dbConn := ""
|
||||
|
|
@ -118,8 +127,7 @@ func (log *SQLLogger) Printf(format string, v ...interface{}) {
|
|||
log.log.Debug(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// initTables constructs the table map for the ORM. If you want to also create
|
||||
// the tables, call CreateTablesIfNotExists on the DbMap.
|
||||
// initTables constructs the table map for the ORM.
|
||||
func initTables(dbMap *gorp.DbMap) {
|
||||
regTable := dbMap.AddTableWithName(regModel{}, "registrations").SetKeys(true, "ID")
|
||||
regTable.SetVersionCol("LockCol")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package satest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
)
|
||||
|
||||
var theKey = `{
|
||||
"kty": "RSA",
|
||||
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
|
||||
"e": "AQAB"
|
||||
}`
|
||||
|
||||
// GoodJWK returns a known-good JsonWebKey that is always the
|
||||
// same. This a hack to allow both the CA and SA tests to benefit
|
||||
// because the CA tests currently require a full-fledged
|
||||
// SQLSAImpl. Long term, when the CA tests no longer need
|
||||
// CreateWorkingRegistration, this and CreateWorkingRegistration can
|
||||
// be pushed back into the SA tests proper.
|
||||
func GoodJWK() jose.JsonWebKey {
|
||||
var jwk jose.JsonWebKey
|
||||
err := json.Unmarshal([]byte(theKey), &jwk)
|
||||
if err != nil {
|
||||
panic("known-good theKey is no longer known-good")
|
||||
}
|
||||
return jwk
|
||||
}
|
||||
|
||||
// CreateWorkingRegistration inserts a new, correct Registration into
|
||||
// SA using GoodKey under the hood. This a hack to allow both the CA
|
||||
// and SA tests to benefit because the CA tests currently require a
|
||||
// full-fledged SQLSAImpl. Long term, when the CA tests no longer need
|
||||
// CreateWorkingRegistration, this and CreateWorkingRegistration can
|
||||
// be pushed back into the SA tests proper.
|
||||
func CreateWorkingRegistration(t *testing.T, sa core.StorageAuthority) core.Registration {
|
||||
contact, err := core.ParseAcmeURL("mailto:foo@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse contact link: %s", err)
|
||||
}
|
||||
contacts := []*core.AcmeURL{contact}
|
||||
reg, err := sa.NewRegistration(core.Registration{
|
||||
Key: GoodJWK(),
|
||||
Contact: contacts,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create new registration")
|
||||
}
|
||||
return reg
|
||||
}
|
||||
|
|
@ -69,12 +69,6 @@ func (ssa *SQLStorageAuthority) SetSQLDebug(state bool) {
|
|||
SetSQLDebug(ssa.dbMap, state)
|
||||
}
|
||||
|
||||
// CreateTablesIfNotExists instructs the ORM to create any missing tables.
|
||||
func (ssa *SQLStorageAuthority) CreateTablesIfNotExists() (err error) {
|
||||
err = ssa.dbMap.CreateTablesIfNotExists()
|
||||
return
|
||||
}
|
||||
|
||||
func statusIsPending(status core.AcmeStatus) bool {
|
||||
return status == core.StatusPending || status == core.StatusProcessing || status == core.StatusUnknown
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ import (
|
|||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
"github.com/letsencrypt/boulder/sa/satest"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
var log = mocks.UseMockLog()
|
||||
const dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
|
||||
|
||||
// TODO(jmhodges): change this to boulder_sa_test database
|
||||
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
var log = mocks.UseMockLog()
|
||||
|
||||
// initSA constructs a SQLStorageAuthority and a clean up function
|
||||
// that should be defer'ed to the end of the test.
|
||||
|
|
@ -39,26 +39,11 @@ func initSA(t *testing.T) (*SQLStorageAuthority, func()) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create SA: %s", err)
|
||||
}
|
||||
if err = sa.CreateTablesIfNotExists(); err != nil {
|
||||
t.Fatalf("Failed to create tables: %s", err)
|
||||
}
|
||||
if err = sa.dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Failed to truncate tables: %s", err)
|
||||
}
|
||||
return sa, func() {
|
||||
if err = sa.dbMap.TruncateTables(); err != nil {
|
||||
t.Fatalf("Failed to truncate tables after the test: %s", err)
|
||||
}
|
||||
sa.dbMap.Db.Close()
|
||||
}
|
||||
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
return sa, cleanUp
|
||||
}
|
||||
|
||||
var (
|
||||
theKey = `{
|
||||
"kty": "RSA",
|
||||
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
|
||||
"e": "AQAB"
|
||||
}`
|
||||
anotherKey = `{
|
||||
"kty":"RSA",
|
||||
"n": "vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw",
|
||||
|
|
@ -70,12 +55,7 @@ func TestAddRegistration(t *testing.T) {
|
|||
sa, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
var jwk jose.JsonWebKey
|
||||
err := json.Unmarshal([]byte(theKey), &jwk)
|
||||
if err != nil {
|
||||
t.Errorf("JSON unmarshal error: %+v", err)
|
||||
return
|
||||
}
|
||||
jwk := satest.GoodJWK()
|
||||
|
||||
contact, err := core.ParseAcmeURL("mailto:foo@example.com")
|
||||
if err != nil {
|
||||
|
|
@ -132,9 +112,7 @@ func TestNoSuchRegistrationErrors(t *testing.T) {
|
|||
t.Errorf("GetRegistration: expected NoSuchRegistrationError, got %T type error (%s)", err, err)
|
||||
}
|
||||
|
||||
var jwk jose.JsonWebKey
|
||||
err = json.Unmarshal([]byte(theKey), &jwk)
|
||||
test.AssertNotError(t, err, "Unmarshal")
|
||||
jwk := satest.GoodJWK()
|
||||
_, err = sa.GetRegistrationByKey(jwk)
|
||||
if _, ok := err.(NoSuchRegistrationError); !ok {
|
||||
t.Errorf("GetRegistrationByKey: expected a NoSuchRegistrationError, got %T type error (%s)", err, err)
|
||||
|
|
@ -150,7 +128,8 @@ func TestAddAuthorization(t *testing.T) {
|
|||
sa, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
PA := core.Authorization{}
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
PA := core.Authorization{RegistrationID: reg.ID}
|
||||
|
||||
PA, err := sa.NewPendingAuthorization(PA)
|
||||
test.AssertNotError(t, err, "Couldn't create new pending authorization")
|
||||
|
|
@ -163,19 +142,12 @@ func TestAddAuthorization(t *testing.T) {
|
|||
expectedPa := core.Authorization{ID: PA.ID}
|
||||
test.AssertMarshaledEquals(t, dbPa.ID, expectedPa.ID)
|
||||
|
||||
var jwk jose.JsonWebKey
|
||||
err = json.Unmarshal([]byte(theKey), &jwk)
|
||||
if err != nil {
|
||||
t.Errorf("JSON unmarshal error: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
combos := make([][]int, 1)
|
||||
combos[0] = []int{0, 1}
|
||||
|
||||
exp := time.Now().AddDate(0, 0, 1)
|
||||
identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "wut.com"}
|
||||
newPa := core.Authorization{ID: PA.ID, Identifier: identifier, RegistrationID: 0, Status: core.StatusPending, Expires: &exp, Combinations: combos}
|
||||
newPa := core.Authorization{ID: PA.ID, Identifier: identifier, RegistrationID: reg.ID, Status: core.StatusPending, Expires: &exp, Combinations: combos}
|
||||
err = sa.UpdatePendingAuthorization(newPa)
|
||||
test.AssertNotError(t, err, "Couldn't update pending authorization with ID "+PA.ID)
|
||||
|
||||
|
|
@ -188,9 +160,16 @@ func TestAddAuthorization(t *testing.T) {
|
|||
}
|
||||
|
||||
func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority) (authz core.Authorization) {
|
||||
return CreateDomainAuthWithRegId(t, domainName, sa, 42)
|
||||
}
|
||||
|
||||
func CreateDomainAuthWithRegId(t *testing.T, domainName string, sa *SQLStorageAuthority, regID int64) (authz core.Authorization) {
|
||||
|
||||
// create pending auth
|
||||
authz, err := sa.NewPendingAuthorization(core.Authorization{Challenges: []core.Challenge{core.Challenge{}}})
|
||||
test.AssertNotError(t, err, "Couldn't create new pending authorization")
|
||||
authz, err := sa.NewPendingAuthorization(core.Authorization{RegistrationID: regID, Challenges: []core.Challenge{core.Challenge{}}})
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create new pending authorization: %s", err)
|
||||
}
|
||||
test.Assert(t, authz.ID != "", "ID shouldn't be blank")
|
||||
|
||||
// prepare challenge for auth
|
||||
|
|
@ -204,7 +183,6 @@ func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority)
|
|||
// validate pending auth
|
||||
authz.Status = core.StatusPending
|
||||
authz.Identifier = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domainName}
|
||||
authz.RegistrationID = 42
|
||||
authz.Expires = &exp
|
||||
authz.Challenges = []core.Challenge{chall}
|
||||
authz.Combinations = combos
|
||||
|
|
@ -225,8 +203,10 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) {
|
|||
authz, err := sa.GetLatestValidAuthorization(0, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
|
||||
test.AssertError(t, err, "Should not have found a valid auth for example.org")
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
|
||||
// authorize "example.org"
|
||||
authz = CreateDomainAuth(t, "example.org", sa)
|
||||
authz = CreateDomainAuthWithRegId(t, "example.org", sa, reg.ID)
|
||||
|
||||
// finalize auth
|
||||
authz.Status = core.StatusValid
|
||||
|
|
@ -238,12 +218,12 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) {
|
|||
test.AssertError(t, err, "Should not have found a valid auth for example.org and regID 0")
|
||||
|
||||
// get authorized domain
|
||||
authz, err = sa.GetLatestValidAuthorization(42, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
|
||||
authz, err = sa.GetLatestValidAuthorization(reg.ID, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "example.org"})
|
||||
test.AssertNotError(t, err, "Should have found a valid auth for example.org and regID 42")
|
||||
test.AssertEquals(t, authz.Status, core.StatusValid)
|
||||
test.AssertEquals(t, authz.Identifier.Type, core.IdentifierDNS)
|
||||
test.AssertEquals(t, authz.Identifier.Value, "example.org")
|
||||
test.AssertEquals(t, authz.RegistrationID, int64(42))
|
||||
test.AssertEquals(t, authz.RegistrationID, reg.ID)
|
||||
}
|
||||
|
||||
// Ensure we get the latest valid authorization for an ident
|
||||
|
|
@ -253,11 +233,11 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
|
||||
domain := "example.org"
|
||||
ident := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: domain}
|
||||
regID := int64(42)
|
||||
var err error
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
// create invalid authz
|
||||
authz := CreateDomainAuth(t, domain, sa)
|
||||
authz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
|
||||
exp := time.Now().AddDate(0, 0, 10) // expire in 10 day
|
||||
authz.Expires = &exp
|
||||
authz.Status = core.StatusInvalid
|
||||
|
|
@ -265,11 +245,11 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
|
||||
|
||||
// should not get the auth
|
||||
authz, err = sa.GetLatestValidAuthorization(regID, ident)
|
||||
authz, err = sa.GetLatestValidAuthorization(reg.ID, ident)
|
||||
test.AssertError(t, err, "Should not have found a valid auth for "+domain)
|
||||
|
||||
// create valid auth
|
||||
authz = CreateDomainAuth(t, domain, sa)
|
||||
authz = CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
|
||||
exp = time.Now().AddDate(0, 0, 1) // expire in 1 day
|
||||
authz.Expires = &exp
|
||||
authz.Status = core.StatusValid
|
||||
|
|
@ -277,27 +257,27 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+authz.ID)
|
||||
|
||||
// should get the valid auth even if it's expire date is lower than the invalid one
|
||||
authz, err = sa.GetLatestValidAuthorization(regID, ident)
|
||||
authz, err = sa.GetLatestValidAuthorization(reg.ID, ident)
|
||||
test.AssertNotError(t, err, "Should have found a valid auth for "+domain)
|
||||
test.AssertEquals(t, authz.Status, core.StatusValid)
|
||||
test.AssertEquals(t, authz.Identifier.Type, ident.Type)
|
||||
test.AssertEquals(t, authz.Identifier.Value, ident.Value)
|
||||
test.AssertEquals(t, authz.RegistrationID, regID)
|
||||
test.AssertEquals(t, authz.RegistrationID, reg.ID)
|
||||
|
||||
// create a newer auth
|
||||
newAuthz := CreateDomainAuth(t, domain, sa)
|
||||
newAuthz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
|
||||
exp = time.Now().AddDate(0, 0, 2) // expire in 2 day
|
||||
newAuthz.Expires = &exp
|
||||
newAuthz.Status = core.StatusValid
|
||||
err = sa.FinalizeAuthorization(newAuthz)
|
||||
test.AssertNotError(t, err, "Couldn't finalize pending authorization with ID "+newAuthz.ID)
|
||||
|
||||
authz, err = sa.GetLatestValidAuthorization(regID, ident)
|
||||
authz, err = sa.GetLatestValidAuthorization(reg.ID, ident)
|
||||
test.AssertNotError(t, err, "Should have found a valid auth for "+domain)
|
||||
test.AssertEquals(t, authz.Status, core.StatusValid)
|
||||
test.AssertEquals(t, authz.Identifier.Type, ident.Type)
|
||||
test.AssertEquals(t, authz.Identifier.Value, ident.Value)
|
||||
test.AssertEquals(t, authz.RegistrationID, regID)
|
||||
test.AssertEquals(t, authz.RegistrationID, reg.ID)
|
||||
// make sure we got the latest auth
|
||||
test.AssertEquals(t, authz.ID, newAuthz.ID)
|
||||
}
|
||||
|
|
@ -306,11 +286,13 @@ func TestAddCertificate(t *testing.T) {
|
|||
sa, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
|
||||
// An example cert taken from EFF's website
|
||||
certDER, err := ioutil.ReadFile("www.eff.org.der")
|
||||
test.AssertNotError(t, err, "Couldn't read example cert DER")
|
||||
|
||||
digest, err := sa.AddCertificate(certDER, 1)
|
||||
digest, err := sa.AddCertificate(certDER, reg.ID)
|
||||
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
||||
test.AssertEquals(t, digest, "qWoItDZmR4P9eFbeYgXXP3SR4ApnkQj8x4LsB_ORKBo")
|
||||
|
||||
|
|
@ -333,7 +315,7 @@ func TestAddCertificate(t *testing.T) {
|
|||
certDER2, err := ioutil.ReadFile("test-cert.der")
|
||||
test.AssertNotError(t, err, "Couldn't read example cert DER")
|
||||
|
||||
digest2, err := sa.AddCertificate(certDER2, 1)
|
||||
digest2, err := sa.AddCertificate(certDER2, reg.ID)
|
||||
test.AssertNotError(t, err, "Couldn't add test-cert.der")
|
||||
test.AssertEquals(t, digest2, "CMVYqWzyqUW7pfBF2CxL0Uk6I0Upsk7p4EWSnd_vYx4")
|
||||
|
||||
|
|
@ -387,10 +369,11 @@ func TestUpdateOCSP(t *testing.T) {
|
|||
sa, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
// Add a cert to the DB to test with.
|
||||
certDER, err := ioutil.ReadFile("www.eff.org.der")
|
||||
test.AssertNotError(t, err, "Couldn't read example cert DER")
|
||||
_, err = sa.AddCertificate(certDER, 1)
|
||||
_, err = sa.AddCertificate(certDER, reg.ID)
|
||||
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
||||
|
||||
serial := "00000000000000000000000000021bd4"
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
"ca": {
|
||||
"serialPrefix": 255,
|
||||
"profile": "ee",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_ca_integration",
|
||||
"debugAddr": "localhost:8001",
|
||||
"testMode": true,
|
||||
"_comment": "This should only be present in testMode. In prod use an HSM.",
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
},
|
||||
|
||||
"sa": {
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"debugAddr": "localhost:8003"
|
||||
},
|
||||
|
||||
|
|
@ -124,27 +124,22 @@
|
|||
},
|
||||
|
||||
"sql": {
|
||||
"SQLDebug": true,
|
||||
"CreateTables": true
|
||||
"SQLDebug": true
|
||||
},
|
||||
|
||||
"revoker": {
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration"
|
||||
},
|
||||
|
||||
"ocspResponder": {
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
},
|
||||
|
||||
"ocspResponder": {
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"path": "/",
|
||||
"listenAddress": "localhost:4002",
|
||||
"debugAddr": "localhost:8005"
|
||||
},
|
||||
|
||||
"ocspUpdater": {
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"minTimeToExpiry": "72h",
|
||||
"debugAddr": "localhost:8006"
|
||||
},
|
||||
|
|
@ -158,7 +153,7 @@
|
|||
"port": "25",
|
||||
"username": "cert-master@example.com",
|
||||
"password": "password",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"messageLimit": 0,
|
||||
"nagTimes": ["24h", "72h", "168h", "336h"],
|
||||
"emailTemplate": "test/example-expiration-template",
|
||||
|
|
|
|||
|
|
@ -114,8 +114,7 @@
|
|||
},
|
||||
|
||||
"sql": {
|
||||
"SQLDebug": true,
|
||||
"CreateTables": true
|
||||
"SQLDebug": true
|
||||
},
|
||||
|
||||
"revoker": {
|
||||
|
|
|
|||
|
|
@ -7,5 +7,21 @@ function die() {
|
|||
exit 1
|
||||
}
|
||||
|
||||
mysql -u root -e "create database boulder_test; grant all privileges on boulder_test.* to 'boulder'@'localhost'" || die "unable to create boulder_test"
|
||||
echo "created boulder_test database"
|
||||
SERVICES="ca
|
||||
sa"
|
||||
DBENVS="development
|
||||
test
|
||||
integration"
|
||||
|
||||
for svc in $SERVICES; do
|
||||
for dbenv in $DBENVS; do
|
||||
db="boulder_${svc}_${dbenv}"
|
||||
|
||||
mysql -u root -e "drop database if exists \`${db}\`; create database if not exists \`${db}\`; grant all privileges on ${db}.* to 'boulder'@'localhost'" || die "unable to create ${db}"
|
||||
echo "created empty ${db} database"
|
||||
|
||||
goose -path=./$svc/_db/ -env=$dbenv up || die "unable to migrate ${db}"
|
||||
echo "migrated ${db} database"
|
||||
done
|
||||
done
|
||||
echo "created all databases"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
_ CleanUpDB = &sql.DB{}
|
||||
)
|
||||
|
||||
// CleanUpDB is an interface with only what is needed to delete all
|
||||
// rows in all tables in a database plus close the database
|
||||
// connection. It is satisfied by *sql.DB.
|
||||
type CleanUpDB interface {
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// ResetTestDatabase deletes all rows in all tables available to the
|
||||
// passed in CleanUpDB, failing the tests if that errors and returning
|
||||
// a clean up function that will attempt the same plus close the
|
||||
// database. "Tables available" means all tables that can be seen in
|
||||
// the MariaDB configuration by the database user except for ones that
|
||||
// are configuration only like goose_db_version (for migrations) or
|
||||
// the ones describing the internal configuration of the server.To be
|
||||
// used only in test code.
|
||||
func ResetTestDatabase(t *testing.T, db CleanUpDB) func() {
|
||||
if err := deleteEverythingInAllTables(db); err != nil {
|
||||
t.Fatalf("Failed to delete everything: %s", err)
|
||||
}
|
||||
return func() {
|
||||
if err := deleteEverythingInAllTables(db); err != nil {
|
||||
t.Fatalf("Failed to truncate tables after the test: %s", err)
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// clearEverythingInAllTables deletes all rows in the tables
|
||||
// available to the CleanUpDB passed in and resets the autoincrement
|
||||
// counters. See allTableNamesInDB for what is meant by "all tables
|
||||
// available". To be used only in test code.
|
||||
func deleteEverythingInAllTables(db CleanUpDB) error {
|
||||
ts, err := allTableNamesInDB(db)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, tn := range ts {
|
||||
// 1 = 1 here prevents the MariaDB i_am_a_dummy setting from
|
||||
// rejecting the DELETE for not having a WHERE clause.
|
||||
_, err := db.Exec("delete from `" + tn + "` where 1 = 1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// allTableNamesInDB returns the names of the tables available to the
|
||||
// CleanUpDB passed in. "Tables available" means all tables that can
|
||||
// be seen in the MariaDB configuration by the database user except
|
||||
// for ones that are configuration only like goose_db_version (for
|
||||
// migrations) or the ones describing the internal configuration of
|
||||
// the server. To be used only in test code.
|
||||
func allTableNamesInDB(db CleanUpDB) ([]string, error) {
|
||||
r, err := db.Query("select table_name from information_schema.tables t where t.table_schema = DATABASE() and t.table_name != 'goose_db_version';")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ts []string
|
||||
for r.Next() {
|
||||
tableName := ""
|
||||
err = r.Scan(&tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, tableName)
|
||||
}
|
||||
return ts, r.Err()
|
||||
}
|
||||
|
|
@ -40,12 +40,14 @@ var ErrTooManyCNAME = errors.New("too many CNAME/DNAME lookups")
|
|||
|
||||
// ValidationAuthorityImpl represents a VA
|
||||
type ValidationAuthorityImpl struct {
|
||||
RA core.RegistrationAuthority
|
||||
log *blog.AuditLogger
|
||||
DNSResolver core.DNSResolver
|
||||
IssuerDomain string
|
||||
TestMode bool
|
||||
UserAgent string
|
||||
RA core.RegistrationAuthority
|
||||
log *blog.AuditLogger
|
||||
DNSResolver core.DNSResolver
|
||||
IssuerDomain string
|
||||
simpleHTTPPort int
|
||||
simpleHTTPSPort int
|
||||
dvsniPort int
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
// NewValidationAuthorityImpl constructs a new VA, and may place it
|
||||
|
|
@ -53,7 +55,23 @@ type ValidationAuthorityImpl struct {
|
|||
func NewValidationAuthorityImpl(tm bool) ValidationAuthorityImpl {
|
||||
logger := blog.GetAuditLogger()
|
||||
logger.Notice("Validation Authority Starting")
|
||||
return ValidationAuthorityImpl{log: logger, TestMode: tm}
|
||||
// TODO(jsha): Remove TestMode entirely. Instead, the various validation ports
|
||||
// should be exported, so the cmd file can set them based on a config.
|
||||
if tm {
|
||||
return ValidationAuthorityImpl{
|
||||
log: logger,
|
||||
simpleHTTPPort: 5001,
|
||||
simpleHTTPSPort: 5001,
|
||||
dvsniPort: 5001,
|
||||
}
|
||||
} else {
|
||||
return ValidationAuthorityImpl{
|
||||
log: logger,
|
||||
simpleHTTPPort: 80,
|
||||
simpleHTTPSPort: 443,
|
||||
dvsniPort: 443,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for audit logging
|
||||
|
|
@ -155,10 +173,8 @@ 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, defaultPort string) (dialer, *core.ProblemDetails) {
|
||||
port := "80"
|
||||
if va.TestMode {
|
||||
port = "5001"
|
||||
} else if defaultPort != "" {
|
||||
port := fmt.Sprintf("%d", va.simpleHTTPPort)
|
||||
if defaultPort != "" {
|
||||
port = defaultPort
|
||||
}
|
||||
d := dialer{
|
||||
|
|
@ -192,18 +208,23 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] Identifier failure", identifier))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
hostName := identifier.Value
|
||||
|
||||
host := identifier.Value
|
||||
var scheme string
|
||||
var port int
|
||||
if input.TLS == nil || (input.TLS != nil && *input.TLS) {
|
||||
scheme = "https"
|
||||
port = va.simpleHTTPSPort
|
||||
} else {
|
||||
scheme = "http"
|
||||
port = va.simpleHTTPPort
|
||||
}
|
||||
portString := fmt.Sprintf("%d", port)
|
||||
hostPort := net.JoinHostPort(host, portString)
|
||||
|
||||
url := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: hostName,
|
||||
Host: hostPort,
|
||||
Path: fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token),
|
||||
}
|
||||
|
||||
|
|
@ -224,12 +245,8 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
httpRequest.Header["User-Agent"] = []string{va.UserAgent}
|
||||
}
|
||||
|
||||
httpRequest.Host = hostName
|
||||
var port string
|
||||
if scheme == "https" {
|
||||
port = "443"
|
||||
}
|
||||
dialer, prob := va.resolveAndConstructDialer(hostName, port)
|
||||
httpRequest.Host = hostPort
|
||||
dialer, prob := va.resolveAndConstructDialer(host, portString)
|
||||
dialer.record.URL = url.String()
|
||||
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
|
||||
if prob != nil {
|
||||
|
|
@ -255,15 +272,15 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
return fmt.Errorf("Too many redirects")
|
||||
}
|
||||
|
||||
host := req.URL.Host
|
||||
port = ""
|
||||
if strings.Contains(host, ":") {
|
||||
splitHost := strings.SplitN(host, ":", 2)
|
||||
reqHost := req.URL.Host
|
||||
reqPort := ""
|
||||
if strings.Contains(reqHost, ":") {
|
||||
splitHost := strings.SplitN(reqHost, ":", 2)
|
||||
if len(splitHost) <= 1 {
|
||||
return fmt.Errorf("Malformed host")
|
||||
}
|
||||
host, port = splitHost[0], splitHost[1]
|
||||
portNum, err := strconv.Atoi(port)
|
||||
reqHost, reqPort = splitHost[0], splitHost[1]
|
||||
portNum, err := strconv.Atoi(reqPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -271,10 +288,10 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
return fmt.Errorf("Invalid port number in redirect")
|
||||
}
|
||||
} else if strings.ToLower(req.URL.Scheme) == "https" {
|
||||
port = "443"
|
||||
reqPort = "443"
|
||||
}
|
||||
|
||||
dialer, err := va.resolveAndConstructDialer(host, port)
|
||||
dialer, err := va.resolveAndConstructDialer(reqHost, reqPort)
|
||||
dialer.record.URL = req.URL.String()
|
||||
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
|
||||
if err != nil {
|
||||
|
|
@ -412,12 +429,9 @@ func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
|
|||
}
|
||||
|
||||
// Make a connection with SNI = nonceName
|
||||
hostPort := net.JoinHostPort(addr.String(), "443")
|
||||
challenge.ValidationRecord[0].Port = "443"
|
||||
if va.TestMode {
|
||||
hostPort = net.JoinHostPort(addr.String(), "5001")
|
||||
challenge.ValidationRecord[0].Port = "5001"
|
||||
}
|
||||
portString := fmt.Sprintf("%d", va.dvsniPort)
|
||||
hostPort := net.JoinHostPort(addr.String(), portString)
|
||||
challenge.ValidationRecord[0].Port = portString
|
||||
va.log.Notice(fmt.Sprintf("DVSNI [%s] Attempting to validate DVSNI for %s %s",
|
||||
identifier, hostPort, ZName))
|
||||
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import (
|
|||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -80,14 +83,14 @@ func createValidation(token string, enableTLS bool) string {
|
|||
return obj.FullSerialize()
|
||||
}
|
||||
|
||||
func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableTLS bool) {
|
||||
func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server {
|
||||
m := http.NewServeMux()
|
||||
|
||||
defaultToken := token
|
||||
currentToken := defaultToken
|
||||
|
||||
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Host != "localhost" && r.Host != "other.valid" && r.Host != "other.valid:8080" {
|
||||
if !strings.HasPrefix(r.Host, "localhost:") && r.Host != "other.valid" && r.Host != "other.valid:8080" {
|
||||
t.Errorf("Bad Host header: " + r.Host)
|
||||
}
|
||||
if strings.HasSuffix(r.URL.Path, path404) {
|
||||
|
|
@ -133,21 +136,10 @@ func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableT
|
|||
}
|
||||
})
|
||||
|
||||
server := &http.Server{Addr: "localhost:5001", Handler: m}
|
||||
conn, err := net.Listen("tcp", server.Addr)
|
||||
if err != nil {
|
||||
waitChan <- true
|
||||
t.Fatalf("Couldn't listen on %s: %s", server.Addr, err)
|
||||
}
|
||||
server := httptest.NewUnstartedServer(m)
|
||||
|
||||
go func() {
|
||||
<-stopChan
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
var listener net.Listener
|
||||
if !enableTLS {
|
||||
listener = conn
|
||||
server.Start()
|
||||
} else {
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1337),
|
||||
|
|
@ -170,18 +162,17 @@ func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableT
|
|||
PrivateKey: &TheKey,
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
server.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{*cert},
|
||||
}
|
||||
|
||||
listener = tls.NewListener(conn, tlsConfig)
|
||||
server.StartTLS()
|
||||
}
|
||||
|
||||
waitChan <- true
|
||||
server.Serve(listener)
|
||||
return server
|
||||
}
|
||||
|
||||
func dvsniSrv(t *testing.T, chall core.Challenge, stopChan, waitChan chan bool) {
|
||||
func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
|
||||
encodedSig := core.B64enc(chall.Validation.Signatures[0].Signature)
|
||||
h := sha256.New()
|
||||
h.Write([]byte(encodedSig))
|
||||
|
|
@ -222,43 +213,25 @@ func dvsniSrv(t *testing.T, chall core.Challenge, stopChan, waitChan chan bool)
|
|||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
httpsServer := &http.Server{Addr: "localhost:5001"}
|
||||
conn, err := net.Listen("tcp", httpsServer.Addr)
|
||||
if err != nil {
|
||||
waitChan <- true
|
||||
t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
|
||||
}
|
||||
tlsListener := tls.NewListener(conn, tlsConfig)
|
||||
|
||||
go func() {
|
||||
<-stopChan
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
waitChan <- true
|
||||
httpsServer.Serve(tlsListener)
|
||||
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
hs.TLS = tlsConfig
|
||||
hs.StartTLS()
|
||||
return hs
|
||||
}
|
||||
|
||||
func brokenTLSSrv(t *testing.T, stopChan, waitChan chan bool) {
|
||||
httpsServer := &http.Server{Addr: "localhost:5001"}
|
||||
conn, err := net.Listen("tcp", httpsServer.Addr)
|
||||
if err != nil {
|
||||
waitChan <- true
|
||||
t.Fatalf("Couldn't listen on %s: %s", httpsServer.Addr, err)
|
||||
func brokenTLSSrv() *httptest.Server {
|
||||
server := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
server.TLS = &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return nil, fmt.Errorf("Failing on purpose")
|
||||
},
|
||||
}
|
||||
tlsListener := tls.NewListener(conn, &tls.Config{})
|
||||
|
||||
go func() {
|
||||
<-stopChan
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
waitChan <- true
|
||||
httpsServer.Serve(tlsListener)
|
||||
server.StartTLS()
|
||||
return server
|
||||
}
|
||||
|
||||
func TestSimpleHttpTLS(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
chall := core.Challenge{
|
||||
|
|
@ -268,11 +241,12 @@ func TestSimpleHttpTLS(t *testing.T) {
|
|||
AccountKey: accountKey,
|
||||
}
|
||||
|
||||
stopChan := make(chan bool, 1)
|
||||
waitChan := make(chan bool, 1)
|
||||
go simpleSrv(t, expectedToken, stopChan, waitChan, true)
|
||||
defer func() { stopChan <- true }()
|
||||
<-waitChan
|
||||
hs := simpleSrv(t, expectedToken, true)
|
||||
defer hs.Close()
|
||||
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
va.simpleHTTPSPort = port
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
|
|
@ -284,7 +258,7 @@ func TestSimpleHttpTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSimpleHttp(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
tls := false
|
||||
|
|
@ -296,17 +270,30 @@ func TestSimpleHttp(t *testing.T) {
|
|||
AccountKey: accountKey,
|
||||
}
|
||||
|
||||
// NOTE: We do not attempt to shut down the server. The problem is that the
|
||||
// "wait-long" handler sleeps for ten seconds, but this test finishes in less
|
||||
// than that. So if we try to call hs.Close() at the end of the test, we'll be
|
||||
// closing the test server while a request is still pending. Unfortunately,
|
||||
// there appears to be an issue in httptest that trips Go's race detector when
|
||||
// that happens, failing the test. So instead, we live with leaving the server
|
||||
// around till the process exits.
|
||||
hs := simpleSrv(t, expectedToken, tls)
|
||||
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
|
||||
// Attempt to fail a challenge by telling the VA to connect to a port we are
|
||||
// not listening on.
|
||||
va.simpleHTTPPort = port + 1
|
||||
if va.simpleHTTPPort == 65536 {
|
||||
va.simpleHTTPPort = port - 1
|
||||
}
|
||||
invalidChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Server's not up yet; expected refusal. Where did we connect?")
|
||||
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
|
||||
stopChan := make(chan bool, 1)
|
||||
waitChan := make(chan bool, 1)
|
||||
go simpleSrv(t, expectedToken, stopChan, waitChan, tls)
|
||||
defer func() { stopChan <- true }()
|
||||
<-waitChan
|
||||
|
||||
va.simpleHTTPPort = port
|
||||
log.Clear()
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
|
|
@ -352,12 +339,10 @@ func TestSimpleHttp(t *testing.T) {
|
|||
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
|
||||
|
||||
va.TestMode = false
|
||||
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)
|
||||
va.TestMode = true
|
||||
|
||||
chall.Token = "wait-long"
|
||||
started := time.Now()
|
||||
|
|
@ -372,7 +357,7 @@ func TestSimpleHttp(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSimpleHttpRedirectLookup(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
tls := false
|
||||
|
|
@ -383,11 +368,11 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
|
|||
AccountKey: accountKey,
|
||||
}
|
||||
|
||||
stopChan := make(chan bool, 1)
|
||||
waitChan := make(chan bool, 1)
|
||||
go simpleSrv(t, expectedToken, stopChan, waitChan, tls)
|
||||
defer func() { stopChan <- true }()
|
||||
<-waitChan
|
||||
hs := simpleSrv(t, expectedToken, tls)
|
||||
defer hs.Close()
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
va.simpleHTTPPort = port
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathMoved
|
||||
|
|
@ -435,7 +420,7 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSimpleHttpRedirectLoop(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
tls := false
|
||||
|
|
@ -445,11 +430,11 @@ func TestSimpleHttpRedirectLoop(t *testing.T) {
|
|||
ValidationRecord: []core.ValidationRecord{},
|
||||
}
|
||||
|
||||
stopChan := make(chan bool, 1)
|
||||
waitChan := make(chan bool, 1)
|
||||
go simpleSrv(t, expectedToken, stopChan, waitChan, tls)
|
||||
defer func() { stopChan <- true }()
|
||||
<-waitChan
|
||||
hs := simpleSrv(t, expectedToken, tls)
|
||||
defer hs.Close()
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
va.simpleHTTPPort = port
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
|
|
@ -458,24 +443,33 @@ func TestSimpleHttpRedirectLoop(t *testing.T) {
|
|||
fmt.Println(finChall)
|
||||
}
|
||||
|
||||
func TestDvsni(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
func getPort(hs *httptest.Server) (int, error) {
|
||||
url, err := url.Parse(hs.URL)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, portString, err := net.SplitHostPort(url.Host)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
port, err := strconv.ParseInt(portString, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
func TestDvsni(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
|
||||
log.Clear()
|
||||
invalidChall, err := va.validateDvsni(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Server's not up yet; expected refusal. Where did we connect?")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
hs := dvsniSrv(t, chall)
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
|
||||
waitChan := make(chan bool, 1)
|
||||
stopChan := make(chan bool, 1)
|
||||
go dvsniSrv(t, chall, stopChan, waitChan)
|
||||
defer func() { stopChan <- true }()
|
||||
<-waitChan
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.dvsniPort = port
|
||||
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateDvsni(ident, chall)
|
||||
|
|
@ -484,20 +478,20 @@ func TestDvsni(t *testing.T) {
|
|||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
|
||||
log.Clear()
|
||||
invalidChall, err = va.validateDvsni(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall)
|
||||
invalidChall, err := va.validateDvsni(core.AcmeIdentifier{
|
||||
Type: core.IdentifierType("ip"),
|
||||
Value: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)),
|
||||
}, 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)
|
||||
|
||||
log.Clear()
|
||||
va.TestMode = false
|
||||
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 is invalid.")
|
||||
test.AssertError(t, err, "Domain name was supposed to be invalid.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
|
||||
|
||||
va.TestMode = true
|
||||
|
||||
// Need to re-sign to get an unknown SNI (from the signature value)
|
||||
chall.Token = core.NewToken()
|
||||
validationPayload, _ := json.Marshal(map[string]interface{}{
|
||||
|
|
@ -518,18 +512,25 @@ func TestDvsni(t *testing.T) {
|
|||
test.AssertError(t, err, "Connection should've timed out")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.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.
|
||||
hs.Close()
|
||||
invalidChall, err = va.validateDvsni(ident, chall, AccountKey)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestTLSError(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
waitChan := make(chan bool, 1)
|
||||
stopChan := make(chan bool, 1)
|
||||
go brokenTLSSrv(t, stopChan, waitChan)
|
||||
defer func() { stopChan <- true }()
|
||||
<-waitChan
|
||||
hs := brokenTLSSrv()
|
||||
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
va.dvsniPort = port
|
||||
|
||||
invalidChall, err := va.validateDvsni(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
|
|
@ -538,7 +539,7 @@ func TestTLSError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidateHTTP(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
@ -549,17 +550,11 @@ func TestValidateHTTP(t *testing.T) {
|
|||
challHTTP.ValidationRecord = []core.ValidationRecord{}
|
||||
challHTTP.AccountKey = accountKey
|
||||
|
||||
stopChanHTTP := make(chan bool, 1)
|
||||
waitChanHTTP := make(chan bool, 1)
|
||||
go simpleSrv(t, challHTTP.Token, stopChanHTTP, waitChanHTTP, tls)
|
||||
|
||||
// Let them start
|
||||
<-waitChanHTTP
|
||||
|
||||
// shutdown cleanly
|
||||
defer func() {
|
||||
stopChanHTTP <- true
|
||||
}()
|
||||
hs := simpleSrv(t, challHTTP.Token, tls)
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
va.simpleHTTPPort = port
|
||||
defer hs.Close()
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
|
|
@ -593,23 +588,18 @@ func createChallenge(challengeType string) core.Challenge {
|
|||
}
|
||||
|
||||
func TestValidateDvsni(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
waitChanDvsni := make(chan bool, 1)
|
||||
stopChanDvsni := make(chan bool, 1)
|
||||
go dvsniSrv(t, chall, stopChanDvsni, waitChanDvsni)
|
||||
hs := dvsniSrv(t, chall)
|
||||
defer hs.Close()
|
||||
|
||||
// Let them start
|
||||
<-waitChanDvsni
|
||||
|
||||
// shutdown cleanly
|
||||
defer func() {
|
||||
stopChanDvsni <- true
|
||||
}()
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
va.dvsniPort = port
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
|
|
@ -623,23 +613,12 @@ func TestValidateDvsni(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidateDvsniNotSane(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
waitChanDvsni := make(chan bool, 1)
|
||||
stopChanDvsni := make(chan bool, 1)
|
||||
go dvsniSrv(t, chall, stopChanDvsni, waitChanDvsni)
|
||||
|
||||
// Let them start
|
||||
<-waitChanDvsni
|
||||
|
||||
// shutdown cleanly
|
||||
defer func() {
|
||||
stopChanDvsni <- true
|
||||
}()
|
||||
|
||||
chall.Token = "not sane"
|
||||
|
||||
|
|
@ -655,7 +634,7 @@ func TestValidateDvsniNotSane(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateValidations(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
@ -665,18 +644,6 @@ func TestUpdateValidations(t *testing.T) {
|
|||
challHTTP.TLS = &tls
|
||||
challHTTP.ValidationRecord = []core.ValidationRecord{}
|
||||
|
||||
stopChanHTTP := make(chan bool, 1)
|
||||
waitChanHTTP := make(chan bool, 1)
|
||||
go simpleSrv(t, challHTTP.Token, stopChanHTTP, waitChanHTTP, tls)
|
||||
|
||||
// Let them start
|
||||
<-waitChanHTTP
|
||||
|
||||
// shutdown cleanly
|
||||
defer func() {
|
||||
stopChanHTTP <- true
|
||||
}()
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
RegistrationID: 1,
|
||||
|
|
@ -722,7 +689,7 @@ func TestCAAChecking(t *testing.T) {
|
|||
// CNAME to critical
|
||||
}
|
||||
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.IssuerDomain = "letsencrypt.org"
|
||||
for _, caaTest := range tests {
|
||||
|
|
@ -754,7 +721,7 @@ func TestCAAChecking(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDNSValidationFailure(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
@ -790,7 +757,7 @@ func TestDNSValidationInvalid(t *testing.T) {
|
|||
Challenges: []core.Challenge{chalDNS},
|
||||
}
|
||||
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
@ -803,7 +770,7 @@ func TestDNSValidationInvalid(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDNSValidationNotSane(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
@ -833,7 +800,7 @@ func TestDNSValidationNotSane(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDNSValidationServFail(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
@ -858,7 +825,7 @@ func TestDNSValidationServFail(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDNSValidationNoServer(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = core.NewDNSResolverImpl(time.Second*5, []string{})
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
|
|
|||
Loading…
Reference in New Issue