Merge master
This commit is contained in:
commit
be751bd948
|
@ -1,7 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.1
|
||||
- 1.5
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
|
@ -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,
|
||||
|
|
|
@ -12,51 +12,51 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/auth",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/config",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs11key",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs12",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs7",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/csr",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/errors",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/helpers",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/info",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/log",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/ocsp",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/signer",
|
||||
"Rev": "e46a042fbff1afcb445a5164d392ab2bf1b938be"
|
||||
"Rev": "190c5f9713ef6c1460fb31ee785044b43bdb1b09"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
|
|
|
@ -27,6 +27,20 @@ const OneYear = 8760 * time.Hour
|
|||
// OneDay is a time.Duration representing a day's worth of seconds.
|
||||
const OneDay = 24 * time.Hour
|
||||
|
||||
// InclusiveDate returns the time.Time representation of a date - 1
|
||||
// nanosecond. This allows time.After to be used inclusively.
|
||||
func InclusiveDate(year int, month time.Month, day int) time.Time {
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(-1 * time.Nanosecond)
|
||||
}
|
||||
|
||||
// Jul2012 is the July 2012 CAB Forum deadline for when CAs must stop
|
||||
// issuing certificates valid for more than 5 years.
|
||||
var Jul2012 = InclusiveDate(2012, time.July, 01)
|
||||
|
||||
// April2015 is the April 2015 CAB Forum deadline for when CAs must stop
|
||||
// issuing certificates valid for more than 39 months.
|
||||
var Apr2015 = InclusiveDate(2015, time.April, 01)
|
||||
|
||||
// KeyLength returns the bit size of ECDSA or RSA PublicKey
|
||||
func KeyLength(key interface{}) int {
|
||||
if key == nil {
|
||||
|
@ -55,6 +69,42 @@ func ExpiryTime(chain []*x509.Certificate) *time.Time {
|
|||
return ¬After
|
||||
}
|
||||
|
||||
// MonthsValid returns the number of months for which a certificate is valid.
|
||||
func MonthsValid(c *x509.Certificate) int {
|
||||
issued := c.NotBefore
|
||||
expiry := c.NotAfter
|
||||
years := (expiry.Year() - issued.Year())
|
||||
months := years*12 + int(expiry.Month()) - int(issued.Month())
|
||||
|
||||
// Round up if valid for less than a full month
|
||||
if expiry.Day() > issued.Day() {
|
||||
months++
|
||||
}
|
||||
return months
|
||||
}
|
||||
|
||||
// ValidExpiry determines if a certificate is valid for an acceptable
|
||||
// length of time per the CA/Browser Forum baseline requirements.
|
||||
// See https://cabforum.org/wp-content/uploads/CAB-Forum-BR-1.3.0.pdf
|
||||
func ValidExpiry(c *x509.Certificate) bool {
|
||||
issued := c.NotBefore
|
||||
|
||||
var maxMonths int
|
||||
switch {
|
||||
case issued.After(Apr2015):
|
||||
maxMonths = 39
|
||||
case issued.After(Jul2012):
|
||||
maxMonths = 60
|
||||
case issued.Before(Jul2012):
|
||||
maxMonths = 120
|
||||
}
|
||||
|
||||
if MonthsValid(c) > maxMonths {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SignatureString returns the TLS signature string corresponding to
|
||||
// an X509 signature algorithm.
|
||||
func SignatureString(alg x509.SignatureAlgorithm) string {
|
||||
|
@ -157,7 +207,6 @@ func ParseCertificatesDER(certsDER []byte, password string) ([]*x509.Certificate
|
|||
if err != nil {
|
||||
certs, err = x509.ParseCertificates(certsDER)
|
||||
if err != nil {
|
||||
//fmt.Println("\n\n\n\n\n\nCRITICALZONE\n\n\n\n\n\n\n\n\n\n")
|
||||
return nil, nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
|
||||
}
|
||||
} else {
|
||||
|
@ -166,7 +215,7 @@ func ParseCertificatesDER(certsDER []byte, password string) ([]*x509.Certificate
|
|||
}
|
||||
} else {
|
||||
if pkcs7data.ContentInfo != "SignedData" {
|
||||
return nil, nil, cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed, errors.New("Can only extract certificates from signed data content info"))
|
||||
return nil, nil, cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed, errors.New("can only extract certificates from signed data content info"))
|
||||
}
|
||||
certs = pkcs7data.Content.SignedData.Certificates
|
||||
}
|
||||
|
@ -200,9 +249,9 @@ func ParseCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
|
|||
} else if cert == nil {
|
||||
return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
|
||||
} else if len(rest) > 0 {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("The PEM file should contain only one object."))
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PEM file should contain only one object"))
|
||||
} else if len(cert) > 1 {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("The PKCS7 object in the PEM file should contain only one certificate"))
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PKCS7 object in the PEM file should contain only one certificate"))
|
||||
}
|
||||
return cert[0], nil
|
||||
}
|
||||
|
@ -225,7 +274,7 @@ func ParseOneCertificateFromPEM(certsPEM []byte) ([]*x509.Certificate, []byte, e
|
|||
return nil, rest, err
|
||||
}
|
||||
if pkcs7data.ContentInfo != "SignedData" {
|
||||
return nil, rest, errors.New("Only PKCS #7 Signed Data Content Info supported for certificate parsing")
|
||||
return nil, rest, errors.New("only PKCS #7 Signed Data Content Info supported for certificate parsing")
|
||||
}
|
||||
certs := pkcs7data.Content.SignedData.Certificates
|
||||
if certs == nil {
|
||||
|
|
|
@ -111,7 +111,58 @@ func TestExpiryTime(t *testing.T) {
|
|||
if *out != expected {
|
||||
t.Fatalf("Expected %v, got %v", expected, *out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonthsValid(t *testing.T) {
|
||||
var cert = &x509.Certificate{
|
||||
NotBefore: time.Date(2015, time.April, 01, 0, 0, 0, 0, time.UTC),
|
||||
NotAfter: time.Date(2015, time.April, 01, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
if MonthsValid(cert) != 0 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
cert.NotAfter = time.Date(2016, time.April, 01, 0, 0, 0, 0, time.UTC)
|
||||
if MonthsValid(cert) != 12 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// extra days should be rounded up to 1 month
|
||||
cert.NotAfter = time.Date(2016, time.April, 02, 0, 0, 0, 0, time.UTC)
|
||||
if MonthsValid(cert) != 13 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func HasValidExpiry(t *testing.T) {
|
||||
// Issue period > April 1, 2015
|
||||
var cert = &x509.Certificate{
|
||||
NotBefore: time.Date(2015, time.April, 01, 0, 0, 0, 0, time.UTC),
|
||||
NotAfter: time.Date(2016, time.April, 01, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
if !ValidExpiry(cert) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
cert.NotAfter = time.Date(2019, time.April, 01, 01, 0, 0, 0, time.UTC)
|
||||
if ValidExpiry(cert) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Issue period < July 1, 2012
|
||||
cert.NotBefore = time.Date(2009, time.March, 01, 0, 0, 0, 0, time.UTC)
|
||||
if ValidExpiry(cert) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Issue period July 1, 2012 - April 1, 2015
|
||||
cert.NotBefore = time.Date(2012, time.July, 01, 0, 0, 0, 0, time.UTC)
|
||||
cert.NotAfter = time.Date(2017, time.July, 01, 0, 0, 0, 0, time.UTC)
|
||||
if !ValidExpiry(cert) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashAlgoString(t *testing.T) {
|
||||
|
|
|
@ -8,6 +8,7 @@ package log
|
|||
import (
|
||||
"fmt"
|
||||
golog "log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// The following constants represent logging levels in increasing levels of seriousness.
|
||||
|
@ -17,6 +18,7 @@ const (
|
|||
LevelWarning
|
||||
LevelError
|
||||
LevelCritical
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
var levelPrefix = [...]string{
|
||||
|
@ -25,6 +27,7 @@ var levelPrefix = [...]string{
|
|||
LevelWarning: "[WARNING] ",
|
||||
LevelError: "[ERROR] ",
|
||||
LevelCritical: "[CRITICAL] ",
|
||||
LevelFatal: "[FATAL] ",
|
||||
}
|
||||
|
||||
// Level stores the current logging level.
|
||||
|
@ -42,6 +45,19 @@ func output(l int, v []interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Fatalf logs a formatted message at the "fatal" level and then exits. The
|
||||
// arguments are handled in the same manner as fmt.Printf.
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
outputf(LevelFatal, format, v)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatal logs its arguments at the "fatal" level and then exits.
|
||||
func Fatal(v ...interface{}) {
|
||||
output(LevelFatal, v)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Criticalf logs a formatted message at the "critical" level. The
|
||||
// arguments are handled in the same manner as fmt.Printf.
|
||||
func Criticalf(format string, v ...interface{}) {
|
||||
|
|
|
@ -118,8 +118,8 @@ func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
|
|||
return nil, cferr.New(cferr.OCSPError, cferr.IssuerMismatch)
|
||||
}
|
||||
|
||||
// Round thisUpdate times to the nearest hour
|
||||
thisUpdate := time.Now().Round(time.Hour)
|
||||
// Round thisUpdate times down to the nearest hour
|
||||
thisUpdate := time.Now().Truncate(time.Hour)
|
||||
nextUpdate := thisUpdate.Add(s.interval)
|
||||
|
||||
status, ok := statusCode[req.Status]
|
||||
|
|
2
Makefile
2
Makefile
|
@ -9,7 +9,7 @@ VERSION ?= 1.0.0
|
|||
EPOCH ?= 1
|
||||
MAINTAINER ?= "Community"
|
||||
|
||||
OBJECTS = $(shell find ./cmd -type d -maxdepth 1 -mindepth 1 | xargs basename)
|
||||
OBJECTS = $(shell find ./cmd -maxdepth 1 -mindepth 1 -type d -exec basename '{}' \;)
|
||||
|
||||
# Build environment variables (referencing core/util.go)
|
||||
COMMIT_ID = $(shell git rev-parse --short HEAD)
|
||||
|
|
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
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
ocspConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp/config"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
"github.com/letsencrypt/boulder/sa/satest"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
|
@ -333,22 +334,32 @@ var FarPast = time.Date(1950, 1, 1, 0, 0, 0, 0, time.UTC)
|
|||
|
||||
var log = mocks.UseMockLog()
|
||||
|
||||
var exPA = cmd.PAConfig{
|
||||
DBConnect: paDBConnStr,
|
||||
}
|
||||
|
||||
// CFSSL config
|
||||
const profileName = "ee"
|
||||
const caKeyFile = "../test/test-ca.key"
|
||||
const caCertFile = "../test/test-ca.pem"
|
||||
|
||||
const issuerCert = "../test/test-ca.pem"
|
||||
const (
|
||||
paDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_pa_test"
|
||||
caDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_ca_test"
|
||||
saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
|
||||
)
|
||||
|
||||
// TODO(jmhodges): change this to boulder_ca_test database
|
||||
var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
var exPA = cmd.PAConfig{
|
||||
DBConnect: dbConnStr,
|
||||
type testCtx struct {
|
||||
caDB core.CertificateAuthorityDatabase
|
||||
sa core.StorageAuthority
|
||||
caConfig cmd.CAConfig
|
||||
reg core.Registration
|
||||
cleanUp func()
|
||||
}
|
||||
|
||||
func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthority, cmd.CAConfig, 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)
|
||||
}
|
||||
|
@ -356,22 +367,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 := cmd.CAConfig{
|
||||
Profile: profileName,
|
||||
|
@ -412,38 +417,38 @@ func setup(t *testing.T) (core.CertificateAuthorityDatabase, core.StorageAuthori
|
|||
},
|
||||
},
|
||||
OCSP: &ocspConfig.Config{
|
||||
CACertFile: issuerCert,
|
||||
ResponderCertFile: issuerCert,
|
||||
CACertFile: caCertFile,
|
||||
ResponderCertFile: caCertFile,
|
||||
KeyFile: caKeyFile,
|
||||
},
|
||||
},
|
||||
}
|
||||
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, issuerCert, exPA)
|
||||
ctx.caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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, exPA)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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
|
||||
|
@ -454,7 +459,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)
|
||||
|
@ -464,11 +469,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, exPA)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.SA = storageAuthority
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
/*
|
||||
|
@ -486,7 +491,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
|
||||
|
@ -527,12 +532,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")
|
||||
|
@ -541,48 +546,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, exPA)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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, exPA)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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, exPA)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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
|
||||
|
@ -602,17 +607,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, exPA)
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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
|
||||
|
@ -624,29 +629,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, exPA)
|
||||
ca.SA = storageAuthority
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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, exPA)
|
||||
ca.SA = storageAuthority
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile, exPA)
|
||||
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.")
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ func startMonitor(rpcCh *amqp.Channel, logger *blog.AuditLogger, stats statsd.St
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("activity-monitor")
|
||||
app := cmd.NewAppShell("activity-monitor", "RPC activity monitor")
|
||||
|
||||
app.Action = func(c cmd.Config) {
|
||||
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
|
||||
|
|
|
@ -143,12 +143,13 @@ func revokeByReg(regID int64, reasonCode int, deny bool, cac rpc.CertificateAuth
|
|||
return
|
||||
}
|
||||
|
||||
var version = "0.0.1"
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "admin-revoker"
|
||||
app.Version = version
|
||||
app.Usage = "Revokes issued certificates"
|
||||
app.Version = cmd.Version()
|
||||
app.Author = "Boulder contributors"
|
||||
app.Email = "ca-dev@letsencrypt.org"
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-ca")
|
||||
app := cmd.NewAppShell("boulder-ca", "Handles issuance operations")
|
||||
app.Action = func(c cmd.Config) {
|
||||
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
|
||||
cmd.FailOnError(err, "Couldn't connect to statsd")
|
||||
|
@ -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, c.PA)
|
||||
cmd.FailOnError(err, "Failed to create CA impl")
|
||||
cai.MaxKeySize = c.Common.MaxKeySize
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-ra")
|
||||
app := cmd.NewAppShell("boulder-ra", "Handles service orchestration")
|
||||
app.Action = func(c cmd.Config) {
|
||||
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
|
||||
cmd.FailOnError(err, "Couldn't connect to statsd")
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-sa")
|
||||
app := cmd.NewAppShell("boulder-sa", "Handles SQL operations")
|
||||
app.Action = func(c cmd.Config) {
|
||||
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
|
||||
cmd.FailOnError(err, "Couldn't connect to statsd")
|
||||
|
@ -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) {}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-va")
|
||||
app := cmd.NewAppShell("boulder-va", "Handles challenge validation")
|
||||
app.Action = func(c cmd.Config) {
|
||||
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
|
||||
cmd.FailOnError(err, "Couldn't connect to statsd")
|
||||
|
|
|
@ -75,7 +75,7 @@ func HandlerTimer(handler http.Handler, stats statsd.Statter) http.Handler {
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-wfe")
|
||||
app := cmd.NewAppShell("boulder-wfe", "Handles HTTP API requests")
|
||||
addrFlag := cli.StringFlag{
|
||||
Name: "addr",
|
||||
Value: "",
|
||||
|
|
|
@ -206,7 +206,7 @@ func (ds durationSlice) Swap(a, b int) {
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("expiration-mailer")
|
||||
app := cmd.NewAppShell("expiration-mailer", "Sends certificate expiration emails")
|
||||
|
||||
app.App.Flags = append(app.App.Flags, cli.IntFlag{
|
||||
Name: "cert_limit",
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -119,7 +119,7 @@ func removeInvalidCerts(csvFilename string, dbMap *gorp.DbMap, stats statsd.Stat
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("external-cert-importer")
|
||||
app := cmd.NewAppShell("external-cert-importer", "Imports external certificates for POP checks")
|
||||
|
||||
app.App.Flags = append(app.App.Flags, cli.StringFlag{
|
||||
Name: "a, valid-certs-file",
|
||||
|
@ -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
|
||||
|
|
|
@ -98,7 +98,10 @@ func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool)
|
|||
log.Debug(fmt.Sprintf("Searching for OCSP issued by us for serial %s", serialString))
|
||||
|
||||
var ocspResponse core.OCSPResponse
|
||||
err := src.dbMap.SelectOne(&ocspResponse, "SELECT * from ocspResponses WHERE serial = :serial ORDER BY createdAt DESC LIMIT 1;",
|
||||
// Note: we order by id rather than createdAt, because otherwise we sometimes
|
||||
// get the wrong result if a certificate is revoked in the same second as its
|
||||
// last update (e.g. client issues and instant revokes).
|
||||
err := src.dbMap.SelectOne(&ocspResponse, "SELECT * from ocspResponses WHERE serial = :serial ORDER BY id DESC LIMIT 1;",
|
||||
map[string]interface{}{"serial": serialString})
|
||||
if err != nil {
|
||||
present = false
|
||||
|
@ -113,7 +116,7 @@ func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool)
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-ocsp-responder")
|
||||
app := cmd.NewAppShell("boulder-ocsp-responder", "Handles OCSP requests")
|
||||
app.Action = func(c cmd.Config) {
|
||||
// Set up logging
|
||||
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
|
||||
|
|
|
@ -184,7 +184,7 @@ func (updater *OCSPUpdater) findStaleResponses(oldestLastUpdatedTime time.Time,
|
|||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("ocsp-updater")
|
||||
app := cmd.NewAppShell("ocsp-updater", "Generates and updates OCSP responses")
|
||||
|
||||
app.App.Flags = append(app.App.Flags, cli.IntFlag{
|
||||
Name: "limit",
|
||||
|
|
14
cmd/shell.go
14
cmd/shell.go
|
@ -104,8 +104,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
SQL struct {
|
||||
CreateTables bool
|
||||
SQLDebug bool
|
||||
SQLDebug bool
|
||||
}
|
||||
|
||||
Statsd struct {
|
||||
|
@ -239,12 +238,19 @@ type AppShell struct {
|
|||
App *cli.App
|
||||
}
|
||||
|
||||
func Version() string {
|
||||
return fmt.Sprintf("0.1.0 [%s]", core.GetBuildID())
|
||||
}
|
||||
|
||||
// NewAppShell creates a basic AppShell object containing CLI metadata
|
||||
func NewAppShell(name string) (shell *AppShell) {
|
||||
func NewAppShell(name, usage string) (shell *AppShell) {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = name
|
||||
app.Version = fmt.Sprintf("0.1.0 [%s]", core.GetBuildID())
|
||||
app.Usage = usage
|
||||
app.Version = Version()
|
||||
app.Author = "Boulder contributors"
|
||||
app.Email = "ca-dev@letsencrypt.org"
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
|
|
64
core/dns.go
64
core/dns.go
|
@ -15,7 +15,26 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DNSResolverImpl represents a resolver system
|
||||
var (
|
||||
// Private CIDRs to ignore per RFC1918
|
||||
// 10.0.0.0/8
|
||||
rfc1918_10 = net.IPNet{
|
||||
IP: []byte{10, 0, 0, 0},
|
||||
Mask: []byte{255, 0, 0, 0},
|
||||
}
|
||||
// 172.16.0.0/12
|
||||
rfc1918_172_16 = net.IPNet{
|
||||
IP: []byte{172, 16, 0, 0},
|
||||
Mask: []byte{255, 240, 0, 0},
|
||||
}
|
||||
// 192.168.0.0/16
|
||||
rfc1918_192_168 = net.IPNet{
|
||||
IP: []byte{192, 168, 0, 0},
|
||||
Mask: []byte{255, 255, 0, 0},
|
||||
}
|
||||
)
|
||||
|
||||
// DNSResolverImpl represents a client that talks to an external resolver
|
||||
type DNSResolverImpl struct {
|
||||
DNSClient *dns.Client
|
||||
Servers []string
|
||||
|
@ -78,47 +97,34 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
|
|||
return txt, rtt, err
|
||||
}
|
||||
|
||||
// LookupHost sends a DNS query to find all A/AAAA records associated with
|
||||
// the provided hostname.
|
||||
func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, time.Duration, error) {
|
||||
var addrs []net.IP
|
||||
var answers []dns.RR
|
||||
func isPrivateV4(ip net.IP) bool {
|
||||
return rfc1918_10.Contains(ip) || rfc1918_172_16.Contains(ip) || rfc1918_192_168.Contains(ip)
|
||||
}
|
||||
|
||||
r, aRtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeA)
|
||||
// LookupHost sends a DNS query to find all A records associated with the provided
|
||||
// hostname. This method assumes that the external resolver will chase CNAME/DNAME
|
||||
// aliases and return relevant A records.
|
||||
func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
|
||||
var addrs []net.IP
|
||||
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeA)
|
||||
if err != nil {
|
||||
return addrs, 0, 0, err
|
||||
return addrs, rtt, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for A query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return nil, aRtt, 0, err
|
||||
return nil, rtt, err
|
||||
}
|
||||
|
||||
answers = append(answers, r.Answer...)
|
||||
|
||||
r, aaaaRtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeAAAA)
|
||||
if err != nil {
|
||||
return addrs, aRtt, 0, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for AAAA query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return nil, aRtt, aaaaRtt, err
|
||||
}
|
||||
|
||||
answers = append(answers, r.Answer...)
|
||||
|
||||
for _, answer := range answers {
|
||||
for _, answer := range r.Answer {
|
||||
if answer.Header().Rrtype == dns.TypeA {
|
||||
if a, ok := answer.(*dns.A); ok {
|
||||
if a, ok := answer.(*dns.A); ok && a.A.To4() != nil && !isPrivateV4(a.A) {
|
||||
addrs = append(addrs, a.A)
|
||||
}
|
||||
} else if answer.Header().Rrtype == dns.TypeAAAA {
|
||||
if aaaa, ok := answer.(*dns.AAAA); ok {
|
||||
addrs = append(addrs, aaaa.AAAA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addrs, aRtt, aaaaRtt, nil
|
||||
return addrs, rtt, nil
|
||||
}
|
||||
|
||||
// LookupCNAME returns the target name if a CNAME record exists for
|
||||
|
|
|
@ -47,6 +47,13 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) {
|
|||
record.Expire = 1
|
||||
record.Minttl = 1
|
||||
appendAnswer(record)
|
||||
case dns.TypeAAAA:
|
||||
if q.Name == "v6.letsencrypt.org." {
|
||||
record := new(dns.AAAA)
|
||||
record.Hdr = dns.RR_Header{Name: "v6.letsencrypt.org.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0}
|
||||
record.AAAA = net.ParseIP("::1")
|
||||
appendAnswer(record)
|
||||
}
|
||||
case dns.TypeA:
|
||||
if q.Name == "cps.letsencrypt.org." {
|
||||
record := new(dns.A)
|
||||
|
@ -166,7 +173,7 @@ func TestDNSLookupsNoServer(t *testing.T) {
|
|||
_, _, err := obj.LookupTXT("letsencrypt.org")
|
||||
test.AssertError(t, err, "No servers")
|
||||
|
||||
_, _, _, err = obj.LookupHost("letsencrypt.org")
|
||||
_, _, err = obj.LookupHost("letsencrypt.org")
|
||||
test.AssertError(t, err, "No servers")
|
||||
|
||||
_, _, err = obj.LookupCNAME("letsencrypt.org")
|
||||
|
@ -186,7 +193,7 @@ func TestDNSServFail(t *testing.T) {
|
|||
_, _, err = obj.LookupCNAME(bad)
|
||||
test.AssertError(t, err, "LookupCNAME didn't return an error")
|
||||
|
||||
_, _, _, err = obj.LookupHost(bad)
|
||||
_, _, err = obj.LookupHost(bad)
|
||||
test.AssertError(t, err, "LookupHost didn't return an error")
|
||||
|
||||
// CAA lookup ignores validation failures from the resolver for now
|
||||
|
@ -213,20 +220,31 @@ func TestDNSLookupTXT(t *testing.T) {
|
|||
func TestDNSLookupHost(t *testing.T) {
|
||||
obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
|
||||
|
||||
ip, _, _, err := obj.LookupHost("servfail.com")
|
||||
ip, _, err := obj.LookupHost("servfail.com")
|
||||
t.Logf("servfail.com - IP: %s, Err: %s", ip, err)
|
||||
test.AssertError(t, err, "Server failure")
|
||||
test.Assert(t, len(ip) == 0, "Should not have IPs")
|
||||
|
||||
ip, _, _, err = obj.LookupHost("nonexistent.letsencrypt.org")
|
||||
ip, _, err = obj.LookupHost("nonexistent.letsencrypt.org")
|
||||
t.Logf("nonexistent.letsencrypt.org - IP: %s, Err: %s", ip, err)
|
||||
test.AssertNotError(t, err, "Not an error to not exist")
|
||||
test.Assert(t, len(ip) == 0, "Should not have IPs")
|
||||
|
||||
ip, _, _, err = obj.LookupHost("cps.letsencrypt.org")
|
||||
// Single IPv4 address
|
||||
ip, _, err = obj.LookupHost("cps.letsencrypt.org")
|
||||
t.Logf("cps.letsencrypt.org - IP: %s, Err: %s", ip, err)
|
||||
test.AssertNotError(t, err, "Not an error to exist")
|
||||
test.Assert(t, len(ip) > 0, "Should have IPs")
|
||||
test.Assert(t, len(ip) == 1, "Should have IP")
|
||||
ip, _, err = obj.LookupHost("cps.letsencrypt.org")
|
||||
t.Logf("cps.letsencrypt.org - IP: %s, Err: %s", ip, err)
|
||||
test.AssertNotError(t, err, "Not an error to exist")
|
||||
test.Assert(t, len(ip) == 1, "Should have IP")
|
||||
|
||||
// No IPv6
|
||||
ip, _, err = obj.LookupHost("v6.letsencrypt.org")
|
||||
t.Logf("v6.letsencrypt.org - IP: %s, Err: %s", ip, err)
|
||||
test.AssertNotError(t, err, "Not an error to exist")
|
||||
test.Assert(t, len(ip) == 0, "Should not have IPs")
|
||||
}
|
||||
|
||||
func TestDNSLookupCAA(t *testing.T) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
@ -143,7 +142,7 @@ type CertificateAuthorityDatabase interface {
|
|||
type DNSResolver interface {
|
||||
ExchangeOne(string, uint16) (*dns.Msg, time.Duration, error)
|
||||
LookupTXT(string) ([]string, time.Duration, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, time.Duration, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, error)
|
||||
LookupCNAME(string) (string, time.Duration, error)
|
||||
LookupDNAME(string) (string, time.Duration, error)
|
||||
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
|
||||
|
|
|
@ -222,6 +222,19 @@ func (r *Registration) MergeUpdate(input Registration) {
|
|||
}
|
||||
}
|
||||
|
||||
// ValidationRecord represents a validation attempt against a specific URL/hostname
|
||||
// and the IP addresses that were resolved and used
|
||||
type ValidationRecord struct {
|
||||
// SimpleHTTP only
|
||||
URL string `json:"url,omitempty"`
|
||||
|
||||
// Shared
|
||||
Hostname string `json:"hostname"`
|
||||
Port string `json:"port"`
|
||||
AddressesResolved []net.IP `json:"addressesResolved"`
|
||||
AddressUsed net.IP `json:"addressUsed"`
|
||||
}
|
||||
|
||||
// Challenge is an aggregate of all data needed for any challenges.
|
||||
//
|
||||
// Rather than define individual types for different types of
|
||||
|
@ -252,6 +265,43 @@ type Challenge struct {
|
|||
|
||||
// Used by dns and dvsni challenges
|
||||
Validation *jose.JsonWebSignature `json:"validation,omitempty"`
|
||||
|
||||
// Contains information about URLs used or redirected to and IPs resolved and
|
||||
// used
|
||||
ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"`
|
||||
}
|
||||
|
||||
// RecordsSane checks the sanity of a ValidationRecord object before sending it
|
||||
// back to the RA to be stored.
|
||||
func (ch Challenge) RecordsSane() bool {
|
||||
if ch.ValidationRecord == nil || len(ch.ValidationRecord) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch ch.Type {
|
||||
case ChallengeTypeSimpleHTTP:
|
||||
for _, rec := range ch.ValidationRecord {
|
||||
if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
|
||||
len(rec.AddressesResolved) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case ChallengeTypeDVSNI:
|
||||
if len(ch.ValidationRecord) > 1 {
|
||||
return false
|
||||
}
|
||||
if ch.ValidationRecord[0].URL != "" {
|
||||
return false
|
||||
}
|
||||
if ch.ValidationRecord[0].Hostname == "" || ch.ValidationRecord[0].Port == "" ||
|
||||
ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
|
||||
return false
|
||||
}
|
||||
case ChallengeTypeDNS:
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSane checks the sanity of a challenge object before issued to the client
|
||||
|
@ -300,7 +350,6 @@ func (ch Challenge) IsSane(completed bool) bool {
|
|||
if completed && ch.Validation == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -361,7 +410,7 @@ type Authorization struct {
|
|||
// in process, these are challenges to be fulfilled; for
|
||||
// final authorizations, they describe the evidence that
|
||||
// the server used in support of granting the authorization.
|
||||
Challenges []Challenge `json:"challenges,omitempty" db:"challenges"`
|
||||
Challenges []Challenge `json:"challenges,omitempty" db:"-"`
|
||||
|
||||
// The server may suggest combinations of challenges if it
|
||||
// requires more than one challenge to be completed.
|
||||
|
|
|
@ -7,6 +7,7 @@ package core
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
|
@ -39,7 +40,33 @@ func TestRegistrationUpdate(t *testing.T) {
|
|||
test.Assert(t, reg.Agreement == update.Agreement, "Agreement was not updated")
|
||||
}
|
||||
|
||||
func TestSanityCheck(t *testing.T) {
|
||||
func TestRecordSanityCheck(t *testing.T) {
|
||||
rec := []ValidationRecord{
|
||||
ValidationRecord{
|
||||
URL: "http://localhost/test",
|
||||
Hostname: "localhost",
|
||||
Port: "80",
|
||||
AddressesResolved: []net.IP{net.IP{127, 0, 0, 1}},
|
||||
AddressUsed: net.IP{127, 0, 0, 1},
|
||||
},
|
||||
}
|
||||
|
||||
chall := Challenge{Type: ChallengeTypeSimpleHTTP, ValidationRecord: rec}
|
||||
test.Assert(t, chall.RecordsSane(), "Record should be sane")
|
||||
chall.ValidationRecord[0].URL = ""
|
||||
test.Assert(t, !chall.RecordsSane(), "Record should not be sane")
|
||||
|
||||
chall = Challenge{Type: ChallengeTypeDVSNI, ValidationRecord: rec}
|
||||
chall.ValidationRecord[0].URL = ""
|
||||
test.Assert(t, chall.RecordsSane(), "Record should be sane")
|
||||
chall.ValidationRecord[0].Hostname = ""
|
||||
test.Assert(t, !chall.RecordsSane(), "Record should not be sane")
|
||||
|
||||
chall.ValidationRecord = append(chall.ValidationRecord, rec...)
|
||||
test.Assert(t, !chall.RecordsSane(), "Record should not be sane")
|
||||
}
|
||||
|
||||
func TestChallengeSanityCheck(t *testing.T) {
|
||||
types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI, ChallengeTypeDNS}
|
||||
for _, challengeType := range types {
|
||||
chall := Challenge{Type: challengeType, Status: StatusInvalid}
|
||||
|
@ -58,9 +85,26 @@ func TestSanityCheck(t *testing.T) {
|
|||
if challengeType == ChallengeTypeSimpleHTTP {
|
||||
tls := true
|
||||
chall.TLS = &tls
|
||||
chall.ValidationRecord = []ValidationRecord{ValidationRecord{
|
||||
URL: "",
|
||||
Hostname: "localhost",
|
||||
Port: "80",
|
||||
AddressesResolved: []net.IP{net.IP{127, 0, 0, 1}},
|
||||
AddressUsed: net.IP{127, 0, 0, 1},
|
||||
}}
|
||||
test.Assert(t, chall.IsSane(false), "IsSane should be true")
|
||||
} else if challengeType == ChallengeTypeDVSNI || challengeType == ChallengeTypeDNS {
|
||||
chall.Validation = new(jose.JsonWebSignature)
|
||||
if challengeType == ChallengeTypeDVSNI {
|
||||
chall.ValidationRecord = []ValidationRecord{ValidationRecord{
|
||||
Hostname: "localhost",
|
||||
Port: "80",
|
||||
AddressesResolved: []net.IP{net.IP{127, 0, 0, 1}},
|
||||
AddressUsed: net.IP{127, 0, 0, 1},
|
||||
}}
|
||||
} else {
|
||||
chall.ValidationRecord = []ValidationRecord{}
|
||||
}
|
||||
test.Assert(t, chall.IsSane(true), "IsSane should be true")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() );
|
|
@ -32,8 +32,12 @@ func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error)
|
|||
}
|
||||
|
||||
// LookupHost is a mock
|
||||
func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, time.Duration, error) {
|
||||
return nil, 0, 0, nil
|
||||
func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
|
||||
if hostname == "always.invalid" || hostname == "invalid.invalid" {
|
||||
return []net.IP{}, 0, nil
|
||||
}
|
||||
ip := net.ParseIP("127.0.0.1")
|
||||
return []net.IP{ip}, 0, nil
|
||||
}
|
||||
|
||||
// LookupCNAME is a mock
|
||||
|
|
|
@ -178,6 +178,7 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
|
|||
RegistrationID: regID,
|
||||
Status: core.StatusPending,
|
||||
Combinations: combinations,
|
||||
Challenges: challenges,
|
||||
}
|
||||
|
||||
// Get a pending Auth first so we can get our ID back, then update with challenges
|
||||
|
@ -190,22 +191,19 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
|
|||
}
|
||||
|
||||
// Construct all the challenge URIs
|
||||
for i := range challenges {
|
||||
for i := range authz.Challenges {
|
||||
// Ignoring these errors because we construct the URLs to be correct
|
||||
challengeURI, _ := core.ParseAcmeURL(ra.AuthzBase + authz.ID + "?challenge=" + strconv.Itoa(i))
|
||||
challenges[i].URI = challengeURI
|
||||
authz.Challenges[i].URI = challengeURI
|
||||
|
||||
if !challenges[i].IsSane(false) {
|
||||
if !authz.Challenges[i].IsSane(false) {
|
||||
// InternalServerError because we generated these challenges, they should
|
||||
// be OK.
|
||||
err = core.InternalServerError(fmt.Sprintf("Challenge didn't pass sanity check: %+v", challenges[i]))
|
||||
err = core.InternalServerError(fmt.Sprintf("Challenge didn't pass sanity check: %+v", authz.Challenges[i]))
|
||||
return authz, err
|
||||
}
|
||||
}
|
||||
|
||||
// Update object
|
||||
authz.Challenges = challenges
|
||||
|
||||
// Store the authorization object, then return it
|
||||
err = ra.SA.UpdatePendingAuthorization(authz)
|
||||
if err != nil {
|
||||
|
|
|
@ -124,14 +124,18 @@ var (
|
|||
|
||||
log = mocks.UseMockLog()
|
||||
|
||||
// TODO(jmhodges): Turn this into boulder_sa_test
|
||||
dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_test"
|
||||
common = cmd.PAConfig{
|
||||
DBConnect: dbConnStr,
|
||||
common = cmd.PAConfig{
|
||||
DBConnect: paDBConnStr,
|
||||
}
|
||||
)
|
||||
|
||||
func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, func()) {
|
||||
const (
|
||||
paDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_pa_test"
|
||||
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)
|
||||
|
@ -145,7 +149,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)
|
||||
}
|
||||
|
@ -154,14 +158,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{}
|
||||
|
||||
|
@ -198,17 +195,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, err := NewRegistrationAuthorityImpl(common)
|
||||
|
@ -231,7 +224,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
|
||||
|
@ -241,7 +234,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)
|
||||
}
|
||||
|
@ -251,34 +244,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
|
||||
}
|
||||
|
||||
|
@ -333,7 +309,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{
|
||||
|
@ -358,7 +334,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{
|
||||
|
@ -386,7 +362,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{
|
||||
|
@ -399,12 +375,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
|
||||
|
@ -413,7 +389,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")
|
||||
|
||||
|
@ -427,7 +403,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()
|
||||
AuthzInitial, _ = sa.NewPendingAuthorization(AuthzInitial)
|
||||
sa.UpdatePendingAuthorization(AuthzInitial)
|
||||
|
@ -451,7 +427,7 @@ func TestUpdateAuthorization(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)
|
||||
|
@ -474,7 +450,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)
|
||||
|
@ -497,8 +473,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")
|
||||
|
||||
|
@ -506,7 +482,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)
|
||||
|
@ -526,9 +502,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)
|
||||
|
@ -543,7 +519,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
|
||||
|
|
|
@ -353,7 +353,10 @@ func (rpc *AmqpRPCServer) processMessage(msg amqp.Delivery) {
|
|||
rpc.log.Audit(fmt.Sprintf(" [s>][%s][%s] Error condition marshalling RPC response %s [%s]", rpc.serverQueue, msg.ReplyTo, msg.Type, msg.CorrelationId))
|
||||
return
|
||||
}
|
||||
rpc.log.Info(fmt.Sprintf(" [s>][%s][%s] replying %s(%s) [%s]", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(jsonResponse), msg.CorrelationId))
|
||||
if response.Error.Value != "" {
|
||||
rpc.log.Info(fmt.Sprintf(" [s>][%s][%s] %s failed, replying: %s (%s) [%s]", rpc.serverQueue, msg.ReplyTo, msg.Type, response.Error.Value, response.Error.Type, msg.CorrelationId))
|
||||
}
|
||||
rpc.log.Debug(fmt.Sprintf(" [s>][%s][%s] replying %s(%s) [%s]", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(jsonResponse), msg.CorrelationId))
|
||||
rpc.Channel.Publish(
|
||||
AmqpExchange,
|
||||
msg.ReplyTo,
|
||||
|
|
|
@ -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,
|
||||
|
@ -26,7 +19,6 @@ CREATE TABLE `authz` (
|
|||
`registrationID` bigint(20) DEFAULT NULL,
|
||||
`status` varchar(255) DEFAULT NULL,
|
||||
`expires` datetime DEFAULT NULL,
|
||||
`challenges` varchar(1536) DEFAULT NULL,
|
||||
`combinations` varchar(255) DEFAULT NULL,
|
||||
`sequence` bigint(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
|
@ -59,6 +51,22 @@ CREATE TABLE `certificateStatus` (
|
|||
PRIMARY KEY (`serial`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `challenges` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`authorizationID` varchar(255) NOT NULL,
|
||||
`LockCol` bigint(20) DEFAULT NULL,
|
||||
`type` varchar(255) NOT NULL,
|
||||
`status` varchar(255) NOT NULL,
|
||||
`error` mediumblob DEFAULT NULL,
|
||||
`validated` datetime DEFAULT NULL,
|
||||
`uri` varchar(255) DEFAULT NULL,
|
||||
`token` varchar(255) NOT NULL,
|
||||
`tls` tinyint(1) DEFAULT NULL,
|
||||
`validation` mediumblob,
|
||||
`validationRecord` mediumblob,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `crls` (
|
||||
`serial` varchar(255) NOT NULL,
|
||||
`createdAt` datetime NOT NULL,
|
||||
|
@ -87,7 +95,6 @@ CREATE TABLE `pending_authz` (
|
|||
`registrationID` bigint(20) DEFAULT NULL,
|
||||
`status` varchar(255) DEFAULT NULL,
|
||||
`expires` datetime DEFAULT NULL,
|
||||
`challenges` varchar(1536) DEFAULT NULL,
|
||||
`combinations` varchar(255) DEFAULT NULL,
|
||||
`LockCol` bigint(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
|
@ -113,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`;
|
|
@ -59,7 +59,7 @@ func NewDbMap(dbConnect string) (*gorp.DbMap, error) {
|
|||
// the mysql driver. Similarly, the driver needs the password and
|
||||
// username unescaped. Compromise by doing the leg work if the config
|
||||
// says the database URL's scheme is a fake one called
|
||||
// "mysqltcp://". See
|
||||
// "mysql+tcp://". See
|
||||
// https://github.com/go-sql-driver/mysql/issues/362 for why we have
|
||||
// to futz around and avoid URL.String.
|
||||
func recombineURLForDB(dbConnect string) (string, error) {
|
||||
|
@ -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 := ""
|
||||
|
@ -95,8 +104,7 @@ func recombineURLForDB(dbConnect string) (string, error) {
|
|||
dbConn += ":" + passwd
|
||||
}
|
||||
dbConn += "@tcp(" + dbURL.Host + ")"
|
||||
// TODO(jmhodges): should be dbURL.EscapedPath() but Travis doesn't have 1.5
|
||||
return dbConn + dbURL.Path + "?" + dsnVals.Encode(), nil
|
||||
return dbConn + dbURL.EscapedPath() + "?" + dsnVals.Encode(), nil
|
||||
}
|
||||
|
||||
// SetSQLDebug enables/disables GORP SQL-level Debugging
|
||||
|
@ -116,11 +124,10 @@ type SQLLogger struct {
|
|||
|
||||
// Printf adapts the AuditLogger to GORP's interface
|
||||
func (log *SQLLogger) Printf(format string, v ...interface{}) {
|
||||
log.log.Debug(fmt.Sprintf(format, v))
|
||||
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")
|
||||
|
@ -128,11 +135,8 @@ func initTables(dbMap *gorp.DbMap) {
|
|||
regTable.ColMap("KeySHA256").SetNotNull(true).SetUnique(true)
|
||||
pendingAuthzTable := dbMap.AddTableWithName(pendingauthzModel{}, "pending_authz").SetKeys(false, "ID")
|
||||
pendingAuthzTable.SetVersionCol("LockCol")
|
||||
pendingAuthzTable.ColMap("Challenges").SetMaxSize(1536)
|
||||
|
||||
authzTable := dbMap.AddTableWithName(authzModel{}, "authz").SetKeys(false, "ID")
|
||||
authzTable.ColMap("Challenges").SetMaxSize(1536)
|
||||
|
||||
dbMap.AddTableWithName(authzModel{}, "authz").SetKeys(false, "ID")
|
||||
dbMap.AddTableWithName(challModel{}, "challenges").SetKeys(true, "ID").SetVersionCol("LockCol")
|
||||
dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(false, "Serial")
|
||||
dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(false, "Serial").SetVersionCol("LockCol")
|
||||
dbMap.AddTableWithName(core.OCSPResponse{}, "ocspResponses").SetKeys(true, "ID")
|
||||
|
|
112
sa/model.go
112
sa/model.go
|
@ -1,13 +1,22 @@
|
|||
// 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/.
|
||||
|
||||
package sa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
)
|
||||
|
||||
var mediumBlobSize = int(math.Pow(2, 24))
|
||||
|
||||
// regModel is the description of a core.Registration in the database.
|
||||
type regModel struct {
|
||||
ID int64 `db:"id"`
|
||||
|
@ -18,6 +27,24 @@ type regModel struct {
|
|||
LockCol int64
|
||||
}
|
||||
|
||||
// challModel is the description of a core.Challenge in the database
|
||||
type challModel struct {
|
||||
ID int64 `db:"id"`
|
||||
AuthorizationID string `db:"authorizationID"`
|
||||
|
||||
Type string `db:"type"`
|
||||
Status core.AcmeStatus `db:"status"`
|
||||
Error []byte `db:"error"`
|
||||
Validated *time.Time `db:"validated"`
|
||||
URI string `db:"uri"`
|
||||
Token string `db:"token"`
|
||||
TLS *bool `db:"tls"`
|
||||
Validation []byte `db:"validation"`
|
||||
ValidationRecord []byte `db:"validationRecord"`
|
||||
|
||||
LockCol int64
|
||||
}
|
||||
|
||||
// newReg creates a reg model object from a core.Registration
|
||||
func registrationToModel(r *core.Registration) (*regModel, error) {
|
||||
key, err := json.Marshal(r.Key)
|
||||
|
@ -54,3 +81,88 @@ func modelToRegistration(rm *regModel) (core.Registration, error) {
|
|||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
|
||||
cm := challModel{
|
||||
AuthorizationID: authID,
|
||||
Type: c.Type,
|
||||
Status: c.Status,
|
||||
Validated: c.Validated,
|
||||
Token: c.Token,
|
||||
TLS: c.TLS,
|
||||
}
|
||||
if c.Validation != nil {
|
||||
cm.Validation = []byte(c.Validation.FullSerialize())
|
||||
if len(cm.Validation) > mediumBlobSize {
|
||||
return nil, fmt.Errorf("Validation object is too large to store in the database")
|
||||
}
|
||||
}
|
||||
if c.Error != nil {
|
||||
errJSON, err := json.Marshal(c.Error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(errJSON) > mediumBlobSize {
|
||||
return nil, fmt.Errorf("Error object is too large to store in the database")
|
||||
}
|
||||
cm.Error = errJSON
|
||||
}
|
||||
if c.URI != nil {
|
||||
if len(c.URI.String()) > 255 {
|
||||
return nil, fmt.Errorf("URI is too long to store in the database")
|
||||
}
|
||||
cm.URI = c.URI.String()
|
||||
}
|
||||
if len(c.ValidationRecord) > 0 {
|
||||
vrJSON, err := json.Marshal(c.ValidationRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(vrJSON) > mediumBlobSize {
|
||||
return nil, fmt.Errorf("Validation Record object is too large to store in the database")
|
||||
}
|
||||
cm.ValidationRecord = vrJSON
|
||||
}
|
||||
return &cm, nil
|
||||
}
|
||||
|
||||
func modelToChallenge(cm *challModel) (core.Challenge, error) {
|
||||
c := core.Challenge{
|
||||
Type: cm.Type,
|
||||
Status: cm.Status,
|
||||
Validated: cm.Validated,
|
||||
Token: cm.Token,
|
||||
TLS: cm.TLS,
|
||||
}
|
||||
if len(cm.URI) > 0 {
|
||||
uri, err := core.ParseAcmeURL(cm.URI)
|
||||
if err != nil {
|
||||
return core.Challenge{}, err
|
||||
}
|
||||
c.URI = uri
|
||||
}
|
||||
if len(cm.Validation) > 0 {
|
||||
val, err := jose.ParseSigned(string(cm.Validation))
|
||||
if err != nil {
|
||||
return core.Challenge{}, err
|
||||
}
|
||||
c.Validation = val
|
||||
}
|
||||
if len(cm.Error) > 0 {
|
||||
var problem core.ProblemDetails
|
||||
err := json.Unmarshal(cm.Error, &problem)
|
||||
if err != nil {
|
||||
return core.Challenge{}, err
|
||||
}
|
||||
c.Error = &problem
|
||||
}
|
||||
if len(cm.ValidationRecord) > 0 {
|
||||
var vr []core.ValidationRecord
|
||||
err := json.Unmarshal(cm.ValidationRecord, &vr)
|
||||
if err != nil {
|
||||
return core.Challenge{}, err
|
||||
}
|
||||
c.ValidationRecord = vr
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -22,6 +22,8 @@ import (
|
|||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
const getChallengesQuery = "SELECT * FROM challenges WHERE authorizationID = :authID ORDER BY id ASC"
|
||||
|
||||
// SQLStorageAuthority defines a Storage Authority
|
||||
type SQLStorageAuthority struct {
|
||||
dbMap *gorp.DbMap
|
||||
|
@ -67,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
|
||||
}
|
||||
|
@ -95,6 +91,33 @@ func existingRegistration(tx *gorp.Transaction, id int64) bool {
|
|||
return count > 0
|
||||
}
|
||||
|
||||
func updateChallenges(authID string, challenges []core.Challenge, tx *gorp.Transaction) error {
|
||||
var challs []challModel
|
||||
_, err := tx.Select(
|
||||
&challs,
|
||||
getChallengesQuery,
|
||||
map[string]interface{}{"authID": authID},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(challs) != len(challenges) {
|
||||
return fmt.Errorf("Invalid number of challenges provided")
|
||||
}
|
||||
for i, authChall := range challenges {
|
||||
chall, err := challengeToModel(&authChall, challs[i].AuthorizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chall.ID = challs[i].ID
|
||||
_, err = tx.Update(chall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NoSuchRegistrationError struct {
|
||||
Msg string
|
||||
}
|
||||
|
@ -152,7 +175,10 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
|
|||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
if authObj == nil {
|
||||
if authObj != nil {
|
||||
authD := *authObj.(*pendingauthzModel)
|
||||
authz = authD.Authorization
|
||||
} else {
|
||||
authObj, err = tx.Get(authzModel{}, id)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
@ -165,29 +191,49 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
|
|||
}
|
||||
authD := authObj.(*authzModel)
|
||||
authz = authD.Authorization
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
var challObjs []challModel
|
||||
_, err = tx.Select(
|
||||
&challObjs,
|
||||
getChallengesQuery,
|
||||
map[string]interface{}{"authID": authz.ID},
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
authD := *authObj.(*pendingauthzModel)
|
||||
authz = authD.Authorization
|
||||
var challs []core.Challenge
|
||||
for _, c := range challObjs {
|
||||
chall, err := modelToChallenge(&c)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return core.Authorization{}, err
|
||||
}
|
||||
challs = append(challs, chall)
|
||||
}
|
||||
authz.Challenges = challs
|
||||
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
// Get the valid authorization with biggest expire date for a given domain and registrationId
|
||||
// GetLatestValidAuthorization gets the valid authorization with biggest expire date for a given domain and registrationId
|
||||
func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
ident, err := json.Marshal(identifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = ssa.dbMap.SelectOne(&authz, "SELECT id, identifier, registrationID, status, expires, challenges, combinations "+
|
||||
"FROM authz "+
|
||||
var auth core.Authorization
|
||||
err = ssa.dbMap.SelectOne(&auth, "SELECT id FROM authz "+
|
||||
"WHERE identifier = :identifier AND registrationID = :registrationId AND status = 'valid' "+
|
||||
"ORDER BY expires DESC LIMIT 1",
|
||||
map[string]interface{}{"identifier": string(ident), "registrationId": registrationId})
|
||||
return
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return ssa.GetAuthorization(auth.ID)
|
||||
}
|
||||
|
||||
// GetCertificateByShortSerial takes an id consisting of the first, sequential half of a
|
||||
|
@ -386,8 +432,22 @@ func (ssa *SQLStorageAuthority) NewPendingAuthorization(authz core.Authorization
|
|||
return
|
||||
}
|
||||
|
||||
for _, c := range authz.Challenges {
|
||||
chall, err := challengeToModel(&c, pendingAuthz.ID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return core.Authorization{}, err
|
||||
}
|
||||
err = tx.Insert(chall)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return core.Authorization{}, err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
output = pendingAuthz.Authorization
|
||||
output.Challenges = authz.Challenges
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -429,6 +489,12 @@ func (ssa *SQLStorageAuthority) UpdatePendingAuthorization(authz core.Authorizat
|
|||
return
|
||||
}
|
||||
|
||||
err = updateChallenges(authz.ID, authz.Challenges, tx)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
|
@ -484,6 +550,12 @@ func (ssa *SQLStorageAuthority) FinalizeAuthorization(authz core.Authorization)
|
|||
return
|
||||
}
|
||||
|
||||
err = updateChallenges(authz.ID, authz.Challenges, tx)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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,21 +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
|
||||
}
|
||||
|
||||
chall := core.SimpleHTTPChallenge()
|
||||
|
||||
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, Challenges: []core.Challenge{chall}, 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)
|
||||
|
||||
|
@ -190,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{})
|
||||
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
|
||||
|
@ -206,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
|
||||
|
@ -227,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
|
||||
|
@ -240,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
|
||||
|
@ -255,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
|
||||
|
@ -267,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
|
||||
|
@ -279,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)
|
||||
}
|
||||
|
@ -308,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")
|
||||
|
||||
|
@ -335,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")
|
||||
|
||||
|
@ -389,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"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python2.7
|
||||
import atexit
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
|
@ -11,7 +12,7 @@ import startservers
|
|||
|
||||
|
||||
class ExitStatus:
|
||||
OK, PythonFailure, NodeFailure, Error = range(4)
|
||||
OK, PythonFailure, NodeFailure, Error, OCSPFailure = range(5)
|
||||
|
||||
|
||||
class ProcInfo:
|
||||
|
@ -32,6 +33,34 @@ def die(status):
|
|||
exit_status = status
|
||||
sys.exit(exit_status)
|
||||
|
||||
def get_ocsp(certFile):
|
||||
openssl_ocsp_cmd = ("""
|
||||
openssl x509 -in %s -out %s.pem -inform der -outform pem;
|
||||
openssl ocsp -no_nonce -issuer ../test-ca.pem -CAfile ../test-ca.pem -cert %s.pem -url http://localhost:4002
|
||||
""" % (certFile, certFile, certFile))
|
||||
try:
|
||||
print openssl_ocsp_cmd
|
||||
output = subprocess.check_output(openssl_ocsp_cmd, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
output = e.output
|
||||
print output
|
||||
print "OpenSSL returned non-zero: %s" % e
|
||||
die(ExitStatus.OCSPFailure)
|
||||
print output
|
||||
return output
|
||||
|
||||
def verify_ocsp_good(certFile):
|
||||
output = get_ocsp(certFile)
|
||||
if not re.search(": good", output):
|
||||
print "Expected OCSP response 'good', got something else."
|
||||
die(ExitStatus.OCSPFailure)
|
||||
|
||||
def verify_ocsp_revoked(certFile):
|
||||
output = get_ocsp(certFile)
|
||||
if not re.search(": revoked", output):
|
||||
print "Expected OCSP response 'revoked', got something else."
|
||||
die(ExitStatus.OCSPFailure)
|
||||
pass
|
||||
|
||||
def run_node_test():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
@ -46,19 +75,26 @@ def run_node_test():
|
|||
if subprocess.Popen('npm install', shell=True).wait() != 0:
|
||||
print("\n Installing NPM modules failed")
|
||||
die(ExitStatus.Error)
|
||||
certFile = os.path.join(tempdir, "cert.der")
|
||||
keyFile = os.path.join(tempdir, "key.pem")
|
||||
if subprocess.Popen('''
|
||||
node test.js --email foo@letsencrypt.org --agree true \
|
||||
--domains foo.com --new-reg http://localhost:4000/acme/new-reg \
|
||||
--certKey %s/key.pem --cert %s/cert.der
|
||||
''' % (tempdir, tempdir), shell=True).wait() != 0:
|
||||
--certKey %s --cert %s
|
||||
''' % (keyFile, certFile), shell=True).wait() != 0:
|
||||
print("\nIssuing failed")
|
||||
die(ExitStatus.NodeFailure)
|
||||
|
||||
verify_ocsp_good(certFile)
|
||||
|
||||
if subprocess.Popen('''
|
||||
node revoke.js %s/cert.der %s/key.pem http://localhost:4000/acme/revoke-cert
|
||||
''' % (tempdir, tempdir), shell=True).wait() != 0:
|
||||
node revoke.js %s %s http://localhost:4000/acme/revoke-cert
|
||||
''' % (certFile, keyFile), shell=True).wait() != 0:
|
||||
print("\nRevoking failed")
|
||||
die(ExitStatus.NodeFailure)
|
||||
|
||||
verify_ocsp_revoked(certFile)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
@ -80,7 +116,7 @@ def cleanup():
|
|||
if exit_status == ExitStatus.OK:
|
||||
print("\n\nSUCCESS")
|
||||
else:
|
||||
print("\n\nFAILURE")
|
||||
print("\n\nFAILURE %d" % exit_status)
|
||||
|
||||
|
||||
exit_status = ExitStatus.OK
|
||||
|
|
|
@ -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.",
|
||||
|
@ -71,10 +71,10 @@
|
|||
"backdate": "1h",
|
||||
"is_ca": false,
|
||||
"issuer_urls": [
|
||||
"http://int-x1.letsencrypt.org/cert"
|
||||
"http://localhost:4000/acme/issuer-cert"
|
||||
],
|
||||
"ocsp_url": "http://int-x1.letsencrypt.org/ocsp",
|
||||
"crl_url": "http://int-x1.letsencrypt.org/crl",
|
||||
"ocsp_url": "http://localhost:4002/ocsp",
|
||||
"crl_url": "http://example.com/crl",
|
||||
"policies": [
|
||||
{
|
||||
"ID": "2.23.140.1.2.1"
|
||||
|
@ -118,7 +118,7 @@
|
|||
},
|
||||
|
||||
"sa": {
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_test",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"debugAddr": "localhost:8003"
|
||||
},
|
||||
|
||||
|
@ -128,23 +128,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",
|
||||
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
|
||||
"path": "/",
|
||||
"listenAddress": "localhost:4001",
|
||||
"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 +157,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",
|
||||
|
@ -173,5 +172,5 @@
|
|||
"dnsTimeout": "10s"
|
||||
},
|
||||
|
||||
"subscriberAgreementURL": "http://localhost:4000/terms/v1"
|
||||
"subscriberAgreementURL": "http://localhost:4001/terms/v1"
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -19,8 +19,10 @@ function main() {
|
|||
console.log('Usage: js revoke.js cert.der key.pem REVOKE_URL');
|
||||
process.exit(1);
|
||||
}
|
||||
var certFile = process.argv[2];
|
||||
console.log('Attempting to revoke', certFile);
|
||||
var key = crypto.importPemPrivateKey(fs.readFileSync(process.argv[3]));
|
||||
var certDER = fs.readFileSync(process.argv[2])
|
||||
var certDER = fs.readFileSync(certFile)
|
||||
var revokeUrl = process.argv[4];
|
||||
var certDERB64URL = util.b64enc(new Buffer(certDER))
|
||||
var revokeMessage = JSON.stringify({
|
||||
|
@ -34,9 +36,8 @@ function main() {
|
|||
console.log(error);
|
||||
process.exit(1);
|
||||
} else if (response.statusCode != 200) {
|
||||
console.log("Got non-200 response: ", response.statusCode);
|
||||
console.log("Got non-200 response asking for nonce (non-fatal):", response.statusCode);
|
||||
}
|
||||
console.log(response.headers);
|
||||
var nonce = response.headers["replay-nonce"];
|
||||
if (!nonce) {
|
||||
console.log("Server HEAD response did not include a replay nonce");
|
||||
|
@ -45,16 +46,14 @@ function main() {
|
|||
|
||||
var jws = crypto.generateSignature(key, new Buffer(revokeMessage), nonce);
|
||||
var payload = JSON.stringify(jws);
|
||||
console.log(payload);
|
||||
|
||||
var req = request.post(revokeUrl, function(error, response) {
|
||||
if (error) {
|
||||
if (error || Math.floor(response.statusCode / 100) != 2) {
|
||||
console.log('Error: ', error);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("Revocation request successful.");
|
||||
}
|
||||
console.log(response.statusCode);
|
||||
console.log(response.headers);
|
||||
console.log(response.body);
|
||||
});
|
||||
req.write(payload);
|
||||
req.end();
|
||||
|
|
|
@ -62,6 +62,7 @@ def start(race_detection):
|
|||
'cmd/boulder-sa',
|
||||
'cmd/boulder-ca',
|
||||
'cmd/boulder-va',
|
||||
'cmd/ocsp-responder',
|
||||
'test/dns-test-srv']:
|
||||
try:
|
||||
processes.append(run(prog, race_detection))
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -29,6 +30,9 @@ import (
|
|||
)
|
||||
|
||||
const maxCNAME = 16 // Prevents infinite loops. Same limit as BIND.
|
||||
const maxRedirect = 10
|
||||
|
||||
var validationTimeout = time.Second * 5
|
||||
|
||||
// Returned by CheckCAARecords if it has to follow too many
|
||||
// consecutive CNAME lookups.
|
||||
|
@ -36,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
|
||||
|
@ -49,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
|
||||
|
@ -63,7 +85,6 @@ type verificationRequestEvent struct {
|
|||
}
|
||||
|
||||
func verifyValidationJWS(validation *jose.JsonWebSignature, accountKey *jose.JsonWebKey, target map[string]interface{}) error {
|
||||
|
||||
if len(validation.Signatures) > 1 {
|
||||
return fmt.Errorf("Too many signatures on validation JWS")
|
||||
}
|
||||
|
@ -98,25 +119,82 @@ func verifyValidationJWS(validation *jose.JsonWebSignature, accountKey *jose.Jso
|
|||
return nil
|
||||
}
|
||||
|
||||
// Validation methods
|
||||
|
||||
// setChallengeErrorFromDNSError checks the error returned from Lookup...
|
||||
// problemDetailsFromDNSError checks the error returned from Lookup...
|
||||
// methods and tests if the error was an underlying net.OpError or an error
|
||||
// caused by resolver returning SERVFAIL or other invalid Rcodes and sets
|
||||
// the challenge.Error field accordingly.
|
||||
func setChallengeErrorFromDNSError(err error, challenge *core.Challenge) {
|
||||
challenge.Error = &core.ProblemDetails{Type: core.ConnectionProblem}
|
||||
// caused by resolver returning SERVFAIL or other invalid Rcodes and returns
|
||||
// the relevant core.ProblemDetails.
|
||||
func problemDetailsFromDNSError(err error) *core.ProblemDetails {
|
||||
problem := &core.ProblemDetails{Type: core.ConnectionProblem}
|
||||
if netErr, ok := err.(*net.OpError); ok {
|
||||
if netErr.Timeout() {
|
||||
challenge.Error.Detail = "DNS query timed out"
|
||||
problem.Detail = "DNS query timed out"
|
||||
} else if netErr.Temporary() {
|
||||
challenge.Error.Detail = "Temporary network connectivity error"
|
||||
problem.Detail = "Temporary network connectivity error"
|
||||
}
|
||||
} else {
|
||||
challenge.Error.Detail = "Server failure at resolver"
|
||||
problem.Detail = "Server failure at resolver"
|
||||
}
|
||||
return problem
|
||||
}
|
||||
|
||||
// getAddr will query for all A records associated with hostname and return the
|
||||
// prefered address, the first net.IP in the addrs slice, and all addresses resolved.
|
||||
// This is the same choice made by the Go internal resolution library used by
|
||||
// net/http, except we only send A queries and accept IPv4 addresses.
|
||||
// TODO(#593): Add IPv6 support
|
||||
func (va ValidationAuthorityImpl) getAddr(hostname string) (addr net.IP, addrs []net.IP, problem *core.ProblemDetails) {
|
||||
addrs, _, err := va.DNSResolver.LookupHost(hostname)
|
||||
if err != nil {
|
||||
problem = problemDetailsFromDNSError(err)
|
||||
va.log.Debug(fmt.Sprintf("%s DNS failure: %s", hostname, err))
|
||||
return
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
problem = &core.ProblemDetails{
|
||||
Type: core.UnknownHostProblem,
|
||||
Detail: fmt.Sprintf("No IPv4 addresses found for %s", hostname),
|
||||
}
|
||||
return
|
||||
}
|
||||
addr = addrs[0]
|
||||
va.log.Info(fmt.Sprintf("Resolved addresses for %s [using %s]: %s", hostname, addr, addrs))
|
||||
return
|
||||
}
|
||||
|
||||
type dialer struct {
|
||||
record core.ValidationRecord
|
||||
}
|
||||
|
||||
func (d *dialer) Dial(_, _ string) (net.Conn, error) {
|
||||
realDialer := net.Dialer{Timeout: validationTimeout}
|
||||
return realDialer.Dial("tcp", net.JoinHostPort(d.record.AddressUsed.String(), d.record.Port))
|
||||
}
|
||||
|
||||
// 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 := fmt.Sprintf("%d", va.simpleHTTPPort)
|
||||
if defaultPort != "" {
|
||||
port = defaultPort
|
||||
}
|
||||
d := dialer{
|
||||
record: core.ValidationRecord{
|
||||
Hostname: name,
|
||||
Port: port,
|
||||
},
|
||||
}
|
||||
|
||||
addr, allAddrs, err := va.getAddr(name)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d.record.AddressesResolved = allAddrs
|
||||
d.record.AddressUsed = addr
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Validation methods
|
||||
|
||||
func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge, accountKey jose.JsonWebKey) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
|
@ -130,23 +208,29 @@ 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
|
||||
}
|
||||
if va.TestMode {
|
||||
hostName = "localhost:5001"
|
||||
}
|
||||
portString := fmt.Sprintf("%d", port)
|
||||
hostPort := net.JoinHostPort(host, portString)
|
||||
|
||||
url := fmt.Sprintf("%s://%s/.well-known/acme-challenge/%s", scheme, hostName, challenge.Token)
|
||||
url := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: hostPort,
|
||||
Path: fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token),
|
||||
}
|
||||
|
||||
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
|
||||
va.log.Audit(fmt.Sprintf("Attempting to validate Simple%s for %s", strings.ToUpper(scheme), url))
|
||||
httpRequest, err := http.NewRequest("GET", url, nil)
|
||||
httpRequest, err := http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.MalformedProblem,
|
||||
|
@ -161,7 +245,16 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
httpRequest.Header["User-Agent"] = []string{va.UserAgent}
|
||||
}
|
||||
|
||||
httpRequest.Host = hostName
|
||||
httpRequest.Host = hostPort
|
||||
dialer, prob := va.resolveAndConstructDialer(host, portString)
|
||||
dialer.record.URL = url.String()
|
||||
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
|
||||
if prob != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = prob
|
||||
return challenge, prob
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
// We are talking to a client that does not yet have a certificate,
|
||||
// so we accept a temporary, invalid one.
|
||||
|
@ -169,18 +262,51 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
// We don't expect to make multiple requests to a client, so close
|
||||
// connection immediately.
|
||||
DisableKeepAlives: true,
|
||||
// Intercept Dial in order to connect to the IP address we
|
||||
// select.
|
||||
Dial: dialer.Dial,
|
||||
}
|
||||
|
||||
logRedirect := func(req *http.Request, via []*http.Request) error {
|
||||
va.log.Info(fmt.Sprintf("validateSimpleHTTP [%s] redirect from %q to %q", identifier, via[len(via)-1].URL.String(), req.URL.String()))
|
||||
if len(challenge.ValidationRecord) >= maxRedirect {
|
||||
return fmt.Errorf("Too many redirects")
|
||||
}
|
||||
|
||||
reqHost := req.URL.Host
|
||||
reqPort := ""
|
||||
if strings.Contains(reqHost, ":") {
|
||||
splitHost := strings.SplitN(reqHost, ":", 2)
|
||||
if len(splitHost) <= 1 {
|
||||
return fmt.Errorf("Malformed host")
|
||||
}
|
||||
reqHost, reqPort = splitHost[0], splitHost[1]
|
||||
portNum, err := strconv.Atoi(reqPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if portNum < 0 || portNum > 65535 {
|
||||
return fmt.Errorf("Invalid port number in redirect")
|
||||
}
|
||||
} else if strings.ToLower(req.URL.Scheme) == "https" {
|
||||
reqPort = "443"
|
||||
}
|
||||
|
||||
dialer, err := va.resolveAndConstructDialer(reqHost, reqPort)
|
||||
dialer.record.URL = req.URL.String()
|
||||
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.Dial = dialer.Dial
|
||||
va.log.Info(fmt.Sprintf("validateSimpleHTTP [%s] redirect from %q to %q [%s]", identifier, via[len(via)-1].URL.String(), req.URL.String(), dialer.record.AddressUsed))
|
||||
return nil
|
||||
}
|
||||
client := http.Client{
|
||||
Transport: tr,
|
||||
CheckRedirect: logRedirect,
|
||||
Timeout: 5 * time.Second,
|
||||
Timeout: validationTimeout,
|
||||
}
|
||||
httpResponse, err := client.Do(httpRequest)
|
||||
|
||||
if err != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
|
@ -195,8 +321,8 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: fmt.Sprintf("Invalid response from %s: %d",
|
||||
url, httpResponse.StatusCode),
|
||||
Detail: fmt.Sprintf("Invalid response from %s [%s]: %d",
|
||||
url.String(), dialer.record.AddressUsed, httpResponse.StatusCode),
|
||||
}
|
||||
err = challenge.Error
|
||||
return challenge, err
|
||||
|
@ -288,14 +414,27 @@ func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
|
|||
Z := hex.EncodeToString(h.Sum(nil))
|
||||
ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.DVSNISuffix)
|
||||
|
||||
// Make a connection with SNI = nonceName
|
||||
hostPort := identifier.Value + ":443"
|
||||
if va.TestMode {
|
||||
hostPort = "localhost:5001"
|
||||
addr, allAddrs, problem := va.getAddr(identifier.Value)
|
||||
challenge.ValidationRecord = []core.ValidationRecord{
|
||||
core.ValidationRecord{
|
||||
Hostname: identifier.Value,
|
||||
AddressesResolved: allAddrs,
|
||||
AddressUsed: addr,
|
||||
},
|
||||
}
|
||||
if problem != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = problem
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
// Make a connection with SNI = nonceName
|
||||
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: 5 * time.Second}, "tcp", hostPort, &tls.Config{
|
||||
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{
|
||||
ServerName: ZName,
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
|
@ -396,7 +535,7 @@ func (va ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, in
|
|||
|
||||
if err != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
setChallengeErrorFromDNSError(err, &challenge)
|
||||
challenge.Error = problemDetailsFromDNSError(err)
|
||||
va.log.Debug(fmt.Sprintf("%s [%s] DNS failure: %s", challenge.Type, identifier, err))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
@ -419,9 +558,6 @@ func (va ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, in
|
|||
// Overall validation process
|
||||
|
||||
func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIndex int, accountKey jose.JsonWebKey) {
|
||||
|
||||
// Select the first supported validation method
|
||||
// XXX: Remove the "break" lines to process all supported validations
|
||||
logEvent := verificationRequestEvent{
|
||||
ID: authz.ID,
|
||||
Requester: authz.RegistrationID,
|
||||
|
@ -440,19 +576,22 @@ func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIn
|
|||
switch authz.Challenges[challengeIndex].Type {
|
||||
case core.ChallengeTypeSimpleHTTP:
|
||||
authz.Challenges[challengeIndex], err = va.validateSimpleHTTP(authz.Identifier, authz.Challenges[challengeIndex], accountKey)
|
||||
break
|
||||
case core.ChallengeTypeDVSNI:
|
||||
authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex], accountKey)
|
||||
break
|
||||
case core.ChallengeTypeDNS:
|
||||
authz.Challenges[challengeIndex], err = va.validateDNS(authz.Identifier, authz.Challenges[challengeIndex], accountKey)
|
||||
break
|
||||
}
|
||||
|
||||
logEvent.Challenge = authz.Challenges[challengeIndex]
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
} else if !authz.Challenges[challengeIndex].RecordsSane() {
|
||||
chall := &authz.Challenges[challengeIndex]
|
||||
chall.Status = core.StatusInvalid
|
||||
chall.Error = &core.ProblemDetails{Type: core.ServerInternalProblem,
|
||||
Detail: "Records for validation failed sanity check"}
|
||||
logEvent.Error = chall.Error.Detail
|
||||
}
|
||||
logEvent.Challenge = authz.Challenges[challengeIndex]
|
||||
}
|
||||
|
||||
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
|
||||
|
|
|
@ -20,6 +20,9 @@ import (
|
|||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -65,6 +68,9 @@ const pathWrongToken = "wrongtoken"
|
|||
const path404 = "404"
|
||||
const pathFound = "302"
|
||||
const pathMoved = "301"
|
||||
const pathRedirectLookup = "re-lookup"
|
||||
const pathRedirectLookupInvalid = "re-lookup-invalid"
|
||||
const pathRedirectPort = "port-redirect"
|
||||
|
||||
func createValidation(token string, enableTLS bool) string {
|
||||
payload, _ := json.Marshal(map[string]interface{}{
|
||||
|
@ -77,13 +83,16 @@ 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 !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) {
|
||||
t.Logf("SIMPLESRV: Got a 404 req\n")
|
||||
http.NotFound(w, r)
|
||||
|
@ -105,28 +114,32 @@ func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableT
|
|||
} else if strings.HasSuffix(r.URL.Path, "wait-long") {
|
||||
t.Logf("SIMPLESRV: Got a wait-long req\n")
|
||||
time.Sleep(time.Second * 10)
|
||||
} else if strings.HasSuffix(r.URL.Path, "re-lookup") {
|
||||
t.Logf("SIMPLESRV: Got a redirect req to a valid hostname\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = "re-lookup"
|
||||
}
|
||||
http.Redirect(w, r, "http://other.valid/path", 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, "re-lookup-invalid") {
|
||||
t.Logf("SIMPLESRV: Got a redirect req to a invalid hostname\n")
|
||||
http.Redirect(w, r, "http://invalid.invalid/path", 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, "looper") {
|
||||
t.Logf("SIMPLESRV: Got a loop req\n")
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) {
|
||||
t.Logf("SIMPLESRV: Got a port redirect req\n")
|
||||
http.Redirect(w, r, "http://other.valid:8080/path", 302)
|
||||
} else {
|
||||
t.Logf("SIMPLESRV: Got a valid req\n")
|
||||
fmt.Fprintf(w, "%s", createValidation(currentToken, enableTLS))
|
||||
fmt.Fprint(w, createValidation(currentToken, enableTLS))
|
||||
currentToken = defaultToken
|
||||
}
|
||||
})
|
||||
|
||||
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),
|
||||
|
@ -149,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))
|
||||
|
@ -201,52 +213,35 @@ 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{Type: core.ChallengeTypeSimpleHTTP, Token: expectedToken}
|
||||
chall := core.Challenge{Type: core.ChallengeTypeSimpleHTTP, Token: expectedToken, ValidationRecord: []core.ValidationRecord{}}
|
||||
|
||||
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, AccountKey)
|
||||
|
@ -258,23 +253,36 @@ func TestSimpleHttpTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSimpleHttp(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
tls := false
|
||||
chall := core.Challenge{Type: core.ChallengeTypeSimpleHTTP, Token: expectedToken, TLS: &tls}
|
||||
chall := core.Challenge{Type: core.ChallengeTypeSimpleHTTP, Token: expectedToken, TLS: &tls, ValidationRecord: []core.ValidationRecord{}}
|
||||
|
||||
// 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, AccountKey)
|
||||
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, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
|
@ -320,12 +328,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, AccountKey)
|
||||
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()
|
||||
|
@ -339,40 +345,133 @@ func TestSimpleHttp(t *testing.T) {
|
|||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
func TestDvsni(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(true)
|
||||
func TestSimpleHttpRedirectLookup(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
tls := false
|
||||
chall := core.Challenge{Token: expectedToken, TLS: &tls, ValidationRecord: []core.ValidationRecord{}}
|
||||
|
||||
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
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2)
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathFound
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3)
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathRedirectLookupInvalid
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`No IPv4 addresses found for invalid.invalid`)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathRedirectLookup
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/re-lookup" to ".*other.valid/path"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathRedirectPort
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall, AccountKey)
|
||||
fmt.Println(finChall.ValidationRecord)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/port-redirect" to ".*other.valid:8080/path"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
}
|
||||
|
||||
func TestSimpleHttpRedirectLoop(t *testing.T) {
|
||||
va := NewValidationAuthorityImpl(false)
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
tls := false
|
||||
chall := core.Challenge{Token: "looper", TLS: &tls, ValidationRecord: []core.ValidationRecord{}}
|
||||
|
||||
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, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, chall.Token)
|
||||
fmt.Println(finChall)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
invalidChall, err := va.validateDvsni(ident, chall, AccountKey)
|
||||
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)
|
||||
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, AccountKey)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "")
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
|
||||
invalidChall, err = va.validateDvsni(core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}, chall, AccountKey)
|
||||
log.Clear()
|
||||
invalidChall, err := va.validateDvsni(core.AcmeIdentifier{
|
||||
Type: core.IdentifierType("ip"),
|
||||
Value: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)),
|
||||
}, chall, AccountKey)
|
||||
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)
|
||||
|
||||
va.TestMode = false
|
||||
log.Clear()
|
||||
invalidChall, err = va.validateDvsni(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall, AccountKey)
|
||||
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{}{
|
||||
|
@ -382,6 +481,7 @@ func TestDvsni(t *testing.T) {
|
|||
signer, _ := jose.NewSigner(jose.RS256, &TheKey)
|
||||
chall.Validation, _ = signer.Sign(validationPayload, "")
|
||||
|
||||
log.Clear()
|
||||
started := time.Now()
|
||||
invalidChall, err = va.validateDvsni(ident, chall, AccountKey)
|
||||
took := time.Since(started)
|
||||
|
@ -391,18 +491,26 @@ func TestDvsni(t *testing.T) {
|
|||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Connection should've timed out")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
test.AssertEquals(t, 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, AccountKey)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
|
@ -411,7 +519,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
|
||||
|
@ -419,18 +527,13 @@ func TestValidateHTTP(t *testing.T) {
|
|||
tls := false
|
||||
challHTTP := core.SimpleHTTPChallenge()
|
||||
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
|
||||
}()
|
||||
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(),
|
||||
|
@ -446,9 +549,10 @@ func TestValidateHTTP(t *testing.T) {
|
|||
// challengeType == "dvsni" or "dns", since they're the same
|
||||
func createChallenge(challengeType string) core.Challenge {
|
||||
chall := core.Challenge{
|
||||
Type: challengeType,
|
||||
Status: core.StatusPending,
|
||||
Token: core.NewToken(),
|
||||
Type: challengeType,
|
||||
Status: core.StatusPending,
|
||||
Token: core.NewToken(),
|
||||
ValidationRecord: []core.ValidationRecord{},
|
||||
}
|
||||
|
||||
validationPayload, _ := json.Marshal(map[string]interface{}{
|
||||
|
@ -462,23 +566,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(),
|
||||
|
@ -492,23 +591,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"
|
||||
|
||||
|
@ -524,7 +612,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
|
||||
|
@ -532,18 +620,7 @@ func TestUpdateValidations(t *testing.T) {
|
|||
tls := false
|
||||
challHTTP := core.SimpleHTTPChallenge()
|
||||
challHTTP.TLS = &tls
|
||||
|
||||
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
|
||||
}()
|
||||
challHTTP.ValidationRecord = []core.ValidationRecord{}
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
|
@ -590,7 +667,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 {
|
||||
|
@ -622,7 +699,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
|
||||
|
@ -658,7 +735,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
|
||||
|
@ -671,7 +748,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
|
||||
|
@ -701,7 +778,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
|
||||
|
@ -726,7 +803,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
|
||||
|
|
|
@ -971,12 +971,11 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
|
|||
return
|
||||
}
|
||||
|
||||
// Blank out ID and regID
|
||||
switch request.Method {
|
||||
case "GET":
|
||||
// Blank out ID and regID
|
||||
authz.ID = ""
|
||||
authz.RegistrationID = 0
|
||||
|
||||
jsonReply, err := json.Marshal(authz)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
|
|
Loading…
Reference in New Issue