Merge branch 'master' into issued-names-limit-2
Conflicts: mocks/mocks.go rpc/rpc-wrappers.go sa/storage-authority.go
This commit is contained in:
commit
acdb1fa91b
|
|
@ -203,7 +203,7 @@ func TestRevoke(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
|
||||
csr, _ := x509.ParseCertificateRequest(CNandSANCSR)
|
||||
certObj, err := ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
|
|
@ -242,7 +242,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
@ -319,7 +319,7 @@ func TestRejectNoName(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ func TestRejectTooManyNames(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
@ -353,7 +353,7 @@ func TestDeduplication(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
@ -377,7 +377,7 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
@ -394,7 +394,7 @@ func TestShortKey(t *testing.T) {
|
|||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
@ -410,7 +410,7 @@ func TestRejectBadAlgorithm(t *testing.T) {
|
|||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca.Publisher = &mocks.MockPublisher{}
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,21 @@ func revokeByReg(regID int64, reasonCode core.RevocationCode, deny bool, rac rpc
|
|||
return
|
||||
}
|
||||
|
||||
// This abstraction is needed so that we can use sort.Sort below
|
||||
type revocationCodes []core.RevocationCode
|
||||
|
||||
func (rc revocationCodes) Len() int {
|
||||
return len(rc)
|
||||
}
|
||||
|
||||
func (rc revocationCodes) Less(i, j int) bool {
|
||||
return rc[i] < rc[j]
|
||||
}
|
||||
|
||||
func (rc revocationCodes) Swap(i, j int) {
|
||||
rc[i], rc[j] = rc[j], rc[i]
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "admin-revoker"
|
||||
|
|
@ -219,7 +234,7 @@ func main() {
|
|||
Name: "list-reasons",
|
||||
Usage: "List all revocation reason codes",
|
||||
Action: func(c *cli.Context) {
|
||||
var codes core.RevocationCodes
|
||||
var codes revocationCodes
|
||||
for k := range core.RevocationReasons {
|
||||
codes = append(codes, k)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,18 +39,18 @@ func main() {
|
|||
go cmd.ProfileCmd("VA", stats)
|
||||
|
||||
pc := &va.PortConfig{
|
||||
SimpleHTTPPort: 80,
|
||||
SimpleHTTPSPort: 443,
|
||||
DVSNIPort: 443,
|
||||
HTTPPort: 80,
|
||||
HTTPSPort: 443,
|
||||
TLSPort: 443,
|
||||
}
|
||||
if c.VA.PortConfig.SimpleHTTPPort != 0 {
|
||||
pc.SimpleHTTPPort = c.VA.PortConfig.SimpleHTTPPort
|
||||
if c.VA.PortConfig.HTTPPort != 0 {
|
||||
pc.HTTPPort = c.VA.PortConfig.HTTPPort
|
||||
}
|
||||
if c.VA.PortConfig.SimpleHTTPSPort != 0 {
|
||||
pc.SimpleHTTPSPort = c.VA.PortConfig.SimpleHTTPSPort
|
||||
if c.VA.PortConfig.HTTPSPort != 0 {
|
||||
pc.HTTPSPort = c.VA.PortConfig.HTTPSPort
|
||||
}
|
||||
if c.VA.PortConfig.DVSNIPort != 0 {
|
||||
pc.DVSNIPort = c.VA.PortConfig.DVSNIPort
|
||||
if c.VA.PortConfig.TLSPort != 0 {
|
||||
pc.TLSPort = c.VA.PortConfig.TLSPort
|
||||
}
|
||||
vai := va.NewValidationAuthorityImpl(pc, stats, clock.Default())
|
||||
dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ func TestCheckCert(t *testing.T) {
|
|||
}
|
||||
delete(problemsMap, p)
|
||||
}
|
||||
for k, _ := range problemsMap {
|
||||
for k := range problemsMap {
|
||||
t.Errorf("Found unexpected problem '%s'.", k)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,11 +56,11 @@ func (m *mockMail) SendMail(to []string, msg string) (err error) {
|
|||
}
|
||||
|
||||
type fakeRegStore struct {
|
||||
RegById map[int64]core.Registration
|
||||
RegByID map[int64]core.Registration
|
||||
}
|
||||
|
||||
func (f fakeRegStore) GetRegistration(id int64) (core.Registration, error) {
|
||||
r, ok := f.RegById[id]
|
||||
r, ok := f.RegByID[id]
|
||||
if !ok {
|
||||
msg := fmt.Sprintf("no such registration %d", id)
|
||||
return r, core.NoSuchRegistrationError(msg)
|
||||
|
|
@ -69,7 +69,7 @@ func (f fakeRegStore) GetRegistration(id int64) (core.Registration, error) {
|
|||
}
|
||||
|
||||
func newFakeRegStore() fakeRegStore {
|
||||
return fakeRegStore{RegById: make(map[int64]core.Registration)}
|
||||
return fakeRegStore{RegByID: make(map[int64]core.Registration)}
|
||||
}
|
||||
|
||||
const testTmpl = `hi, cert for DNS names {{.DNSNames}} is going to expire in {{.DaysToExpiration}} days ({{.ExpirationDate}})`
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
|
@ -66,14 +67,13 @@ func NewSourceFromDatabase(dbMap *gorp.DbMap, caKeyHash []byte) (src *DBSource,
|
|||
}
|
||||
|
||||
// Response is called by the HTTP server to handle a new OCSP request.
|
||||
func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool) {
|
||||
func (src *DBSource) Response(req *ocsp.Request) ([]byte, bool) {
|
||||
log := blog.GetAuditLogger()
|
||||
|
||||
// Check that this request is for the proper CA
|
||||
if bytes.Compare(req.IssuerKeyHash, src.caKeyHash) != 0 {
|
||||
log.Debug(fmt.Sprintf("Request intended for CA Cert ID: %s", hex.EncodeToString(req.IssuerKeyHash)))
|
||||
present = false
|
||||
return
|
||||
return nil, false
|
||||
}
|
||||
|
||||
serialString := core.SerialToString(req.SerialNumber)
|
||||
|
|
@ -86,37 +86,33 @@ func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool)
|
|||
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
|
||||
return
|
||||
return nil, false
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("OCSP Response sent for CA=%s, Serial=%s", hex.EncodeToString(src.caKeyHash), serialString))
|
||||
|
||||
response = ocspResponse.Response
|
||||
present = true
|
||||
return
|
||||
return ocspResponse.Response, true
|
||||
}
|
||||
|
||||
func makeDBSource(dbConnect, issuerCert string, sqlDebug bool) (cfocsp.Source, error) {
|
||||
var noSource cfocsp.Source
|
||||
func makeDBSource(dbConnect, issuerCert string, sqlDebug bool) (*DBSource, error) {
|
||||
// Configure DB
|
||||
dbMap, err := sa.NewDbMap(dbConnect)
|
||||
if err != nil {
|
||||
return noSource, fmt.Errorf("Could not connect to database: %s", err)
|
||||
return nil, fmt.Errorf("Could not connect to database: %s", err)
|
||||
}
|
||||
sa.SetSQLDebug(dbMap, sqlDebug)
|
||||
|
||||
// Load the CA's key so we can store its SubjectKey in the DB
|
||||
caCertDER, err := cmd.LoadCert(issuerCert)
|
||||
if err != nil {
|
||||
return noSource, fmt.Errorf("Could not read issuer cert %s: %s", issuerCert, err)
|
||||
return nil, fmt.Errorf("Could not read issuer cert %s: %s", issuerCert, err)
|
||||
}
|
||||
caCert, err := x509.ParseCertificate(caCertDER)
|
||||
if err != nil {
|
||||
return noSource, fmt.Errorf("Could not parse issuer cert %s: %s", issuerCert, err)
|
||||
return nil, fmt.Errorf("Could not parse issuer cert %s: %s", issuerCert, err)
|
||||
}
|
||||
if len(caCert.SubjectKeyId) == 0 {
|
||||
return noSource, fmt.Errorf("Empty subjectKeyID")
|
||||
return nil, fmt.Errorf("Empty subjectKeyID")
|
||||
}
|
||||
|
||||
// Construct source from DB
|
||||
|
|
@ -161,6 +157,8 @@ func main() {
|
|||
}
|
||||
source, err = cfocsp.NewSourceFromFile(filename)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Couldn't read file: %s", url.Path))
|
||||
} else {
|
||||
cmd.FailOnError(errors.New(`"source" parameter not found in JSON config`), "unable to start ocsp-responder")
|
||||
}
|
||||
|
||||
stopTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownStopTimeout)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
cfocsp "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/crypto/ocsp"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
func TestCacheControl(t *testing.T) {
|
||||
src := make(ocsp.InMemorySource)
|
||||
src := make(cfocsp.InMemorySource)
|
||||
h := handler(src, 10*time.Second)
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("GET", "/", nil)
|
||||
|
|
@ -24,3 +31,80 @@ func TestCacheControl(t *testing.T) {
|
|||
t.Errorf("Cache-Control value: want %#v, got %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
req = mustRead("./testdata/ocsp.req")
|
||||
resp = mustRead("./testdata/ocsp.resp")
|
||||
)
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
ocspReq, err := ocsp.ParseRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("ocsp.ParseRequest: %s", err)
|
||||
}
|
||||
src := make(cfocsp.InMemorySource)
|
||||
src[ocspReq.SerialNumber.String()] = resp
|
||||
|
||||
h := handler(src, 10*time.Second)
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("POST", "/", bytes.NewReader(req))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Code: want %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
if !bytes.Equal(w.Body.Bytes(), resp) {
|
||||
t.Errorf("Mismatched body: want %#v, got %#v", resp, w.Body.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBHandler(t *testing.T) {
|
||||
src, err := makeDBSource("mysql+tcp://boulder@localhost:3306/boulder_sa_test", "./testdata/test-ca.der.pem", false)
|
||||
if err != nil {
|
||||
t.Fatalf("makeDBSource: %s", err)
|
||||
}
|
||||
defer test.ResetTestDatabase(t, src.dbMap.Db)
|
||||
ocspResp, err := ocsp.ParseResponse(resp, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ocsp.ParseResponse: %s", err)
|
||||
}
|
||||
|
||||
dbOCSP := &core.OCSPResponse{
|
||||
Serial: core.SerialToString(ocspResp.SerialNumber),
|
||||
CreatedAt: time.Now(),
|
||||
Response: resp,
|
||||
}
|
||||
err = src.dbMap.Insert(dbOCSP)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to insert response: %s", err)
|
||||
}
|
||||
|
||||
h := handler(src, 10*time.Second)
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequest("POST", "/", bytes.NewReader(req))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Code: want %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
if !bytes.Equal(w.Body.Bytes(), resp) {
|
||||
t.Errorf("Mismatched body: want %#v, got %#v", resp, w.Body.Bytes())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func mustRead(path string) []byte {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("open %#v: %s", path, err))
|
||||
}
|
||||
b, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("read all %#v: %s", path, err))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV
|
||||
BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw
|
||||
NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i
|
||||
8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8
|
||||
tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj
|
||||
7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8
|
||||
BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD
|
||||
HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj
|
||||
UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7
|
||||
eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
|
||||
A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB
|
||||
vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl
|
||||
zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo
|
||||
vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L
|
||||
oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW
|
||||
rFo4Uv1EnkKJm3vJFe50eJGhEKlx
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDCCkd5mgXFErJ3
|
||||
F2M0E9dw+Ta/md5i8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9
|
||||
r9tSQcL8VM6WUOM8tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHS
|
||||
zUYtNKNeFI6Glamj7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p5
|
||||
8QkP4LHLShVLXDa8BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aP
|
||||
kE/cefmP+1xOfUuDHOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w
|
||||
5qxSPTarAgMBAAECggEAZh00uhjFOo35X1TufwSGF0z/c9uMvfMB4i1ufM2qgXud
|
||||
WXLSLcrksZhhTfLAS4KSTa3PtSKqLBoPg1tdhy9WZqZWxaIxw8ybzaGtn8HNHGyr
|
||||
LzsVlSLT2ATN4C7VAT9+DeVext0kWHtdz3r5mGagJq2Yx9jRGpQW6rBA9h4ol699
|
||||
BM09UPCcdlGmpdrb0jDjyfohG139EBSmEeB+Jim+oLO1sXe/LvWllU0UL527CExp
|
||||
ykiIjASd4s7tFErV9sVJ+bDI97GOyBUGcVMiQ+TRPKFr0kfLgbJz24l8ycPI4odp
|
||||
IGY+6igicg67n5BktAH+UfCQlUIpWbF2SwRAMht0AQKBgQD8gocy2VuCPj285hBY
|
||||
8g/1GFd58HkCh54bOhAOb2PK+NE4mRuHCBlBj/tQOmgYz2Pna2k5ldJSUwXsUKkx
|
||||
9R7hutnwXbcQTSQIRcjhYDLeGetJYXR96ylDig+6XjdW3A5SIc2JzlbVThP39TTm
|
||||
gRqE/rj9G4ARMfHxffp7YT5AqwKBgQDEuN0pYMKjaW0xvc7WYUOqGHqt2di/BwMr
|
||||
Ur438MtePArELY35P6kDcrfnlacDToA3Tebk9Rw18y1kl3BFO7VdJbQJSa6RWbp5
|
||||
aK7E5lq1pCrdyhGwiaI1f5VgzeY8ywS3TqGqU9GOqpENiZqgs1ly9l8gZSaw8/yF
|
||||
uDWGg7jiAQKBgQCyLtGEmkiuoYkjUR1cBoQoKeMgkwZxOI3jHJfT99ptkiLhU3lP
|
||||
UfGwiA+JT43BZCdVWEBKeGSP3zIgzdJ3BEekdhvwN9FEWYsBo2zbTOzYOWYExBZV
|
||||
/KmDlVr/4hge3O3mGyBVDBvOLWh94rRPq+6wxqZ3RP6cI6hdBs7IXZh2PQKBgQDB
|
||||
rav4kA4xKpvaDCC2yj3/Gmi1/zO5J2NEZQtoMgdXeM+0w5Dy4204Otq7A4jR5Ziw
|
||||
Wl9H7dZfe1Kmpb5gO1/dHEC7oDJhYjEIVTs0GgMWsFGP2OE/qNHtz/W2wCC8m7jB
|
||||
7IWYFzvLNTzoUiDNtKYNXGjdkRjdwOlOkcUI8Wi2AQKBgQC9EJsMz/ySt58IvwWy
|
||||
fQJyg742j21pXHqlMnmHygnSgNa7f3yPQK3FxjvhIPmgu7x8+sSUtXHOjKhZML3p
|
||||
SdTm/yN487hOYp03jy/wVXLcCDp9XhBeIt/z/TZMPMjAHOLG9xG6cF8AOVq7mLBc
|
||||
tsDWUHoXPZj/YciXZLq3fPuXyw==
|
||||
-----END PRIVATE KEY-----
|
||||
30
cmd/shell.go
30
cmd/shell.go
|
|
@ -30,7 +30,7 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
_ "net/http/pprof" // HTTP performance profiling, added transparently to HTTP APIs
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
|
@ -42,6 +42,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/publisher"
|
||||
"github.com/letsencrypt/boulder/va"
|
||||
)
|
||||
|
||||
// Config stores configuration parameters that applications
|
||||
|
|
@ -114,11 +115,7 @@ type Config struct {
|
|||
VA struct {
|
||||
UserAgent string
|
||||
|
||||
PortConfig struct {
|
||||
SimpleHTTPPort int
|
||||
SimpleHTTPSPort int
|
||||
DVSNIPort int
|
||||
}
|
||||
PortConfig va.PortConfig
|
||||
|
||||
MaxConcurrentRPCServerRequests int64
|
||||
|
||||
|
|
@ -220,6 +217,9 @@ type Config struct {
|
|||
SubscriberAgreementURL string
|
||||
}
|
||||
|
||||
// CAConfig structs have configuration information for the certificate
|
||||
// authority, including database parameters as well as controls for
|
||||
// issued certificates.
|
||||
type CAConfig struct {
|
||||
Profile string
|
||||
TestMode bool
|
||||
|
|
@ -242,6 +242,8 @@ type CAConfig struct {
|
|||
DebugAddr string
|
||||
}
|
||||
|
||||
// PAConfig specifies how a policy authority should connect to its
|
||||
// database, and what policies it should enforce.
|
||||
type PAConfig struct {
|
||||
DBConnect string
|
||||
EnforcePolicyWhitelist bool
|
||||
|
|
@ -301,6 +303,7 @@ type AppShell struct {
|
|||
App *cli.App
|
||||
}
|
||||
|
||||
// Version returns a string representing the version of boulder running.
|
||||
func Version() string {
|
||||
return fmt.Sprintf("0.1.0 [%s]", core.GetBuildID())
|
||||
}
|
||||
|
|
@ -412,6 +415,11 @@ func LoadCert(path string) (cert []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// DebugServer starts a server to receive debug information. Typical
|
||||
// usage is to start it in a goroutine, configured with an address
|
||||
// from the appropriate configuration object:
|
||||
//
|
||||
// go cmd.DebugServer(c.XA.DebugAddr)
|
||||
func DebugServer(addr string) {
|
||||
if addr == "" {
|
||||
log.Fatalf("unable to boot debug server because no address was given for it. Set debugAddr.")
|
||||
|
|
@ -424,12 +432,19 @@ func DebugServer(addr string) {
|
|||
log.Println(http.Serve(ln, nil))
|
||||
}
|
||||
|
||||
// ConfigDuration is just an alias for time.Duration that allows
|
||||
// serialization to YAML as well as JSON.
|
||||
type ConfigDuration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
// ErrDurationMustBeString is returned when a non-string value is
|
||||
// presented to be deserialized as a ConfigDuration
|
||||
var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration")
|
||||
|
||||
// UnmarshalJSON parses a string into a ConfigDuration using
|
||||
// time.ParseDuration. If the input does not unmarshal as a
|
||||
// string, then UnmarshalJSON returns ErrDurationMustBeString.
|
||||
func (d *ConfigDuration) UnmarshalJSON(b []byte) error {
|
||||
s := ""
|
||||
err := json.Unmarshal(b, &s)
|
||||
|
|
@ -444,10 +459,13 @@ func (d *ConfigDuration) UnmarshalJSON(b []byte) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// MarshalJSON returns the string form of the duration, as a byte array.
|
||||
func (d ConfigDuration) MarshalJSON() ([]byte, error) {
|
||||
return []byte(d.Duration.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML uses the same frmat as JSON, but is called by the YAML
|
||||
// parser (vs. the JSON parser).
|
||||
func (d *ConfigDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var s string
|
||||
if err := unmarshal(&s); err != nil {
|
||||
|
|
|
|||
|
|
@ -5,31 +5,45 @@
|
|||
|
||||
package core
|
||||
|
||||
// SimpleHTTPChallenge constructs a random HTTP challenge
|
||||
func SimpleHTTPChallenge() Challenge {
|
||||
tls := true
|
||||
import (
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
)
|
||||
|
||||
func newChallenge(challengeType string, accountKey *jose.JsonWebKey) Challenge {
|
||||
return Challenge{
|
||||
Type: ChallengeTypeSimpleHTTP,
|
||||
Status: StatusPending,
|
||||
Token: NewToken(),
|
||||
TLS: &tls,
|
||||
Type: challengeType,
|
||||
Status: StatusPending,
|
||||
AccountKey: accountKey,
|
||||
Token: NewToken(),
|
||||
}
|
||||
}
|
||||
|
||||
// SimpleHTTPChallenge constructs a random HTTP challenge
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func SimpleHTTPChallenge(accountKey *jose.JsonWebKey) Challenge {
|
||||
challenge := newChallenge(ChallengeTypeSimpleHTTP, accountKey)
|
||||
tls := true
|
||||
challenge.TLS = &tls
|
||||
return challenge
|
||||
}
|
||||
|
||||
// DvsniChallenge constructs a random DVSNI challenge
|
||||
func DvsniChallenge() Challenge {
|
||||
return Challenge{
|
||||
Type: ChallengeTypeDVSNI,
|
||||
Status: StatusPending,
|
||||
Token: NewToken(),
|
||||
}
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func DvsniChallenge(accountKey *jose.JsonWebKey) Challenge {
|
||||
return newChallenge(ChallengeTypeDVSNI, accountKey)
|
||||
}
|
||||
|
||||
// DNSChallenge constructs a random DNS challenge
|
||||
func DNSChallenge() Challenge {
|
||||
return Challenge{
|
||||
Type: ChallengeTypeDNS,
|
||||
Status: StatusPending,
|
||||
Token: NewToken(),
|
||||
}
|
||||
// HTTPChallenge01 constructs a random http-01 challenge
|
||||
func HTTPChallenge01(accountKey *jose.JsonWebKey) Challenge {
|
||||
return newChallenge(ChallengeTypeHTTP01, accountKey)
|
||||
}
|
||||
|
||||
// TLSSNIChallenge01 constructs a random tls-sni-00 challenge
|
||||
func TLSSNIChallenge01(accountKey *jose.JsonWebKey) Challenge {
|
||||
return newChallenge(ChallengeTypeTLSSNI01, accountKey)
|
||||
}
|
||||
|
||||
// DNSChallenge01 constructs a random DNS challenge
|
||||
func DNSChallenge01(accountKey *jose.JsonWebKey) Challenge {
|
||||
return newChallenge(ChallengeTypeDNS01, accountKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,22 +13,48 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
)
|
||||
|
||||
// challenges.go
|
||||
|
||||
var accountKeyJSON = `{
|
||||
"kty":"RSA",
|
||||
"n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
|
||||
"e":"AQAB"
|
||||
}`
|
||||
|
||||
func TestChallenges(t *testing.T) {
|
||||
simpleHTTP := SimpleHTTPChallenge()
|
||||
if simpleHTTP.Status != StatusPending {
|
||||
t.Errorf("Incorrect status for challenge: %v", simpleHTTP.Status)
|
||||
}
|
||||
if len(simpleHTTP.Token) != 43 {
|
||||
t.Errorf("Incorrect length for simpleHTTP token: %v", simpleHTTP.Token)
|
||||
var accountKey *jose.JsonWebKey
|
||||
err := json.Unmarshal([]byte(accountKeyJSON), &accountKey)
|
||||
if err != nil {
|
||||
t.Errorf("Error unmarshaling JWK: %v", err)
|
||||
}
|
||||
|
||||
dvsni := DvsniChallenge()
|
||||
if dvsni.Status != StatusPending {
|
||||
t.Errorf("Incorrect status for challenge: %v", dvsni.Status)
|
||||
simpleHTTP := SimpleHTTPChallenge(accountKey)
|
||||
if !simpleHTTP.IsSane(false) {
|
||||
t.Errorf("New HTTP challenge is not sane: %v", simpleHTTP)
|
||||
}
|
||||
|
||||
dvsni := DvsniChallenge(accountKey)
|
||||
if !dvsni.IsSane(false) {
|
||||
t.Errorf("New DVSNI challenge is not sane: %v", dvsni)
|
||||
}
|
||||
|
||||
http01 := HTTPChallenge01(accountKey)
|
||||
if !http01.IsSane(false) {
|
||||
t.Errorf("New http-01 challenge is not sane: %v", http01)
|
||||
}
|
||||
|
||||
tlssni01 := TLSSNIChallenge01(accountKey)
|
||||
if !tlssni01.IsSane(false) {
|
||||
t.Errorf("New tls-sni-01 challenge is not sane: %v", tlssni01)
|
||||
}
|
||||
|
||||
dns01 := DNSChallenge01(accountKey)
|
||||
if !dns01.IsSane(false) {
|
||||
t.Errorf("New dns-01 challenge is not sane: %v", dns01)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,8 +127,8 @@ func TestMergeChallenge(t *testing.T) {
|
|||
if probe.Validated != merged.Validated {
|
||||
t.Errorf("MergeChallenge allowed response to overwrite completed time")
|
||||
}
|
||||
if probe.Token != merged.Token {
|
||||
t.Errorf("MergeChallenge allowed response to overwrite status")
|
||||
if probe.KeyAuthorization != merged.KeyAuthorization {
|
||||
t.Errorf("MergeChallenge allowed response to overwrite authorized key")
|
||||
}
|
||||
if probe.TLS != merged.TLS {
|
||||
t.Errorf("MergeChallenge failed to overwrite TLS")
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ type CertificateAuthority interface {
|
|||
// PolicyAuthority defines the public interface for the Boulder PA
|
||||
type PolicyAuthority interface {
|
||||
WillingToIssue(id AcmeIdentifier, regID int64) error
|
||||
ChallengesFor(AcmeIdentifier) ([]Challenge, [][]int)
|
||||
ChallengesFor(AcmeIdentifier, *jose.JsonWebKey) ([]Challenge, [][]int, error)
|
||||
}
|
||||
|
||||
// StorageGetter are the Boulder SA's read-only methods
|
||||
|
|
|
|||
225
core/objects.go
225
core/objects.go
|
|
@ -6,6 +6,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
|
|
@ -91,11 +92,13 @@ const (
|
|||
const (
|
||||
ChallengeTypeSimpleHTTP = "simpleHttp"
|
||||
ChallengeTypeDVSNI = "dvsni"
|
||||
ChallengeTypeDNS = "dns"
|
||||
ChallengeTypeHTTP01 = "http-01"
|
||||
ChallengeTypeTLSSNI01 = "tls-sni-01"
|
||||
ChallengeTypeDNS01 = "dns-01"
|
||||
)
|
||||
|
||||
// The suffix appended to pseudo-domain names in DVSNI challenges
|
||||
const DVSNISuffix = "acme.invalid"
|
||||
const TLSSNISuffix = "acme.invalid"
|
||||
|
||||
// The label attached to DNS names in DNS challenges
|
||||
const DNSPrefix = "_acme-challenge"
|
||||
|
|
@ -192,6 +195,97 @@ type ValidationRecord struct {
|
|||
AddressUsed net.IP `json:"addressUsed"`
|
||||
}
|
||||
|
||||
// KeyAuthorization represents a domain holder's authorization for a
|
||||
// specific account key to satisfy a specific challenge.
|
||||
type KeyAuthorization struct {
|
||||
Token string
|
||||
Thumbprint string
|
||||
}
|
||||
|
||||
// NewKeyAuthorization computes the thumbprint and assembles the object
|
||||
func NewKeyAuthorization(token string, key *jose.JsonWebKey) (KeyAuthorization, error) {
|
||||
if key == nil {
|
||||
return KeyAuthorization{}, fmt.Errorf("Cannot authorize a nil key")
|
||||
}
|
||||
|
||||
thumbprint, err := Thumbprint(key)
|
||||
if err != nil {
|
||||
return KeyAuthorization{}, err
|
||||
}
|
||||
|
||||
return KeyAuthorization{
|
||||
Token: token,
|
||||
Thumbprint: thumbprint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewKeyAuthorizationFromString parses the string and composes a key authorization struct
|
||||
func NewKeyAuthorizationFromString(input string) (ka KeyAuthorization, err error) {
|
||||
parts := strings.Split(input, ".")
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("Invalid key authorization: %d parts", len(parts))
|
||||
return
|
||||
} else if !LooksLikeAToken(parts[0]) {
|
||||
err = fmt.Errorf("Invalid key authorization: malformed token")
|
||||
return
|
||||
} else if !LooksLikeAToken(parts[1]) {
|
||||
// Thumbprints have the same syntax as tokens in boulder
|
||||
// Both are base64-encoded and 32 octets
|
||||
err = fmt.Errorf("Invalid key authorization: malformed key thumbprint")
|
||||
return
|
||||
}
|
||||
|
||||
ka = KeyAuthorization{
|
||||
Token: parts[0],
|
||||
Thumbprint: parts[1],
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String produces the string representation of a key authorization
|
||||
func (ka KeyAuthorization) String() string {
|
||||
return ka.Token + "." + ka.Thumbprint
|
||||
}
|
||||
|
||||
// Match determines whether this KeyAuthorization matches the given token and key
|
||||
func (ka KeyAuthorization) Match(token string, key *jose.JsonWebKey) bool {
|
||||
if key == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
thumbprint, err := Thumbprint(key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
tokensEqual := subtle.ConstantTimeCompare([]byte(token), []byte(ka.Token))
|
||||
thumbprintsEqual := subtle.ConstantTimeCompare([]byte(thumbprint), []byte(ka.Thumbprint))
|
||||
|
||||
return tokensEqual == 1 && thumbprintsEqual == 1
|
||||
}
|
||||
|
||||
// MarshalJSON packs a key authorization into its string representation
|
||||
func (ka KeyAuthorization) MarshalJSON() (result []byte, err error) {
|
||||
return json.Marshal(ka.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON unpacks a key authorization from a string
|
||||
func (ka *KeyAuthorization) UnmarshalJSON(data []byte) (err error) {
|
||||
var str string
|
||||
err = json.Unmarshal(data, &str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsed, err := NewKeyAuthorizationFromString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*ka = parsed
|
||||
return
|
||||
}
|
||||
|
||||
// Challenge is an aggregate of all data needed for any challenges.
|
||||
//
|
||||
// Rather than define individual types for different types of
|
||||
|
|
@ -216,15 +310,18 @@ type Challenge struct {
|
|||
// A URI to which a response can be POSTed
|
||||
URI string `json:"uri"`
|
||||
|
||||
// Used by simpleHttp, dvsni, and dns challenges
|
||||
// Used by simpleHttp, http-00, tls-sni-00, and dns-00 challenges
|
||||
Token string `json:"token,omitempty"`
|
||||
|
||||
// Used by simpleHTTP challenges
|
||||
// Used by simpleHttp challenges
|
||||
TLS *bool `json:"tls,omitempty"`
|
||||
|
||||
// Used by dns and dvsni challenges
|
||||
// Used by dvsni challenges
|
||||
Validation *jose.JsonWebSignature `json:"validation,omitempty"`
|
||||
|
||||
// Used by http-00, tls-sni-00, and dns-00 challenges
|
||||
KeyAuthorization *KeyAuthorization `json:"keyAuthorization,omitempty"`
|
||||
|
||||
// Contains information about URLs used or redirected to and IPs resolved and
|
||||
// used
|
||||
ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"`
|
||||
|
|
@ -249,6 +346,9 @@ func (ch Challenge) RecordsSane() bool {
|
|||
|
||||
switch ch.Type {
|
||||
case ChallengeTypeSimpleHTTP:
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this case
|
||||
fallthrough
|
||||
case ChallengeTypeHTTP01:
|
||||
for _, rec := range ch.ValidationRecord {
|
||||
if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
|
||||
len(rec.AddressesResolved) == 0 {
|
||||
|
|
@ -256,6 +356,9 @@ func (ch Challenge) RecordsSane() bool {
|
|||
}
|
||||
}
|
||||
case ChallengeTypeDVSNI:
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this case
|
||||
fallthrough
|
||||
case ChallengeTypeTLSSNI01:
|
||||
if len(ch.ValidationRecord) > 1 {
|
||||
return false
|
||||
}
|
||||
|
|
@ -266,16 +369,25 @@ func (ch Challenge) RecordsSane() bool {
|
|||
ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
|
||||
return false
|
||||
}
|
||||
case ChallengeTypeDNS:
|
||||
case ChallengeTypeDNS01:
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSane checks the sanity of a challenge object before issued to the client
|
||||
// (completed = false) and before validation (completed = true).
|
||||
func (ch Challenge) IsSane(completed bool) bool {
|
||||
// isLegacy returns true if the challenge is of a legacy type (i.e., one defined
|
||||
// before draft-ietf-acme-acme-00)
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func (ch Challenge) isLegacy() bool {
|
||||
return (ch.Type == ChallengeTypeSimpleHTTP) ||
|
||||
(ch.Type == ChallengeTypeDVSNI)
|
||||
}
|
||||
|
||||
// legacyIsSane performs sanity checks for legacy challenge types, which have
|
||||
// a different structure / logic than current challenges.
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func (ch Challenge) legacyIsSane(completed bool) bool {
|
||||
if ch.Status != StatusPending {
|
||||
return false
|
||||
}
|
||||
|
|
@ -303,9 +415,6 @@ func (ch Challenge) IsSane(completed bool) bool {
|
|||
return false
|
||||
}
|
||||
case ChallengeTypeDVSNI:
|
||||
// Same as DNS
|
||||
fallthrough
|
||||
case ChallengeTypeDNS:
|
||||
// check extra fields aren't used
|
||||
if ch.TLS != nil {
|
||||
return false
|
||||
|
|
@ -330,9 +439,10 @@ func (ch Challenge) IsSane(completed bool) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// MergeResponse copies a subset of client-provided data to the current Challenge.
|
||||
// legacyMergeResponse copies a subset of client-provided data to the current Challenge.
|
||||
// Note: This method does not update the challenge on the left side of the '.'
|
||||
func (ch Challenge) MergeResponse(resp Challenge) Challenge {
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func (ch Challenge) legacyMergeResponse(resp Challenge) Challenge {
|
||||
switch ch.Type {
|
||||
case ChallengeTypeSimpleHTTP:
|
||||
// For simpleHttp, only "tls" is client-provided
|
||||
|
|
@ -345,8 +455,6 @@ func (ch Challenge) MergeResponse(resp Challenge) Challenge {
|
|||
}
|
||||
|
||||
case ChallengeTypeDVSNI:
|
||||
fallthrough
|
||||
case ChallengeTypeDNS:
|
||||
// For dvsni and dns, only "validation" is client-provided
|
||||
if resp.Validation != nil {
|
||||
ch.Validation = resp.Validation
|
||||
|
|
@ -356,6 +464,65 @@ func (ch Challenge) MergeResponse(resp Challenge) Challenge {
|
|||
return ch
|
||||
}
|
||||
|
||||
// IsSane checks the sanity of a challenge object before issued to the client
|
||||
// (completed = false) and before validation (completed = true).
|
||||
func (ch Challenge) IsSane(completed bool) bool {
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this branch
|
||||
if ch.isLegacy() {
|
||||
return ch.legacyIsSane(completed)
|
||||
}
|
||||
|
||||
if ch.Status != StatusPending {
|
||||
return false
|
||||
}
|
||||
|
||||
// There always needs to be an account key and token
|
||||
if ch.AccountKey == nil || !LooksLikeAToken(ch.Token) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Before completion, the key authorization field should be empty
|
||||
if !completed && ch.KeyAuthorization != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the challenge is completed, then there should be a key authorization,
|
||||
// and it should match the challenge.
|
||||
if completed {
|
||||
if ch.KeyAuthorization == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !ch.KeyAuthorization.Match(ch.Token, ch.AccountKey) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MergeResponse copies a subset of client-provided data to the current Challenge.
|
||||
// Note: This method does not update the challenge on the left side of the '.'
|
||||
func (ch Challenge) MergeResponse(resp Challenge) Challenge {
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this branch
|
||||
if ch.isLegacy() {
|
||||
return ch.legacyMergeResponse(resp)
|
||||
}
|
||||
|
||||
// The only client-provided field is the key authorization, and all current
|
||||
// challenge types use it.
|
||||
switch ch.Type {
|
||||
case ChallengeTypeHTTP01:
|
||||
fallthrough
|
||||
case ChallengeTypeTLSSNI01:
|
||||
fallthrough
|
||||
case ChallengeTypeDNS01:
|
||||
ch.KeyAuthorization = resp.KeyAuthorization
|
||||
}
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// Authorization represents the authorization of an account key holder
|
||||
// to act on behalf of a domain. This struct is intended to be used both
|
||||
// internally and for JSON marshaling on the wire. Any fields that should be
|
||||
|
|
@ -450,11 +617,6 @@ type Certificate struct {
|
|||
Expires time.Time `db:"expires"`
|
||||
}
|
||||
|
||||
type IssuedCertIdentifierData struct {
|
||||
ReversedName string
|
||||
Serial string
|
||||
}
|
||||
|
||||
// IdentifierData holds information about what certificates are known for a
|
||||
// given identifier. This is used to present Proof of Posession challenges in
|
||||
// the case where a certificate already exists. The DB table holding
|
||||
|
|
@ -557,6 +719,8 @@ type OCSPSigningRequest struct {
|
|||
RevokedAt time.Time
|
||||
}
|
||||
|
||||
// SignedCertificateTimestamp represents objects used by Certificate Transparency
|
||||
// to demonstrate that a certificate was submitted to a CT log. See RFC 6962.
|
||||
type SignedCertificateTimestamp struct {
|
||||
ID int `db:"id"`
|
||||
// The version of the protocol to which the SCT conforms
|
||||
|
|
@ -577,8 +741,6 @@ type SignedCertificateTimestamp struct {
|
|||
LockCol int64
|
||||
}
|
||||
|
||||
type RPCSignedCertificateTimestamp SignedCertificateTimestamp
|
||||
|
||||
type rawSignedCertificateTimestamp struct {
|
||||
Version uint8 `json:"sct_version"`
|
||||
LogID string `json:"id"`
|
||||
|
|
@ -587,6 +749,9 @@ type rawSignedCertificateTimestamp struct {
|
|||
Extensions string `json:"extensions"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the add-chain response from a CT log. It fills all of
|
||||
// the fields in the SignedCertificateTimestamp struct except for ID and
|
||||
// CertificateSerial, which are used for local recordkeeping in the Boulder DB.
|
||||
func (sct *SignedCertificateTimestamp) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var rawSCT rawSignedCertificateTimestamp
|
||||
|
|
@ -649,20 +814,6 @@ func (sct *SignedCertificateTimestamp) CheckSignature() error {
|
|||
// RevocationCode is used to specify a certificate revocation reason
|
||||
type RevocationCode int
|
||||
|
||||
type RevocationCodes []RevocationCode
|
||||
|
||||
func (rc RevocationCodes) Len() int {
|
||||
return len(rc)
|
||||
}
|
||||
|
||||
func (rc RevocationCodes) Less(i, j int) bool {
|
||||
return rc[i] < rc[j]
|
||||
}
|
||||
|
||||
func (rc RevocationCodes) Swap(i, j int) {
|
||||
rc[i], rc[j] = rc[j], rc[i]
|
||||
}
|
||||
|
||||
// RevocationReasons provides a map from reason code to string explaining the
|
||||
// code
|
||||
var RevocationReasons = map[RevocationCode]string{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"testing"
|
||||
|
|
@ -40,6 +42,24 @@ func TestRegistrationUpdate(t *testing.T) {
|
|||
test.Assert(t, reg.Agreement == update.Agreement, "Agreement was not updated")
|
||||
}
|
||||
|
||||
var testKey1, _ = rsa.GenerateKey(rand.Reader, 2048)
|
||||
var testKey2, _ = rsa.GenerateKey(rand.Reader, 2048)
|
||||
|
||||
func TestKeyAuthorization(t *testing.T) {
|
||||
jwk1 := &jose.JsonWebKey{Key: testKey1.Public()}
|
||||
jwk2 := &jose.JsonWebKey{Key: testKey2.Public()}
|
||||
|
||||
ka1, err := NewKeyAuthorization("99DrlWuy-4Nc82olAy0cK7Shnm4uV32pJovyucGEWME", jwk1)
|
||||
test.AssertNotError(t, err, "Failed to create a new key authorization")
|
||||
ka2, err := NewKeyAuthorization("Iy2_-2OA8lyD0lwhmD8dD3TIL3wlNpiUhLTXPJG5qOM", jwk2)
|
||||
test.AssertNotError(t, err, "Failed to create a new key authorization")
|
||||
|
||||
test.Assert(t, ka1.Match(ka1.Token, jwk1), "Authorized key should match itself")
|
||||
test.Assert(t, !ka1.Match(ka1.Token, jwk2), "Authorized key should not match a different key")
|
||||
test.Assert(t, !ka1.Match(ka2.Token, jwk1), "Authorized key should not match a different token")
|
||||
test.Assert(t, !ka1.Match(ka2.Token, jwk2), "Authorized key should not match a completely different key")
|
||||
}
|
||||
|
||||
func TestRecordSanityCheck(t *testing.T) {
|
||||
rec := []ValidationRecord{
|
||||
ValidationRecord{
|
||||
|
|
@ -66,7 +86,8 @@ func TestRecordSanityCheck(t *testing.T) {
|
|||
test.Assert(t, !chall.RecordsSane(), "Record should not be sane")
|
||||
}
|
||||
|
||||
func TestChallengeSanityCheck(t *testing.T) {
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this test
|
||||
func TestChallengeSanityCheck_Legacy(t *testing.T) {
|
||||
// Make a temporary account key
|
||||
var accountKey *jose.JsonWebKey
|
||||
err := json.Unmarshal([]byte(`{
|
||||
|
|
@ -76,7 +97,7 @@ func TestChallengeSanityCheck(t *testing.T) {
|
|||
}`), &accountKey)
|
||||
test.AssertNotError(t, err, "Error unmarshaling JWK")
|
||||
|
||||
types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI, ChallengeTypeDNS}
|
||||
types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI}
|
||||
for _, challengeType := range types {
|
||||
chall := Challenge{
|
||||
Type: challengeType,
|
||||
|
|
@ -107,7 +128,7 @@ func TestChallengeSanityCheck(t *testing.T) {
|
|||
AddressUsed: net.IP{127, 0, 0, 1},
|
||||
}}
|
||||
test.Assert(t, chall.IsSane(true), "IsSane should be true")
|
||||
} else if challengeType == ChallengeTypeDVSNI || challengeType == ChallengeTypeDNS {
|
||||
} else if challengeType == ChallengeTypeDVSNI {
|
||||
chall.Validation = new(jose.JsonWebSignature)
|
||||
if challengeType == ChallengeTypeDVSNI {
|
||||
chall.ValidationRecord = []ValidationRecord{ValidationRecord{
|
||||
|
|
@ -128,6 +149,43 @@ func TestChallengeSanityCheck(t *testing.T) {
|
|||
test.Assert(t, !chall.IsSane(true), "IsSane should be false")
|
||||
}
|
||||
|
||||
func TestChallengeSanityCheck(t *testing.T) {
|
||||
// Make a temporary account key
|
||||
var accountKey *jose.JsonWebKey
|
||||
err := json.Unmarshal([]byte(`{
|
||||
"kty":"RSA",
|
||||
"n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
|
||||
"e":"AQAB"
|
||||
}`), &accountKey)
|
||||
test.AssertNotError(t, err, "Error unmarshaling JWK")
|
||||
|
||||
ka, err := NewKeyAuthorization("KQqLsiS5j0CONR_eUXTUSUDNVaHODtc-0pD6ACif7U4", accountKey)
|
||||
test.AssertNotError(t, err, "Error creating key authorization")
|
||||
|
||||
types := []string{ChallengeTypeHTTP01, ChallengeTypeTLSSNI01, ChallengeTypeDNS01}
|
||||
for _, challengeType := range types {
|
||||
chall := Challenge{
|
||||
Type: challengeType,
|
||||
Status: StatusInvalid,
|
||||
AccountKey: accountKey,
|
||||
}
|
||||
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
|
||||
|
||||
chall.Status = StatusPending
|
||||
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
|
||||
|
||||
chall.Token = ka.Token
|
||||
test.Assert(t, chall.IsSane(false), "IsSane should be true")
|
||||
|
||||
chall.KeyAuthorization = &ka
|
||||
test.Assert(t, chall.IsSane(true), "IsSane should be true")
|
||||
}
|
||||
|
||||
chall := Challenge{Type: "bogus", Status: StatusPending}
|
||||
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
|
||||
test.Assert(t, !chall.IsSane(true), "IsSane should be false")
|
||||
}
|
||||
|
||||
func TestJSONBufferUnmarshal(t *testing.T) {
|
||||
testStruct := struct {
|
||||
Buffer JSONBuffer
|
||||
|
|
|
|||
104
core/util.go
104
core/util.go
|
|
@ -8,6 +8,7 @@ package core
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
|
|
@ -26,6 +27,7 @@ import (
|
|||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
|
|
@ -153,6 +155,14 @@ func NewToken() string {
|
|||
return RandomString(32)
|
||||
}
|
||||
|
||||
var tokenFormat = regexp.MustCompile("^[\\w-]{43}$")
|
||||
|
||||
// LooksLikeAToken checks whether a string represents a 32-octet value in
|
||||
// the URL-safe base64 alphabet.
|
||||
func LooksLikeAToken(token string) bool {
|
||||
return tokenFormat.MatchString(token)
|
||||
}
|
||||
|
||||
// Fingerprints
|
||||
|
||||
// Fingerprint256 produces an unpadded, URL-safe Base64-encoded SHA256 digest
|
||||
|
|
@ -163,6 +173,96 @@ func Fingerprint256(data []byte) string {
|
|||
return B64enc(d.Sum(nil))
|
||||
}
|
||||
|
||||
// Thumbprint produces a JWK thumbprint [RFC7638] of a JWK.
|
||||
// XXX(rlb): This is adapted from a PR to go-jose, but we need it here until
|
||||
// that PR is merged and we update to that version.
|
||||
// https://github.com/square/go-jose/pull/37
|
||||
// XXX(rlb): Once that lands, we should replace the digest methods below
|
||||
// with this standard thumbprint.
|
||||
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
||||
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
||||
|
||||
// Get JOSE name of curve
|
||||
func curveName(crv elliptic.Curve) (string, error) {
|
||||
switch crv {
|
||||
case elliptic.P256():
|
||||
return "P-256", nil
|
||||
case elliptic.P384():
|
||||
return "P-384", nil
|
||||
case elliptic.P521():
|
||||
return "P-521", nil
|
||||
default:
|
||||
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
||||
}
|
||||
}
|
||||
|
||||
// Get size of curve in bytes
|
||||
func curveSize(crv elliptic.Curve) int {
|
||||
bits := crv.Params().BitSize
|
||||
|
||||
div := bits / 8
|
||||
mod := bits % 8
|
||||
|
||||
if mod == 0 {
|
||||
return div
|
||||
}
|
||||
|
||||
return div + 1
|
||||
}
|
||||
|
||||
func newFixedSizeBuffer(data []byte, length int) []byte {
|
||||
if len(data) > length {
|
||||
panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
|
||||
}
|
||||
pad := make([]byte, length-len(data))
|
||||
return append(pad, data...)
|
||||
}
|
||||
|
||||
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
||||
coordLength := curveSize(curve)
|
||||
crv, err := curveName(curve)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ecThumbprintTemplate, crv,
|
||||
B64enc(newFixedSizeBuffer(x.Bytes(), coordLength)),
|
||||
B64enc(newFixedSizeBuffer(y.Bytes(), coordLength))), nil
|
||||
}
|
||||
|
||||
func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
||||
return fmt.Sprintf(rsaThumbprintTemplate,
|
||||
B64enc(big.NewInt(int64(e)).Bytes()),
|
||||
B64enc(n.Bytes())), nil
|
||||
}
|
||||
|
||||
// Thumbprint computes the JWK Thumbprint of a key using the
|
||||
// indicated hash algorithm.
|
||||
func Thumbprint(k *jose.JsonWebKey) (string, error) {
|
||||
var input string
|
||||
var err error
|
||||
switch key := k.Key.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||
case *ecdsa.PrivateKey:
|
||||
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||
case *rsa.PublicKey:
|
||||
input, err = rsaThumbprintInput(key.N, key.E)
|
||||
case *rsa.PrivateKey:
|
||||
input, err = rsaThumbprintInput(key.N, key.E)
|
||||
default:
|
||||
return "", fmt.Errorf("square/go-jose: unkown key type")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(input))
|
||||
return B64enc(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// KeyDigest produces a padded, standard Base64-encoded SHA256 digest of a
|
||||
// provided public key.
|
||||
func KeyDigest(key crypto.PublicKey) (string, error) {
|
||||
|
|
@ -201,6 +301,7 @@ func KeyDigestEquals(j, k crypto.PublicKey) bool {
|
|||
// AcmeURL is a URL that automatically marshal/unmarshal to JSON strings
|
||||
type AcmeURL url.URL
|
||||
|
||||
// ParseAcmeURL is just a wrapper around url.Parse that returns an *AcmeURL
|
||||
func ParseAcmeURL(s string) (*AcmeURL, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
|
|
@ -327,6 +428,9 @@ func StringToSerial(serial string) (*big.Int, error) {
|
|||
return &serialNum, err
|
||||
}
|
||||
|
||||
// ValidSerial tests whether the input string represents a syntactically
|
||||
// valid serial number, i.e., that it is a valid hex string between 32
|
||||
// and 36 characters long.
|
||||
func ValidSerial(serial string) bool {
|
||||
// Originally, serial numbers were 32 hex characters long. We later increased
|
||||
// them to 36, but we allow the shorter ones because they exist in some
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ func TestNewToken(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
func TestLooksLikeAToken(t *testing.T) {
|
||||
test.Assert(t, !LooksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS"), "Accepted short token")
|
||||
test.Assert(t, !LooksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS%"), "Accepted invalid token")
|
||||
test.Assert(t, LooksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOSU"), "Rejected valid token")
|
||||
}
|
||||
|
||||
func TestSerialUtils(t *testing.T) {
|
||||
serial := SerialToString(big.NewInt(100000000000000000))
|
||||
test.AssertEquals(t, serial, "00000000000000000000016345785d8a0000")
|
||||
|
|
@ -58,12 +64,22 @@ const JWK1JSON = `{
|
|||
"e": "AQAB"
|
||||
}`
|
||||
const JWK1Digest = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=`
|
||||
const JWK1Thumbprint = `-kVpHjJCDNQQk-j9BGMpzHAVCiOqvoTRZB-Ov4CAiM4`
|
||||
const JWK2JSON = `{
|
||||
"kty":"RSA",
|
||||
"n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw",
|
||||
"e":"AQAB"
|
||||
}`
|
||||
|
||||
func TestKeyThumbprint(t *testing.T) {
|
||||
var jwk jose.JsonWebKey
|
||||
json.Unmarshal([]byte(JWK1JSON), &jwk)
|
||||
thumbprint, err := Thumbprint(&jwk)
|
||||
|
||||
test.AssertNotError(t, err, "Failed to compute JWK digest")
|
||||
test.AssertEquals(t, thumbprint, JWK1Thumbprint)
|
||||
}
|
||||
|
||||
func TestKeyDigest(t *testing.T) {
|
||||
// Test with JWK (value, reference, and direct)
|
||||
var jwk jose.JsonWebKey
|
||||
|
|
|
|||
40
mocks/log.go
40
mocks/log.go
|
|
@ -13,10 +13,10 @@ import (
|
|||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
// MockSyslogWriter implements the blog.SyslogWriter interface. It
|
||||
// SyslogWriter implements the blog.SyslogWriter interface. It
|
||||
// stores all logged messages in a buffer for inspection by test
|
||||
// functions (via GetAll()) instead of sending them to syslog.
|
||||
type MockSyslogWriter struct {
|
||||
type SyslogWriter struct {
|
||||
logged []*LogMessage
|
||||
msgChan chan<- *LogMessage
|
||||
getChan <-chan []*LogMessage
|
||||
|
|
@ -24,7 +24,7 @@ type MockSyslogWriter struct {
|
|||
closeChan chan<- struct{}
|
||||
}
|
||||
|
||||
// LogMessage is a log entry that has been sent to a MockSyslogWriter.
|
||||
// LogMessage is a log entry that has been sent to a SyslogWriter.
|
||||
type LogMessage struct {
|
||||
Priority syslog.Priority // aka Log level
|
||||
Message string // content of log message
|
||||
|
|
@ -54,19 +54,19 @@ func (lm *LogMessage) String() string {
|
|||
// // ...
|
||||
// Assert(t, len(log.GetAll()) > 0, "Should have logged something")
|
||||
// }
|
||||
func UseMockLog() *MockSyslogWriter {
|
||||
func UseMockLog() *SyslogWriter {
|
||||
sw := NewSyslogWriter()
|
||||
blog.GetAuditLogger().SyslogWriter = sw
|
||||
return sw
|
||||
}
|
||||
|
||||
// NewSyslogWriter returns a new MockSyslogWriter.
|
||||
func NewSyslogWriter() *MockSyslogWriter {
|
||||
// NewSyslogWriter returns a new SyslogWriter.
|
||||
func NewSyslogWriter() *SyslogWriter {
|
||||
msgChan := make(chan *LogMessage)
|
||||
getChan := make(chan []*LogMessage)
|
||||
clearChan := make(chan struct{})
|
||||
closeChan := make(chan struct{})
|
||||
msw := &MockSyslogWriter{
|
||||
msw := &SyslogWriter{
|
||||
logged: []*LogMessage{},
|
||||
msgChan: msgChan,
|
||||
getChan: getChan,
|
||||
|
|
@ -91,7 +91,7 @@ func NewSyslogWriter() *MockSyslogWriter {
|
|||
return msw
|
||||
}
|
||||
|
||||
func (msw *MockSyslogWriter) write(m string, priority syslog.Priority) error {
|
||||
func (msw *SyslogWriter) write(m string, priority syslog.Priority) error {
|
||||
msw.msgChan <- &LogMessage{Message: m, Priority: priority}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ func (msw *MockSyslogWriter) write(m string, priority syslog.Priority) error {
|
|||
// Clear(), if applicable).
|
||||
//
|
||||
// The caller must not modify the returned slice or its elements.
|
||||
func (msw *MockSyslogWriter) GetAll() []*LogMessage {
|
||||
func (msw *SyslogWriter) GetAll() []*LogMessage {
|
||||
return <-msw.getChan
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ func (msw *MockSyslogWriter) GetAll() []*LogMessage {
|
|||
// is more important than performance.
|
||||
//
|
||||
// The caller must not modify the elements of the returned slice.
|
||||
func (msw *MockSyslogWriter) GetAllMatching(reString string) (matches []*LogMessage) {
|
||||
func (msw *SyslogWriter) GetAllMatching(reString string) (matches []*LogMessage) {
|
||||
re := regexp.MustCompile(reString)
|
||||
for _, logMsg := range <-msw.getChan {
|
||||
if re.MatchString(logMsg.Message) {
|
||||
|
|
@ -121,52 +121,52 @@ func (msw *MockSyslogWriter) GetAllMatching(reString string) (matches []*LogMess
|
|||
}
|
||||
|
||||
// Clear resets the log buffer.
|
||||
func (msw *MockSyslogWriter) Clear() {
|
||||
func (msw *SyslogWriter) Clear() {
|
||||
msw.clearChan <- struct{}{}
|
||||
}
|
||||
|
||||
// Close releases resources. No other methods may be called after this.
|
||||
func (msw *MockSyslogWriter) Close() error {
|
||||
func (msw *SyslogWriter) Close() error {
|
||||
msw.closeChan <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alert logs at LOG_ALERT
|
||||
func (msw *MockSyslogWriter) Alert(m string) error {
|
||||
func (msw *SyslogWriter) Alert(m string) error {
|
||||
return msw.write(m, syslog.LOG_ALERT)
|
||||
}
|
||||
|
||||
// Crit logs at LOG_CRIT
|
||||
func (msw *MockSyslogWriter) Crit(m string) error {
|
||||
func (msw *SyslogWriter) Crit(m string) error {
|
||||
return msw.write(m, syslog.LOG_CRIT)
|
||||
}
|
||||
|
||||
// Debug logs at LOG_DEBUG
|
||||
func (msw *MockSyslogWriter) Debug(m string) error {
|
||||
func (msw *SyslogWriter) Debug(m string) error {
|
||||
return msw.write(m, syslog.LOG_DEBUG)
|
||||
}
|
||||
|
||||
// Emerg logs at LOG_EMERG
|
||||
func (msw *MockSyslogWriter) Emerg(m string) error {
|
||||
func (msw *SyslogWriter) Emerg(m string) error {
|
||||
return msw.write(m, syslog.LOG_EMERG)
|
||||
}
|
||||
|
||||
// Err logs at LOG_ERR
|
||||
func (msw *MockSyslogWriter) Err(m string) error {
|
||||
func (msw *SyslogWriter) Err(m string) error {
|
||||
return msw.write(m, syslog.LOG_ERR)
|
||||
}
|
||||
|
||||
// Info logs at LOG_INFO
|
||||
func (msw *MockSyslogWriter) Info(m string) error {
|
||||
func (msw *SyslogWriter) Info(m string) error {
|
||||
return msw.write(m, syslog.LOG_INFO)
|
||||
}
|
||||
|
||||
// Notice logs at LOG_NOTICE
|
||||
func (msw *MockSyslogWriter) Notice(m string) error {
|
||||
func (msw *SyslogWriter) Notice(m string) error {
|
||||
return msw.write(m, syslog.LOG_NOTICE)
|
||||
}
|
||||
|
||||
// Warning logs at LOG_WARNING
|
||||
func (msw *MockSyslogWriter) Warning(m string) error {
|
||||
func (msw *SyslogWriter) Warning(m string) error {
|
||||
return msw.write(m, syslog.LOG_WARNING)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,17 +20,17 @@ import (
|
|||
"github.com/letsencrypt/boulder/core"
|
||||
)
|
||||
|
||||
// MockDNS is a mock
|
||||
type MockDNS struct {
|
||||
// DNSResolver is a mock
|
||||
type DNSResolver struct {
|
||||
}
|
||||
|
||||
// ExchangeOne is a mock
|
||||
func (mock *MockDNS) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) {
|
||||
func (mock *DNSResolver) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// LookupTXT is a mock
|
||||
func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error) {
|
||||
func (mock *DNSResolver) LookupTXT(hostname string) ([]string, time.Duration, error) {
|
||||
if hostname == "_acme-challenge.servfail.com" {
|
||||
return nil, 0, fmt.Errorf("SERVFAIL")
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error)
|
|||
}
|
||||
|
||||
// LookupHost is a mock
|
||||
func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
|
||||
func (mock *DNSResolver) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
|
||||
if hostname == "always.invalid" || hostname == "invalid.invalid" {
|
||||
return []net.IP{}, 0, nil
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error
|
|||
}
|
||||
|
||||
// LookupCNAME is a mock
|
||||
func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) {
|
||||
func (mock *DNSResolver) LookupCNAME(domain string) (string, time.Duration, error) {
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "cname-absent.com":
|
||||
return "absent.com.", 30, nil
|
||||
|
|
@ -76,7 +76,7 @@ func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) {
|
|||
}
|
||||
|
||||
// LookupDNAME is a mock
|
||||
func (mock *MockDNS) LookupDNAME(domain string) (string, time.Duration, error) {
|
||||
func (mock *DNSResolver) LookupDNAME(domain string) (string, time.Duration, error) {
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "cname-and-dname.com", "dname-present.com":
|
||||
return "dname-target.present.com.", time.Minute, nil
|
||||
|
|
@ -94,7 +94,7 @@ func (mock *MockDNS) LookupDNAME(domain string) (string, time.Duration, error) {
|
|||
}
|
||||
|
||||
// LookupCAA is a mock
|
||||
func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) {
|
||||
func (mock *DNSResolver) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) {
|
||||
var results []*dns.CAA
|
||||
var record dns.CAA
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
|
|
@ -118,7 +118,7 @@ func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error)
|
|||
}
|
||||
|
||||
// LookupMX is a mock
|
||||
func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) {
|
||||
func (mock *DNSResolver) LookupMX(domain string) ([]string, time.Duration, error) {
|
||||
switch strings.TrimRight(domain, ".") {
|
||||
case "letsencrypt.org":
|
||||
fallthrough
|
||||
|
|
@ -128,8 +128,8 @@ func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) {
|
|||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// MockSA is a mock
|
||||
type MockSA struct {
|
||||
// StorageAuthority is a mock
|
||||
type StorageAuthority struct {
|
||||
authorizedDomains map[string]bool
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ const (
|
|||
)
|
||||
|
||||
// GetRegistration is a mock
|
||||
func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) {
|
||||
func (sa *StorageAuthority) GetRegistration(id int64) (core.Registration, error) {
|
||||
if id == 100 {
|
||||
// Tag meaning "Missing"
|
||||
return core.Registration{}, errors.New("missing")
|
||||
|
|
@ -167,7 +167,7 @@ func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) {
|
|||
}
|
||||
|
||||
// GetRegistrationByKey is a mock
|
||||
func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) {
|
||||
func (sa *StorageAuthority) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) {
|
||||
var test1KeyPublic jose.JsonWebKey
|
||||
var test2KeyPublic jose.JsonWebKey
|
||||
test1KeyPublic.UnmarshalJSON([]byte(test1KeyPublicJSON))
|
||||
|
|
@ -187,7 +187,7 @@ func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration,
|
|||
}
|
||||
|
||||
// GetAuthorization is a mock
|
||||
func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) {
|
||||
func (sa *StorageAuthority) GetAuthorization(id string) (core.Authorization, error) {
|
||||
if id == "valid" {
|
||||
exp := time.Now().AddDate(100, 0, 0)
|
||||
return core.Authorization{
|
||||
|
|
@ -209,7 +209,7 @@ func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) {
|
|||
}
|
||||
|
||||
// GetCertificate is a mock
|
||||
func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) {
|
||||
func (sa *StorageAuthority) GetCertificate(serial string) (core.Certificate, error) {
|
||||
// Serial ee == 238.crt
|
||||
if serial == "0000000000000000000000000000000000ee" {
|
||||
certPemBytes, _ := ioutil.ReadFile("test/238.crt")
|
||||
|
|
@ -231,7 +231,7 @@ func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) {
|
|||
}
|
||||
|
||||
// GetCertificateStatus is a mock
|
||||
func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, error) {
|
||||
func (sa *StorageAuthority) GetCertificateStatus(serial string) (core.CertificateStatus, error) {
|
||||
// Serial ee == 238.crt
|
||||
if serial == "0000000000000000000000000000000000ee" {
|
||||
return core.CertificateStatus{
|
||||
|
|
@ -247,57 +247,57 @@ func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, e
|
|||
}
|
||||
|
||||
// AlreadyDeniedCSR is a mock
|
||||
func (sa *MockSA) AlreadyDeniedCSR([]string) (bool, error) {
|
||||
func (sa *StorageAuthority) AlreadyDeniedCSR([]string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// AddCertificate is a mock
|
||||
func (sa *MockSA) AddCertificate(certDER []byte, regID int64) (digest string, err error) {
|
||||
func (sa *StorageAuthority) AddCertificate(certDER []byte, regID int64) (digest string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// FinalizeAuthorization is a mock
|
||||
func (sa *MockSA) FinalizeAuthorization(authz core.Authorization) (err error) {
|
||||
func (sa *StorageAuthority) FinalizeAuthorization(authz core.Authorization) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// MarkCertificateRevoked is a mock
|
||||
func (sa *MockSA) MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode core.RevocationCode) (err error) {
|
||||
func (sa *StorageAuthority) MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode core.RevocationCode) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateOCSP is a mock
|
||||
func (sa *MockSA) UpdateOCSP(serial string, ocspResponse []byte) (err error) {
|
||||
func (sa *StorageAuthority) UpdateOCSP(serial string, ocspResponse []byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NewPendingAuthorization is a mock
|
||||
func (sa *MockSA) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) {
|
||||
func (sa *StorageAuthority) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NewRegistration is a mock
|
||||
func (sa *MockSA) NewRegistration(reg core.Registration) (regR core.Registration, err error) {
|
||||
func (sa *StorageAuthority) NewRegistration(reg core.Registration) (regR core.Registration, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// UpdatePendingAuthorization is a mock
|
||||
func (sa *MockSA) UpdatePendingAuthorization(authz core.Authorization) (err error) {
|
||||
func (sa *StorageAuthority) UpdatePendingAuthorization(authz core.Authorization) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateRegistration is a mock
|
||||
func (sa *MockSA) UpdateRegistration(reg core.Registration) (err error) {
|
||||
func (sa *StorageAuthority) UpdateRegistration(reg core.Registration) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetSCTReceipt is a mock
|
||||
func (sa *MockSA) GetSCTReceipt(serial string, logID string) (sct core.SignedCertificateTimestamp, err error) {
|
||||
func (sa *StorageAuthority) GetSCTReceipt(serial string, logID string) (sct core.SignedCertificateTimestamp, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// AddSCTReceipt is a mock
|
||||
func (sa *MockSA) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) {
|
||||
func (sa *StorageAuthority) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) {
|
||||
if sct.Signature == nil {
|
||||
err = fmt.Errorf("Bad times")
|
||||
}
|
||||
|
|
@ -305,8 +305,8 @@ func (sa *MockSA) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error)
|
|||
}
|
||||
|
||||
// GetLatestValidAuthorization is a mock
|
||||
func (sa *MockSA) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
if registrationId == 1 && identifier.Type == "dns" {
|
||||
func (sa *StorageAuthority) GetLatestValidAuthorization(registrationID int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
if registrationID == 1 && identifier.Type == "dns" {
|
||||
if sa.authorizedDomains[identifier.Value] || identifier.Value == "not-an-example.com" {
|
||||
exp := time.Now().AddDate(100, 0, 0)
|
||||
return core.Authorization{Status: core.StatusValid, RegistrationID: 1, Expires: &exp, Identifier: identifier}, nil
|
||||
|
|
@ -316,21 +316,21 @@ func (sa *MockSA) GetLatestValidAuthorization(registrationId int64, identifier c
|
|||
}
|
||||
|
||||
// CountCertificatesRange is a mock
|
||||
func (sa *MockSA) CountCertificatesRange(_, _ time.Time) (int64, error) {
|
||||
func (sa *StorageAuthority) CountCertificatesRange(_, _ time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// CountCertificatesByNames is a mock
|
||||
func (sa *MockSA) CountCertificatesByNames(_ []string, _, _ time.Time) (ret map[string]int, err error) {
|
||||
func (sa *StorageAuthority) CountCertificatesByNames(_ []string, _, _ time.Time) (ret map[string]int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// MockPublisher is a mock
|
||||
type MockPublisher struct {
|
||||
// Publisher is a mock
|
||||
type Publisher struct {
|
||||
// empty
|
||||
}
|
||||
|
||||
// SubmitToCT is a mock
|
||||
func (*MockPublisher) SubmitToCT([]byte) error {
|
||||
func (*Publisher) SubmitToCT([]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
|
@ -198,14 +199,14 @@ func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64
|
|||
// acceptable for the given identifier.
|
||||
//
|
||||
// Note: Current implementation is static, but future versions may not be.
|
||||
func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) (challenges []core.Challenge, combinations [][]int) {
|
||||
func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) {
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these lines
|
||||
challenges = []core.Challenge{
|
||||
core.SimpleHTTPChallenge(),
|
||||
core.DvsniChallenge(),
|
||||
}
|
||||
combinations = [][]int{
|
||||
[]int{0},
|
||||
[]int{1},
|
||||
core.SimpleHTTPChallenge(accountKey),
|
||||
core.DvsniChallenge(accountKey),
|
||||
core.HTTPChallenge01(accountKey),
|
||||
core.TLSSNIChallenge01(accountKey),
|
||||
}
|
||||
combinations = [][]int{[]int{0}, []int{1}, []int{2}, []int{3}}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
|
|
@ -167,17 +170,38 @@ func TestWillingToIssue(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var accountKeyJSON = `{
|
||||
"kty":"RSA",
|
||||
"n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
|
||||
"e":"AQAB"
|
||||
}`
|
||||
|
||||
func TestChallengesFor(t *testing.T) {
|
||||
pa, cleanup := paImpl(t)
|
||||
defer cleanup()
|
||||
|
||||
challenges, combinations := pa.ChallengesFor(core.AcmeIdentifier{})
|
||||
var accountKey *jose.JsonWebKey
|
||||
err := json.Unmarshal([]byte(accountKeyJSON), &accountKey)
|
||||
if err != nil {
|
||||
t.Errorf("Error unmarshaling JWK: %v", err)
|
||||
}
|
||||
|
||||
if len(challenges) != 2 || challenges[0].Type != core.ChallengeTypeSimpleHTTP ||
|
||||
challenges[1].Type != core.ChallengeTypeDVSNI {
|
||||
challenges, combinations, err := pa.ChallengesFor(core.AcmeIdentifier{}, accountKey)
|
||||
if err != nil {
|
||||
t.Errorf("Error generating challenges: %v", err)
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these tests
|
||||
if len(challenges) != 4 ||
|
||||
challenges[0].Type != core.ChallengeTypeSimpleHTTP ||
|
||||
challenges[1].Type != core.ChallengeTypeDVSNI ||
|
||||
challenges[2].Type != core.ChallengeTypeHTTP01 ||
|
||||
challenges[3].Type != core.ChallengeTypeTLSSNI01 {
|
||||
t.Error("Incorrect challenges returned")
|
||||
}
|
||||
if len(combinations) != 2 || combinations[0][0] != 0 || combinations[1][0] != 1 {
|
||||
if len(combinations) != 4 ||
|
||||
combinations[0][0] != 0 || combinations[1][0] != 1 ||
|
||||
combinations[2][0] != 2 || combinations[3][0] != 3 {
|
||||
t.Error("Incorrect combinations returned")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/sa"
|
||||
)
|
||||
|
||||
// LogDescription tells you how to connect to a log and verify its statements.
|
||||
type LogDescription struct {
|
||||
ID string
|
||||
URI string
|
||||
|
|
@ -35,6 +36,9 @@ type rawLogDescription struct {
|
|||
PublicKey string `json:"key"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses a simple JSON format for log descriptions. Both the
|
||||
// URI and the public key are expected to be strings. The public key is a
|
||||
// base64-encoded PKIX public key structure.
|
||||
func (logDesc *LogDescription) UnmarshalJSON(data []byte) error {
|
||||
var rawLogDesc rawLogDescription
|
||||
if err := json.Unmarshal(data, &rawLogDesc); err != nil {
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ func setup(t *testing.T, port, retries int) (PublisherImpl, *x509.Certificate) {
|
|||
})
|
||||
test.AssertNotError(t, err, "Couldn't create new Publisher")
|
||||
pub.issuerBundle = append(pub.issuerBundle, base64.StdEncoding.EncodeToString(intermediatePEM.Bytes))
|
||||
pub.SA = &mocks.MockSA{}
|
||||
pub.SA = &mocks.StorageAuthority{}
|
||||
|
||||
leafPEM, _ := pem.Decode([]byte(testLeaf))
|
||||
leaf, err := x509.ParseCertificate(leafPEM.Bytes)
|
||||
|
|
|
|||
|
|
@ -214,13 +214,8 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
|
|||
return authz, err
|
||||
}
|
||||
|
||||
// Create validations, but we have to update them with URIs later
|
||||
challenges, combinations := ra.PA.ChallengesFor(identifier)
|
||||
|
||||
for i := range challenges {
|
||||
// Add the account key used to generate the challenge
|
||||
challenges[i].AccountKey = ®.Key
|
||||
}
|
||||
// Create validations. The WFE will update them with URIs before sending them out.
|
||||
challenges, combinations, err := ra.PA.ChallengesFor(identifier, ®.Key)
|
||||
|
||||
expires := ra.clk.Now().Add(ra.authorizationLifetime)
|
||||
|
||||
|
|
@ -578,6 +573,12 @@ func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization
|
|||
}
|
||||
authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response)
|
||||
|
||||
// At this point, the challenge should be sane as a complete challenge
|
||||
if !authz.Challenges[challengeIndex].IsSane(true) {
|
||||
err = core.MalformedRequestError("Response does not complete challenge")
|
||||
return
|
||||
}
|
||||
|
||||
// Store the updated version
|
||||
if err = ra.SA.UpdatePendingAuthorization(authz); err != nil {
|
||||
// This can pretty much only happen when the client corrupts the Challenge
|
||||
|
|
|
|||
|
|
@ -101,9 +101,6 @@ var (
|
|||
}
|
||||
|
||||
ResponseIndex = 0
|
||||
Response = core.Challenge{
|
||||
Type: "simpleHttp",
|
||||
}
|
||||
|
||||
ExampleCSR = &x509.CertificateRequest{}
|
||||
|
||||
|
|
@ -116,11 +113,7 @@ var (
|
|||
Identifier: core.AcmeIdentifier{Type: "dns", Value: "not-example.com"},
|
||||
RegistrationID: 1,
|
||||
Status: "pending",
|
||||
Challenges: []core.Challenge{
|
||||
core.SimpleHTTPChallenge(),
|
||||
core.DvsniChallenge(),
|
||||
},
|
||||
Combinations: [][]int{[]int{0}, []int{1}},
|
||||
Combinations: [][]int{[]int{0}, []int{1}},
|
||||
}
|
||||
AuthzFinal = core.Authorization{}
|
||||
|
||||
|
|
@ -132,6 +125,16 @@ const (
|
|||
saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"
|
||||
)
|
||||
|
||||
func makeResponse(ch core.Challenge) (out core.Challenge, err error) {
|
||||
keyAuthorization, err := core.NewKeyAuthorization(ch.Token, ch.AccountKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = core.Challenge{KeyAuthorization: &keyAuthorization}
|
||||
return
|
||||
}
|
||||
|
||||
func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, clock.FakeClock, func()) {
|
||||
err := json.Unmarshal(AccountKeyJSONA, &AccountKeyA)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal public JWK")
|
||||
|
|
@ -144,7 +147,11 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
test.AssertNotError(t, err, "Failed to unmarshal private JWK")
|
||||
|
||||
err = json.Unmarshal(ShortKeyJSON, &ShortKey)
|
||||
test.AssertNotError(t, err, "Failed to unmarshall JWK")
|
||||
test.AssertNotError(t, err, "Failed to unmarshal JWK")
|
||||
|
||||
simpleHTTP := core.SimpleHTTPChallenge(&AccountKeyA)
|
||||
dvsni := core.DvsniChallenge(&AccountKeyA)
|
||||
AuthzInitial.Challenges = []core.Challenge{simpleHTTP, dvsni}
|
||||
|
||||
fc := clock.NewFake()
|
||||
|
||||
|
|
@ -195,7 +202,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
ValidityPeriod: time.Hour * 2190,
|
||||
NotAfter: time.Now().Add(time.Hour * 8761),
|
||||
Clk: fc,
|
||||
Publisher: &mocks.MockPublisher{},
|
||||
Publisher: &mocks.Publisher{},
|
||||
}
|
||||
cleanUp := func() {
|
||||
saDBCleanUp()
|
||||
|
|
@ -218,7 +225,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
ra.VA = va
|
||||
ra.CA = &ca
|
||||
ra.PA = pa
|
||||
ra.DNSResolver = &mocks.MockDNS{}
|
||||
ra.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
AuthzInitial.RegistrationID = Registration.ID
|
||||
|
||||
|
|
@ -256,38 +263,38 @@ func TestValidateContacts(t *testing.T) {
|
|||
|
||||
nStats, _ := statsd.NewNoopClient()
|
||||
|
||||
err := validateContacts([]*core.AcmeURL{}, &mocks.MockDNS{}, nStats)
|
||||
err := validateContacts([]*core.AcmeURL{}, &mocks.DNSResolver{}, nStats)
|
||||
test.AssertNotError(t, err, "No Contacts")
|
||||
|
||||
err = validateContacts([]*core.AcmeURL{tel}, &mocks.MockDNS{}, nStats)
|
||||
err = validateContacts([]*core.AcmeURL{tel}, &mocks.DNSResolver{}, nStats)
|
||||
test.AssertNotError(t, err, "Simple Telephone")
|
||||
|
||||
err = validateContacts([]*core.AcmeURL{validEmail}, &mocks.MockDNS{}, nStats)
|
||||
err = validateContacts([]*core.AcmeURL{validEmail}, &mocks.DNSResolver{}, nStats)
|
||||
test.AssertNotError(t, err, "Valid Email")
|
||||
|
||||
err = validateContacts([]*core.AcmeURL{invalidEmail}, &mocks.MockDNS{}, nStats)
|
||||
err = validateContacts([]*core.AcmeURL{invalidEmail}, &mocks.DNSResolver{}, nStats)
|
||||
test.AssertError(t, err, "Invalid Email")
|
||||
|
||||
err = validateContacts([]*core.AcmeURL{malformedEmail}, &mocks.MockDNS{}, nStats)
|
||||
err = validateContacts([]*core.AcmeURL{malformedEmail}, &mocks.DNSResolver{}, nStats)
|
||||
test.AssertError(t, err, "Malformed Email")
|
||||
|
||||
err = validateContacts([]*core.AcmeURL{ansible}, &mocks.MockDNS{}, nStats)
|
||||
err = validateContacts([]*core.AcmeURL{ansible}, &mocks.DNSResolver{}, nStats)
|
||||
test.AssertError(t, err, "Unknown scehme")
|
||||
}
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
_, err := validateEmail("an email`", &mocks.MockDNS{})
|
||||
_, err := validateEmail("an email`", &mocks.DNSResolver{})
|
||||
test.AssertError(t, err, "Malformed")
|
||||
|
||||
_, err = validateEmail("a@not.a.domain", &mocks.MockDNS{})
|
||||
_, err = validateEmail("a@not.a.domain", &mocks.DNSResolver{})
|
||||
test.AssertError(t, err, "Cannot resolve")
|
||||
t.Logf("No Resolve: %s", err)
|
||||
|
||||
_, err = validateEmail("a@example.com", &mocks.MockDNS{})
|
||||
_, err = validateEmail("a@example.com", &mocks.DNSResolver{})
|
||||
test.AssertError(t, err, "No MX Record")
|
||||
t.Logf("No MX: %s", err)
|
||||
|
||||
_, err = validateEmail("a@email.com", &mocks.MockDNS{})
|
||||
_, err = validateEmail("a@email.com", &mocks.DNSResolver{})
|
||||
test.AssertNotError(t, err, "Valid")
|
||||
}
|
||||
|
||||
|
|
@ -377,12 +384,22 @@ func TestNewAuthorization(t *testing.T) {
|
|||
test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending")
|
||||
|
||||
// TODO Verify that challenges are correct
|
||||
test.Assert(t, len(authz.Challenges) == 2, "Incorrect number of challenges returned")
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these lines
|
||||
test.Assert(t, len(authz.Challenges) == 4, "Incorrect number of challenges returned")
|
||||
test.Assert(t, authz.Challenges[0].Type == core.ChallengeTypeSimpleHTTP, "Challenge 0 not SimpleHTTP")
|
||||
test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI")
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete these lines
|
||||
test.Assert(t, authz.Challenges[2].Type == core.ChallengeTypeHTTP01, "Challenge 2 not http-00")
|
||||
test.Assert(t, authz.Challenges[3].Type == core.ChallengeTypeTLSSNI01, "Challenge 3 not tlssni-00")
|
||||
|
||||
test.Assert(t, authz.Challenges[0].IsSane(false), "Challenge 0 is not sane")
|
||||
test.Assert(t, authz.Challenges[1].IsSane(false), "Challenge 1 is not sane")
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete these lines
|
||||
test.Assert(t, authz.Challenges[2].IsSane(false), "Challenge 2 is not sane")
|
||||
test.Assert(t, authz.Challenges[3].IsSane(false), "Challenge 3 is not sane")
|
||||
|
||||
t.Log("DONE TestNewAuthorization")
|
||||
}
|
||||
|
||||
|
|
@ -394,7 +411,9 @@ func TestUpdateAuthorization(t *testing.T) {
|
|||
authz, err := ra.NewAuthorization(AuthzRequest, Registration.ID)
|
||||
test.AssertNotError(t, err, "NewAuthorization failed")
|
||||
|
||||
authz, err = ra.UpdateAuthorization(authz, ResponseIndex, Response)
|
||||
response, err := makeResponse(authz.Challenges[ResponseIndex])
|
||||
test.AssertNotError(t, err, "Unable to construct response to challenge")
|
||||
authz, err = ra.UpdateAuthorization(authz, ResponseIndex, response)
|
||||
test.AssertNotError(t, err, "UpdateAuthorization failed")
|
||||
|
||||
// Verify that returned authz same as DB
|
||||
|
|
@ -428,7 +447,9 @@ func TestUpdateAuthorizationReject(t *testing.T) {
|
|||
test.AssertNotError(t, err, "UpdateRegistration failed")
|
||||
|
||||
// Verify that the RA rejected the authorization request
|
||||
_, err = ra.UpdateAuthorization(authz, ResponseIndex, Response)
|
||||
response, err := makeResponse(authz.Challenges[ResponseIndex])
|
||||
test.AssertNotError(t, err, "Unable to construct response to challenge")
|
||||
_, err = ra.UpdateAuthorization(authz, ResponseIndex, response)
|
||||
test.AssertEquals(t, err, core.UnauthorizedError("Challenge cannot be updated with a different key"))
|
||||
|
||||
t.Log("DONE TestUpdateAuthorizationReject")
|
||||
|
|
@ -437,7 +458,9 @@ func TestUpdateAuthorizationReject(t *testing.T) {
|
|||
func TestOnValidationUpdateSuccess(t *testing.T) {
|
||||
_, sa, ra, fclk, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
authzUpdated, _ := sa.NewPendingAuthorization(AuthzInitial)
|
||||
authzUpdated, err := sa.NewPendingAuthorization(AuthzInitial)
|
||||
test.AssertNotError(t, err, "Failed to create new pending authz")
|
||||
|
||||
expires := fclk.Now().Add(300 * 24 * time.Hour)
|
||||
authzUpdated.Expires = &expires
|
||||
sa.UpdatePendingAuthorization(authzUpdated)
|
||||
|
|
@ -632,7 +655,7 @@ func TestDomainsForRateLimiting(t *testing.T) {
|
|||
}
|
||||
|
||||
type mockSAWithNameCounts struct {
|
||||
mocks.MockSA
|
||||
mocks.StorageAuthority
|
||||
nameCounts map[string]int
|
||||
t *testing.T
|
||||
clk clock.FakeClock
|
||||
|
|
|
|||
|
|
@ -200,16 +200,16 @@ func (rpc *AmqpRPCServer) Handle(method string, handler func([]byte) ([]byte, er
|
|||
rpc.dispatchTable[method] = handler
|
||||
}
|
||||
|
||||
// RPCError is a JSON wrapper for error as it cannot be un/marshalled
|
||||
// rpcError is a JSON wrapper for error as it cannot be un/marshalled
|
||||
// due to type interface{}.
|
||||
type RPCError struct {
|
||||
type rpcError struct {
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// Wraps a error in a RPCError so it can be marshalled to
|
||||
// Wraps a error in a rpcError so it can be marshalled to
|
||||
// JSON.
|
||||
func wrapError(err error) (rpcError RPCError) {
|
||||
func wrapError(err error) (rpcError rpcError) {
|
||||
if err != nil {
|
||||
rpcError.Value = err.Error()
|
||||
switch err.(type) {
|
||||
|
|
@ -240,8 +240,8 @@ func wrapError(err error) (rpcError RPCError) {
|
|||
return
|
||||
}
|
||||
|
||||
// Unwraps a RPCError and returns the correct error type.
|
||||
func unwrapError(rpcError RPCError) (err error) {
|
||||
// Unwraps a rpcError and returns the correct error type.
|
||||
func unwrapError(rpcError rpcError) (err error) {
|
||||
if rpcError.Value != "" {
|
||||
switch rpcError.Type {
|
||||
case "InternalServerError":
|
||||
|
|
@ -273,11 +273,11 @@ func unwrapError(rpcError RPCError) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// RPCResponse is a stuct for wire-representation of response messages
|
||||
// rpcResponse is a stuct for wire-representation of response messages
|
||||
// used by DispatchSync
|
||||
type RPCResponse struct {
|
||||
type rpcResponse struct {
|
||||
ReturnVal []byte `json:"returnVal,omitempty"`
|
||||
Error RPCError `json:"error,omitempty"`
|
||||
Error rpcError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// AmqpChannel sets a AMQP connection up using SSL if configuration is provided
|
||||
|
|
@ -362,7 +362,7 @@ func (rpc *AmqpRPCServer) processMessage(msg amqp.Delivery) {
|
|||
rpc.log.Audit(fmt.Sprintf(" [s<][%s][%s] Misrouted message: %s - %s - %s", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(msg.Body), msg.CorrelationId))
|
||||
return
|
||||
}
|
||||
var response RPCResponse
|
||||
var response rpcResponse
|
||||
var err error
|
||||
response.ReturnVal, err = cb(msg.Body)
|
||||
response.Error = wrapError(err)
|
||||
|
|
@ -407,7 +407,7 @@ func (rpc *AmqpRPCServer) replyTooManyRequests(msg amqp.Delivery) {
|
|||
// until a fatal error is returned or AmqpRPCServer.Stop() is called and all
|
||||
// remaining messages are processed.
|
||||
func (rpc *AmqpRPCServer) Start(c cmd.Config) error {
|
||||
tooManyGoroutines := RPCResponse{
|
||||
tooManyGoroutines := rpcResponse{
|
||||
Error: wrapError(core.TooManyRPCRequestsError("RPC server has spawned too many Goroutines")),
|
||||
}
|
||||
tooManyRequestsResponse, err := json.Marshal(tooManyGoroutines)
|
||||
|
|
@ -632,7 +632,7 @@ func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []b
|
|||
callStarted := time.Now()
|
||||
select {
|
||||
case jsonResponse := <-rpc.Dispatch(method, body):
|
||||
var rpcResponse RPCResponse
|
||||
var rpcResponse rpcResponse
|
||||
err = json.Unmarshal(jsonResponse, &rpcResponse)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// RPCClient describes the functions an RPC Client performs
|
||||
type RPCClient interface {
|
||||
// Client describes the functions an RPC Client performs
|
||||
type Client interface {
|
||||
SetTimeout(time.Duration)
|
||||
Dispatch(string, []byte) chan []byte
|
||||
DispatchSync(string, []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// RPCServer describes the functions an RPC Server performs
|
||||
type RPCServer interface {
|
||||
// Server describes the functions an RPC Server performs
|
||||
type Server interface {
|
||||
Handle(string, func([]byte) ([]byte, error))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ func errorCondition(method string, err error, obj interface{}) {
|
|||
}
|
||||
|
||||
// NewRegistrationAuthorityServer constructs an RPC server
|
||||
func NewRegistrationAuthorityServer(rpc RPCServer, impl core.RegistrationAuthority) error {
|
||||
func NewRegistrationAuthorityServer(rpc Server, impl core.RegistrationAuthority) error {
|
||||
log := blog.GetAuditLogger()
|
||||
|
||||
rpc.Handle(MethodNewRegistration, func(req []byte) (response []byte, err error) {
|
||||
|
|
@ -345,11 +345,11 @@ func NewRegistrationAuthorityServer(rpc RPCServer, impl core.RegistrationAuthori
|
|||
|
||||
// RegistrationAuthorityClient represents an RA RPC client
|
||||
type RegistrationAuthorityClient struct {
|
||||
rpc RPCClient
|
||||
rpc Client
|
||||
}
|
||||
|
||||
// NewRegistrationAuthorityClient constructs an RPC client
|
||||
func NewRegistrationAuthorityClient(client RPCClient) (rac RegistrationAuthorityClient, err error) {
|
||||
func NewRegistrationAuthorityClient(client Client) (rac RegistrationAuthorityClient, err error) {
|
||||
rac = RegistrationAuthorityClient{rpc: client}
|
||||
return
|
||||
}
|
||||
|
|
@ -496,7 +496,7 @@ func (rac RegistrationAuthorityClient) OnValidationUpdate(authz core.Authorizati
|
|||
//
|
||||
// ValidationAuthorityClient / Server
|
||||
// -> UpdateValidations
|
||||
func NewValidationAuthorityServer(rpc RPCServer, impl core.ValidationAuthority) (err error) {
|
||||
func NewValidationAuthorityServer(rpc Server, impl core.ValidationAuthority) (err error) {
|
||||
rpc.Handle(MethodUpdateValidations, func(req []byte) (response []byte, err error) {
|
||||
var vaReq validationRequest
|
||||
if err = json.Unmarshal(req, &vaReq); err != nil {
|
||||
|
|
@ -540,11 +540,11 @@ func NewValidationAuthorityServer(rpc RPCServer, impl core.ValidationAuthority)
|
|||
|
||||
// ValidationAuthorityClient represents an RPC client for the VA
|
||||
type ValidationAuthorityClient struct {
|
||||
rpc RPCClient
|
||||
rpc Client
|
||||
}
|
||||
|
||||
// NewValidationAuthorityClient constructs an RPC client
|
||||
func NewValidationAuthorityClient(client RPCClient) (vac ValidationAuthorityClient, err error) {
|
||||
func NewValidationAuthorityClient(client Client) (vac ValidationAuthorityClient, err error) {
|
||||
vac = ValidationAuthorityClient{rpc: client}
|
||||
return
|
||||
}
|
||||
|
|
@ -589,7 +589,8 @@ func (vac ValidationAuthorityClient) CheckCAARecords(ident core.AcmeIdentifier)
|
|||
return
|
||||
}
|
||||
|
||||
func NewPublisherServer(rpc RPCServer, impl core.Publisher) (err error) {
|
||||
// NewPublisherServer creates a new server that wraps a CT publisher
|
||||
func NewPublisherServer(rpc Server, impl core.Publisher) (err error) {
|
||||
rpc.Handle(MethodSubmitToCT, func(req []byte) (response []byte, err error) {
|
||||
err = impl.SubmitToCT(req)
|
||||
return
|
||||
|
|
@ -600,11 +601,11 @@ func NewPublisherServer(rpc RPCServer, impl core.Publisher) (err error) {
|
|||
|
||||
// PublisherClient is a client to communicate with the Publisher Authority
|
||||
type PublisherClient struct {
|
||||
rpc RPCClient
|
||||
rpc Client
|
||||
}
|
||||
|
||||
// NewPublisherClient constructs an RPC client
|
||||
func NewPublisherClient(client RPCClient) (pub PublisherClient, err error) {
|
||||
func NewPublisherClient(client Client) (pub PublisherClient, err error) {
|
||||
pub = PublisherClient{rpc: client}
|
||||
return
|
||||
}
|
||||
|
|
@ -619,7 +620,7 @@ func (pub PublisherClient) SubmitToCT(der []byte) (err error) {
|
|||
//
|
||||
// CertificateAuthorityClient / Server
|
||||
// -> IssueCertificate
|
||||
func NewCertificateAuthorityServer(rpc RPCServer, impl core.CertificateAuthority) (err error) {
|
||||
func NewCertificateAuthorityServer(rpc Server, impl core.CertificateAuthority) (err error) {
|
||||
rpc.Handle(MethodIssueCertificate, func(req []byte) (response []byte, err error) {
|
||||
var icReq issueCertificateRequest
|
||||
err = json.Unmarshal(req, &icReq)
|
||||
|
|
@ -686,11 +687,11 @@ func NewCertificateAuthorityServer(rpc RPCServer, impl core.CertificateAuthority
|
|||
|
||||
// CertificateAuthorityClient is a client to communicate with the CA.
|
||||
type CertificateAuthorityClient struct {
|
||||
rpc RPCClient
|
||||
rpc Client
|
||||
}
|
||||
|
||||
// NewCertificateAuthorityClient constructs an RPC client
|
||||
func NewCertificateAuthorityClient(client RPCClient) (cac CertificateAuthorityClient, err error) {
|
||||
func NewCertificateAuthorityClient(client Client) (cac CertificateAuthorityClient, err error) {
|
||||
cac = CertificateAuthorityClient{rpc: client}
|
||||
return
|
||||
}
|
||||
|
|
@ -752,7 +753,7 @@ func (cac CertificateAuthorityClient) GenerateOCSP(signRequest core.OCSPSigningR
|
|||
}
|
||||
|
||||
// NewStorageAuthorityServer constructs an RPC server
|
||||
func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error {
|
||||
func NewStorageAuthorityServer(rpc Server, impl core.StorageAuthority) error {
|
||||
rpc.Handle(MethodUpdateRegistration, func(req []byte) (response []byte, err error) {
|
||||
var reg core.Registration
|
||||
if err = json.Unmarshal(req, ®); err != nil {
|
||||
|
|
@ -1055,7 +1056,7 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error
|
|||
}
|
||||
|
||||
sct, err := impl.GetSCTReceipt(gsctReq.Serial, gsctReq.LogID)
|
||||
jsonResponse, err := json.Marshal(core.RPCSignedCertificateTimestamp(sct))
|
||||
jsonResponse, err := json.Marshal(core.SignedCertificateTimestamp(sct))
|
||||
if err != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
errorCondition(MethodGetSCTReceipt, err, req)
|
||||
|
|
@ -1066,7 +1067,7 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error
|
|||
})
|
||||
|
||||
rpc.Handle(MethodAddSCTReceipt, func(req []byte) (response []byte, err error) {
|
||||
var sct core.RPCSignedCertificateTimestamp
|
||||
var sct core.SignedCertificateTimestamp
|
||||
err = json.Unmarshal(req, &sct)
|
||||
if err != nil {
|
||||
// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
|
||||
|
|
@ -1089,11 +1090,11 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error
|
|||
|
||||
// StorageAuthorityClient is a client to communicate with the Storage Authority
|
||||
type StorageAuthorityClient struct {
|
||||
rpc RPCClient
|
||||
rpc Client
|
||||
}
|
||||
|
||||
// NewStorageAuthorityClient constructs an RPC client
|
||||
func NewStorageAuthorityClient(client RPCClient) (sac StorageAuthorityClient, err error) {
|
||||
func NewStorageAuthorityClient(client Client) (sac StorageAuthorityClient, err error) {
|
||||
sac = StorageAuthorityClient{rpc: client}
|
||||
return
|
||||
}
|
||||
|
|
@ -1145,10 +1146,10 @@ func (cac StorageAuthorityClient) GetAuthorization(id string) (authz core.Author
|
|||
}
|
||||
|
||||
// GetLatestValidAuthorization sends a request to get an Authorization by RegID, Identifier
|
||||
func (cac StorageAuthorityClient) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
func (cac StorageAuthorityClient) GetLatestValidAuthorization(registrationID int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
|
||||
var lvar latestValidAuthorizationRequest
|
||||
lvar.RegID = registrationId
|
||||
lvar.RegID = registrationID
|
||||
lvar.Identifier = identifier
|
||||
|
||||
data, err := json.Marshal(lvar)
|
||||
|
|
@ -1369,6 +1370,8 @@ func (cac StorageAuthorityClient) CountCertificatesByNames(names []string, earli
|
|||
return
|
||||
}
|
||||
|
||||
// GetSCTReceipt retrieves an SCT according to the serial number of a certificate
|
||||
// and the logID of the log to which it was submitted.
|
||||
func (cac StorageAuthorityClient) GetSCTReceipt(serial string, logID string) (receipt core.SignedCertificateTimestamp, err error) {
|
||||
var gsctReq struct {
|
||||
Serial string
|
||||
|
|
@ -1391,6 +1394,7 @@ func (cac StorageAuthorityClient) GetSCTReceipt(serial string, logID string) (re
|
|||
return
|
||||
}
|
||||
|
||||
// AddSCTReceipt adds a new SCT to the database.
|
||||
func (cac StorageAuthorityClient) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) {
|
||||
data, err := json.Marshal(sct)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
ALTER TABLE `challenges` ADD COLUMN (`keyAuthorization` varchar(255));
|
||||
ALTER TABLE `challenges` DROP COLUMN `validation`;
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
ALTER TABLE `challenges` DROP COLUMN `keyAuthorization`;
|
||||
ALTER TABLE `challenges` ADD COLUMN (`validation` mediumblob);
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
// Provide access to the MySQL driver
|
||||
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
|
||||
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
|
|
|||
19
sa/model.go
19
sa/model.go
|
|
@ -35,6 +35,8 @@ type regModel struct {
|
|||
}
|
||||
|
||||
// challModel is the description of a core.Challenge in the database
|
||||
//
|
||||
// The Validation field is a stub; the column is only there for backward compatibility.
|
||||
type challModel struct {
|
||||
ID int64 `db:"id"`
|
||||
AuthorizationID string `db:"authorizationID"`
|
||||
|
|
@ -45,7 +47,7 @@ type challModel struct {
|
|||
Validated *time.Time `db:"validated"`
|
||||
Token string `db:"token"`
|
||||
TLS *bool `db:"tls"`
|
||||
Validation []byte `db:"validation"`
|
||||
KeyAuthorization string `db:"keyAuthorization"`
|
||||
ValidationRecord []byte `db:"validationRecord"`
|
||||
AccountKey []byte `db:"accountKey"`
|
||||
|
||||
|
|
@ -99,11 +101,12 @@ func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
|
|||
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.KeyAuthorization != nil {
|
||||
kaString := c.KeyAuthorization.String()
|
||||
if len(kaString) > 255 {
|
||||
return nil, fmt.Errorf("Key authorization is too large to store in the database")
|
||||
}
|
||||
cm.KeyAuthorization = kaString
|
||||
}
|
||||
if c.Error != nil {
|
||||
errJSON, err := json.Marshal(c.Error)
|
||||
|
|
@ -147,12 +150,12 @@ func modelToChallenge(cm *challModel) (core.Challenge, error) {
|
|||
Token: cm.Token,
|
||||
TLS: cm.TLS,
|
||||
}
|
||||
if len(cm.Validation) > 0 {
|
||||
val, err := jose.ParseSigned(string(cm.Validation))
|
||||
if len(cm.KeyAuthorization) > 0 {
|
||||
ka, err := core.NewKeyAuthorizationFromString(cm.KeyAuthorization)
|
||||
if err != nil {
|
||||
return core.Challenge{}, err
|
||||
}
|
||||
c.Validation = val
|
||||
c.KeyAuthorization = &ka
|
||||
}
|
||||
if len(cm.Error) > 0 {
|
||||
var problem core.ProblemDetails
|
||||
|
|
|
|||
|
|
@ -212,16 +212,16 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz
|
|||
}
|
||||
|
||||
// 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) {
|
||||
func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationID int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
||||
ident, err := json.Marshal(identifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var auth core.Authorization
|
||||
err = ssa.dbMap.SelectOne(&auth, "SELECT id FROM authz "+
|
||||
"WHERE identifier = :identifier AND registrationID = :registrationId AND status = 'valid' "+
|
||||
"WHERE identifier = :identifier AND registrationID = :registrationID AND status = 'valid' "+
|
||||
"ORDER BY expires DESC LIMIT 1",
|
||||
map[string]interface{}{"identifier": string(ident), "registrationId": registrationId})
|
||||
map[string]interface{}{"identifier": string(ident), "registrationID": registrationID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -229,6 +229,8 @@ func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationId int64
|
|||
return ssa.GetAuthorization(auth.ID)
|
||||
}
|
||||
|
||||
// TooManyCertificatesError indicates that the number of certificates returned by
|
||||
// CountCertificates exceeded the hard-coded limit of 10,000 certificates.
|
||||
type TooManyCertificatesError string
|
||||
|
||||
func (t TooManyCertificatesError) Error() string {
|
||||
|
|
|
|||
|
|
@ -168,10 +168,10 @@ func TestAddAuthorization(t *testing.T) {
|
|||
}
|
||||
|
||||
func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority) (authz core.Authorization) {
|
||||
return CreateDomainAuthWithRegId(t, domainName, sa, 42)
|
||||
return CreateDomainAuthWithRegID(t, domainName, sa, 42)
|
||||
}
|
||||
|
||||
func CreateDomainAuthWithRegId(t *testing.T, domainName string, sa *SQLStorageAuthority, regID int64) (authz core.Authorization) {
|
||||
func CreateDomainAuthWithRegID(t *testing.T, domainName string, sa *SQLStorageAuthority, regID int64) (authz core.Authorization) {
|
||||
|
||||
// create pending auth
|
||||
authz, err := sa.NewPendingAuthorization(core.Authorization{RegistrationID: regID, Challenges: []core.Challenge{core.Challenge{}}})
|
||||
|
|
@ -212,7 +212,7 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) {
|
|||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
|
||||
// authorize "example.org"
|
||||
authz = CreateDomainAuthWithRegId(t, "example.org", sa, reg.ID)
|
||||
authz = CreateDomainAuthWithRegID(t, "example.org", sa, reg.ID)
|
||||
|
||||
// finalize auth
|
||||
authz.Status = core.StatusValid
|
||||
|
|
@ -243,7 +243,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
// create invalid authz
|
||||
authz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
|
||||
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
|
||||
|
|
@ -255,7 +255,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
test.AssertError(t, err, "Should not have found a valid auth for "+domain)
|
||||
|
||||
// create valid auth
|
||||
authz = CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
|
||||
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
|
||||
|
|
@ -271,7 +271,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
test.AssertEquals(t, authz.RegistrationID, reg.ID)
|
||||
|
||||
// create a newer auth
|
||||
newAuthz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID)
|
||||
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
|
||||
|
|
|
|||
24
test.sh
24
test.sh
|
|
@ -63,13 +63,23 @@ function run() {
|
|||
}
|
||||
|
||||
function run_and_comment() {
|
||||
if [ "x${TRAVIS}" = "x" ] || [ "${TRAVIS_PULL_REQUEST}" == "false" ] || [ ! -f "${GITHUB_SECRET_FILE}" ] ; then
|
||||
run "$@"
|
||||
echo "$@"
|
||||
result=$("$@" 2>&1)
|
||||
echo ${result}
|
||||
|
||||
if [ "x${result}" == "x" ]; then
|
||||
update_status --state success
|
||||
echo "Success: $@"
|
||||
else
|
||||
result=$(run "$@")
|
||||
local status=$?
|
||||
# Only send a comment if exit code > 0
|
||||
if [ ${status} -ne 0 ] ; then
|
||||
FAILURE=1
|
||||
update_status --state failure
|
||||
echo "[!] FAILURE: $@"
|
||||
fi
|
||||
|
||||
# If this is a travis PR run, post a comment
|
||||
if [ "x${TRAVIS}" != "x" ] && [ "${TRAVIS_PULL_REQUEST}" != "false" ] && [ -f "${GITHUB_SECRET_FILE}" ] ; then
|
||||
# If the output is non-empty, post a comment and mark this as a failure
|
||||
if [ "x${result}" -ne "x" ] ; then
|
||||
echo $'```\n'${result}$'\n```' | github-pr-status --authfile $GITHUB_SECRET_FILE \
|
||||
--owner "letsencrypt" --repo "boulder" \
|
||||
comment --pr "${TRAVIS_PULL_REQUEST}" -b -
|
||||
|
|
@ -157,7 +167,7 @@ fi
|
|||
#
|
||||
if [[ "$RUN" =~ "lint" ]] ; then
|
||||
start_context "test/golint"
|
||||
[ -x "$(which golint)" ] && run golint ./...
|
||||
run_and_comment golint -min_confidence=0.81 ./...
|
||||
end_context #test/golint
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -134,9 +134,9 @@
|
|||
"userAgent": "boulder",
|
||||
"debugAddr": "localhost:8004",
|
||||
"portConfig": {
|
||||
"simpleHTTPPort": 5001,
|
||||
"simpleHTTPSPort": 5001,
|
||||
"dvsniPort": 5001
|
||||
"httpPort": 5001,
|
||||
"httpsPort": 5001,
|
||||
"tlsPort": 5001
|
||||
},
|
||||
"maxConcurrentRPCServerRequests": 16
|
||||
},
|
||||
|
|
|
|||
|
|
@ -137,6 +137,12 @@ module.exports = {
|
|||
return forge.pki.certificateRequestToPem(c);
|
||||
},
|
||||
|
||||
thumbprint: function(publicKey) {
|
||||
// Only handling RSA keys
|
||||
input = bytesToBuffer('{"e":"'+ publicKey.e + '","kty":"RSA","n":"'+ publicKey.n +'"}');
|
||||
return util.b64enc(crypto.createHash('sha256').update(input).digest());
|
||||
},
|
||||
|
||||
///// SIGNATURE GENERATION / VERIFICATION
|
||||
|
||||
generateSignature: function(keyPair, payload, nonce) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ var cryptoUtil = require("./crypto-util");
|
|||
var child_process = require('child_process');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var inquirer = require("inquirer");
|
||||
var request = require('request');
|
||||
var url = require('url');
|
||||
|
|
@ -103,7 +102,7 @@ var cliOptions = cli.parse({
|
|||
|
||||
var state = {
|
||||
certPrivateKey: null,
|
||||
accountPrivateKey: null,
|
||||
accountKeyPair: null,
|
||||
|
||||
newRegistrationURL: cliOptions.newReg,
|
||||
registrationURL: "",
|
||||
|
|
@ -221,8 +220,8 @@ function makeAccountKeyPair(answers) {
|
|||
console.log(error);
|
||||
process.exit(1);
|
||||
}
|
||||
state.accountPrivateKey = cryptoUtil.importPemPrivateKey(fs.readFileSync("account-key.pem"));
|
||||
state.acme = new Acme(state.accountPrivateKey);
|
||||
state.accountKeyPair = cryptoUtil.importPemPrivateKey(fs.readFileSync("account-key.pem"));
|
||||
state.acme = new Acme(state.accountKeyPair);
|
||||
|
||||
console.log();
|
||||
if (cliOptions.email) {
|
||||
|
|
@ -348,26 +347,20 @@ function getReadyToValidate(err, resp, body) {
|
|||
|
||||
var authz = JSON.parse(body);
|
||||
|
||||
var simpleHttp = authz.challenges.filter(function(x) { return x.type == "simpleHttp"; });
|
||||
if (simpleHttp.length == 0) {
|
||||
var httpChallenges = authz.challenges.filter(function(x) { return x.type == "http-01"; });
|
||||
if (httpChallenges.length == 0) {
|
||||
console.log("The server didn't offer any challenges we can handle.");
|
||||
process.exit(1);
|
||||
}
|
||||
var challenge = httpChallenges[0];
|
||||
|
||||
// Construct a key authorization for this token and key
|
||||
var thumbprint = cryptoUtil.thumbprint(state.accountKeyPair.publicKey);
|
||||
var keyAuthorization = challenge.token + "." + thumbprint;
|
||||
|
||||
var challenge = simpleHttp[0];
|
||||
var path = cryptoUtil.randomString(8) + ".txt";
|
||||
var challengePath = ".well-known/acme-challenge/" + challenge.token;
|
||||
state.responseURL = challenge["uri"];
|
||||
state.path = path;
|
||||
|
||||
// Sign validation JWS
|
||||
var validationObject = JSON.stringify({
|
||||
type: "simpleHttp",
|
||||
token: challenge.token,
|
||||
tls: true
|
||||
})
|
||||
var validationJWS = cryptoUtil.generateSignature(state.accountPrivateKey,
|
||||
new Buffer(validationObject))
|
||||
state.path = challengePath;
|
||||
|
||||
// For local, test-mode validation
|
||||
function httpResponder(req, response) {
|
||||
|
|
@ -376,18 +369,16 @@ function getReadyToValidate(err, resp, body) {
|
|||
if ((host.split(/:/)[0] === state.domain || /localhost/.test(state.newRegistrationURL)) &&
|
||||
req.method === "GET" &&
|
||||
req.url == "/" + challengePath) {
|
||||
response.writeHead(200, {"Content-Type": "application/jose+json"});
|
||||
response.end(JSON.stringify(validationJWS));
|
||||
console.log("Providing key authorization:", keyAuthorization);
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
response.end(keyAuthorization);
|
||||
} else {
|
||||
console.log("Got invalid request for", req.method, host, req.url);
|
||||
response.writeHead(404, {"Content-Type": "text/plain"});
|
||||
response.end("");
|
||||
}
|
||||
}
|
||||
state.httpServer = https.createServer({
|
||||
cert: fs.readFileSync("temp-cert.pem"),
|
||||
key: fs.readFileSync(state.keyFile)
|
||||
}, httpResponder)
|
||||
state.httpServer = http.createServer(httpResponder)
|
||||
if (/localhost/.test(state.newRegistrationURL)) {
|
||||
state.httpServer.listen(5001)
|
||||
} else {
|
||||
|
|
@ -397,8 +388,7 @@ function getReadyToValidate(err, resp, body) {
|
|||
cli.spinner("Validating domain");
|
||||
post(state.responseURL, {
|
||||
resource: "challenge",
|
||||
path: state.path,
|
||||
tls: true,
|
||||
keyAuthorization: keyAuthorization,
|
||||
}, ensureValidation);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,24 +42,24 @@ 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
|
||||
simpleHTTPPort int
|
||||
simpleHTTPSPort int
|
||||
dvsniPort int
|
||||
UserAgent string
|
||||
stats statsd.Statter
|
||||
clk clock.Clock
|
||||
RA core.RegistrationAuthority
|
||||
log *blog.AuditLogger
|
||||
DNSResolver core.DNSResolver
|
||||
IssuerDomain string
|
||||
httpPort int
|
||||
httpsPort int
|
||||
tlsPort int
|
||||
UserAgent string
|
||||
stats statsd.Statter
|
||||
clk clock.Clock
|
||||
}
|
||||
|
||||
// PortConfig specifies what ports the VA should call to on the remote
|
||||
// host when performing its checks.
|
||||
type PortConfig struct {
|
||||
SimpleHTTPPort int
|
||||
SimpleHTTPSPort int
|
||||
DVSNIPort int
|
||||
HTTPPort int
|
||||
HTTPSPort int
|
||||
TLSPort int
|
||||
}
|
||||
|
||||
// NewValidationAuthorityImpl constructs a new VA
|
||||
|
|
@ -67,12 +67,12 @@ func NewValidationAuthorityImpl(pc *PortConfig, stats statsd.Statter, clk clock.
|
|||
logger := blog.GetAuditLogger()
|
||||
logger.Notice("Validation Authority Starting")
|
||||
return &ValidationAuthorityImpl{
|
||||
log: logger,
|
||||
simpleHTTPPort: pc.SimpleHTTPPort,
|
||||
simpleHTTPSPort: pc.SimpleHTTPSPort,
|
||||
dvsniPort: pc.DVSNIPort,
|
||||
stats: stats,
|
||||
clk: clk,
|
||||
log: logger,
|
||||
httpPort: pc.HTTPPort,
|
||||
httpsPort: pc.HTTPSPort,
|
||||
tlsPort: pc.TLSPort,
|
||||
stats: stats,
|
||||
clk: clk,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +86,7 @@ type verificationRequestEvent struct {
|
|||
Error string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
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")
|
||||
|
|
@ -178,7 +179,7 @@ func (d *dialer) Dial(_, _ string) (net.Conn, error) {
|
|||
// resolveAndConstructDialer gets the prefered address using va.getAddr and returns
|
||||
// the chosen address and dialer for that address and correct port.
|
||||
func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name, defaultPort string) (dialer, *core.ProblemDetails) {
|
||||
port := fmt.Sprintf("%d", va.simpleHTTPPort)
|
||||
port := fmt.Sprintf("%d", va.httpPort)
|
||||
if defaultPort != "" {
|
||||
port = defaultPort
|
||||
}
|
||||
|
|
@ -200,50 +201,37 @@ func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name, defaultPort s
|
|||
|
||||
// Validation methods
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, path string, useTLS bool, input core.Challenge) ([]byte, core.Challenge, error) {
|
||||
emptyBody := []byte{}
|
||||
challenge := input
|
||||
|
||||
if identifier.Type != core.IdentifierDNS {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.MalformedProblem,
|
||||
Detail: "Identifier type for SimpleHTTP was not DNS",
|
||||
}
|
||||
|
||||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] Identifier failure", identifier))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
host := identifier.Value
|
||||
var scheme string
|
||||
var port int
|
||||
if input.TLS == nil || (input.TLS != nil && *input.TLS) {
|
||||
scheme := "http"
|
||||
port := va.httpPort
|
||||
if useTLS {
|
||||
scheme = "https"
|
||||
port = va.simpleHTTPSPort
|
||||
} else {
|
||||
scheme = "http"
|
||||
port = va.simpleHTTPPort
|
||||
port = va.httpsPort
|
||||
}
|
||||
portString := fmt.Sprintf("%d", port)
|
||||
portString := strconv.Itoa(port)
|
||||
hostPort := net.JoinHostPort(host, portString)
|
||||
|
||||
url := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: hostPort,
|
||||
Path: fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token),
|
||||
Path: path,
|
||||
}
|
||||
|
||||
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
|
||||
va.log.Audit(fmt.Sprintf("Attempting to validate Simple%s for %s", strings.ToUpper(scheme), url))
|
||||
va.log.Audit(fmt.Sprintf("Attempting to validate %s for %s", challenge.Type, url))
|
||||
httpRequest, err := http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.MalformedProblem,
|
||||
Detail: "URL provided for SimpleHTTP was invalid",
|
||||
Detail: "URL provided for HTTP was invalid",
|
||||
}
|
||||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] HTTP failure: %s", identifier, err))
|
||||
va.log.Debug(fmt.Sprintf("%s [%s] HTTP failure: %s", challenge.Type, identifier, err))
|
||||
challenge.Status = core.StatusInvalid
|
||||
return challenge, err
|
||||
return emptyBody, challenge, err
|
||||
}
|
||||
|
||||
if va.UserAgent != "" {
|
||||
|
|
@ -257,7 +245,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
|
|||
if prob != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = prob
|
||||
return challenge, prob
|
||||
return emptyBody, challenge, prob
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
|
|
@ -303,7 +291,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
|
|||
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))
|
||||
va.log.Info(fmt.Sprintf("%s [%s] redirect from %q to %q [%s]", challenge.Type, identifier, via[len(via)-1].URL.String(), req.URL.String(), dialer.record.AddressUsed))
|
||||
return nil
|
||||
}
|
||||
client := http.Client{
|
||||
|
|
@ -319,7 +307,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
|
|||
Detail: fmt.Sprintf("Could not connect to %s", url),
|
||||
}
|
||||
va.log.Debug(strings.Join([]string{challenge.Error.Error(), err.Error()}, ": "))
|
||||
return challenge, err
|
||||
return emptyBody, challenge, err
|
||||
}
|
||||
|
||||
if httpResponse.StatusCode != 200 {
|
||||
|
|
@ -330,18 +318,107 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
|
|||
url.String(), dialer.record.AddressUsed, httpResponse.StatusCode),
|
||||
}
|
||||
err = challenge.Error
|
||||
return challenge, err
|
||||
return emptyBody, challenge, err
|
||||
}
|
||||
|
||||
// Read body & test
|
||||
body, readErr := ioutil.ReadAll(httpResponse.Body)
|
||||
if readErr != nil {
|
||||
body, err := ioutil.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: fmt.Sprintf("Error reading HTTP response body"),
|
||||
Detail: fmt.Sprintf("Error reading HTTP response body: %v", err),
|
||||
}
|
||||
return challenge, readErr
|
||||
return emptyBody, challenge, err
|
||||
}
|
||||
|
||||
return body, challenge, nil
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIdentifier, input core.Challenge, zName string) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
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 := strconv.Itoa(va.tlsPort)
|
||||
hostPort := net.JoinHostPort(addr.String(), portString)
|
||||
challenge.ValidationRecord[0].Port = portString
|
||||
va.log.Notice(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", challenge.Type, identifier, hostPort, zName))
|
||||
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{
|
||||
ServerName: zName,
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: parseHTTPConnError(err),
|
||||
Detail: "Failed to connect to host for DVSNI challenge",
|
||||
}
|
||||
va.log.Debug(fmt.Sprintf("%s [%s] TLS Connection failure: %s", challenge.Type, identifier, err))
|
||||
return challenge, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Check that zName is a dNSName SAN in the server's certificate
|
||||
certs := conn.ConnectionState().PeerCertificates
|
||||
if len(certs) == 0 {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: "No certs presented for TLS SNI challenge",
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
for _, name := range certs[0].DNSNames {
|
||||
if subtle.ConstantTimeCompare([]byte(name), []byte(zName)) == 1 {
|
||||
challenge.Status = core.StatusValid
|
||||
return challenge, nil
|
||||
}
|
||||
}
|
||||
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: "Correct zName not found for TLS SNI challenge",
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
if identifier.Type != core.IdentifierDNS {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.MalformedProblem,
|
||||
Detail: "Identifier type for SimpleHTTP was not DNS",
|
||||
}
|
||||
|
||||
va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] Identifier failure", identifier))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
// Perform the fetch
|
||||
path := fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token)
|
||||
useTLS := (challenge.TLS == nil) || *challenge.TLS
|
||||
body, challenge, err := va.fetchHTTP(identifier, path, useTLS, challenge)
|
||||
if err != nil {
|
||||
return challenge, err
|
||||
}
|
||||
|
||||
// Parse and verify JWS
|
||||
|
|
@ -381,6 +458,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti
|
|||
return challenge, nil
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
|
||||
func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
|
|
@ -417,67 +495,82 @@ func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
|
|||
h := sha256.New()
|
||||
h.Write([]byte(encodedSignature))
|
||||
Z := hex.EncodeToString(h.Sum(nil))
|
||||
ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.DVSNISuffix)
|
||||
ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.TLSSNISuffix)
|
||||
|
||||
addr, allAddrs, problem := va.getAddr(identifier.Value)
|
||||
challenge.ValidationRecord = []core.ValidationRecord{
|
||||
core.ValidationRecord{
|
||||
Hostname: identifier.Value,
|
||||
AddressesResolved: allAddrs,
|
||||
AddressUsed: addr,
|
||||
},
|
||||
}
|
||||
if problem != nil {
|
||||
return va.validateTLSWithZName(identifier, challenge, ZName)
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
if identifier.Type != core.IdentifierDNS {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = problem
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.MalformedProblem,
|
||||
Detail: "Identifier type for HTTP validation was not DNS",
|
||||
}
|
||||
|
||||
va.log.Debug(fmt.Sprintf("%s [%s] Identifier failure", challenge.Type, identifier))
|
||||
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: validationTimeout}, "tcp", hostPort, &tls.Config{
|
||||
ServerName: ZName,
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
|
||||
// Perform the fetch
|
||||
path := fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token)
|
||||
body, challenge, err := va.fetchHTTP(identifier, path, false, challenge)
|
||||
if err != nil {
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: parseHTTPConnError(err),
|
||||
Detail: "Failed to connect to host for DVSNI challenge",
|
||||
}
|
||||
va.log.Debug(fmt.Sprintf("DVSNI [%s] TLS Connection failure: %s", identifier, err))
|
||||
return challenge, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Check that ZName is a dNSName SAN in the server's certificate
|
||||
certs := conn.ConnectionState().PeerCertificates
|
||||
if len(certs) == 0 {
|
||||
// Parse body as a key authorization object
|
||||
serverKeyAuthorization, err := core.NewKeyAuthorizationFromString(string(body))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error parsing key authorization file: %s", err.Error())
|
||||
va.log.Debug(err.Error())
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: "No certs presented for DVSNI challenge",
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
for _, name := range certs[0].DNSNames {
|
||||
if subtle.ConstantTimeCompare([]byte(name), []byte(ZName)) == 1 {
|
||||
challenge.Status = core.StatusValid
|
||||
return challenge, nil
|
||||
Detail: err.Error(),
|
||||
}
|
||||
return challenge, err
|
||||
}
|
||||
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: "Correct ZName not found for DVSNI challenge",
|
||||
// Check that the account key for this challenge is authorized by this object
|
||||
if !serverKeyAuthorization.Match(challenge.Token, challenge.AccountKey) {
|
||||
err = fmt.Errorf("The key authorization file from the server did not match this challenge [%v] != [%v]",
|
||||
challenge.KeyAuthorization.String(), string(body))
|
||||
va.log.Debug(err.Error())
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: err.Error(),
|
||||
}
|
||||
return challenge, err
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
return challenge, challenge.Error
|
||||
|
||||
challenge.Status = core.StatusValid
|
||||
return challenge, nil
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateTLSSNI01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
if identifier.Type != "dns" {
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.MalformedProblem,
|
||||
Detail: "Identifier type for TLS-SNI was not DNS",
|
||||
}
|
||||
challenge.Status = core.StatusInvalid
|
||||
va.log.Debug(fmt.Sprintf("TLS-SNI [%s] Identifier failure", identifier))
|
||||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
// Compute the digest that will appear in the certificate
|
||||
h := sha256.New()
|
||||
h.Write([]byte(challenge.KeyAuthorization.String()))
|
||||
Z := hex.EncodeToString(h.Sum(nil))
|
||||
ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.TLSSNISuffix)
|
||||
|
||||
return va.validateTLSWithZName(identifier, challenge, ZName)
|
||||
}
|
||||
|
||||
// parseHTTPConnError returns the ACME ProblemType corresponding to an error
|
||||
|
|
@ -502,7 +595,7 @@ func parseHTTPConnError(err error) core.ProblemType {
|
|||
return core.ConnectionProblem
|
||||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
|
||||
challenge := input
|
||||
|
||||
if identifier.Type != core.IdentifierDNS {
|
||||
|
|
@ -515,24 +608,10 @@ func (va *ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, i
|
|||
return challenge, challenge.Error
|
||||
}
|
||||
|
||||
// Check that JWS body is as expected
|
||||
// * "type" == "dvsni"
|
||||
// * "token" == challenge.token
|
||||
target := map[string]interface{}{
|
||||
"type": core.ChallengeTypeDNS,
|
||||
"token": challenge.Token,
|
||||
}
|
||||
err := verifyValidationJWS(challenge.Validation, challenge.AccountKey, target)
|
||||
if err != nil {
|
||||
va.log.Debug(err.Error())
|
||||
challenge.Status = core.StatusInvalid
|
||||
challenge.Error = &core.ProblemDetails{
|
||||
Type: core.UnauthorizedProblem,
|
||||
Detail: err.Error(),
|
||||
}
|
||||
return challenge, err
|
||||
}
|
||||
encodedSignature := core.B64enc(challenge.Validation.Signatures[0].Signature)
|
||||
// Compute the digest of the key authorization file
|
||||
h := sha256.New()
|
||||
h.Write([]byte(challenge.KeyAuthorization.String()))
|
||||
authorizedKeysDigest := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
// Look for the required record in the DNS
|
||||
challengeSubdomain := fmt.Sprintf("%s.%s", core.DNSPrefix, identifier.Value)
|
||||
|
|
@ -548,7 +627,7 @@ func (va *ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, i
|
|||
}
|
||||
|
||||
for _, element := range txts {
|
||||
if subtle.ConstantTimeCompare([]byte(element), []byte(encodedSignature)) == 1 {
|
||||
if subtle.ConstantTimeCompare([]byte(element), []byte(authorizedKeysDigest)) == 1 {
|
||||
challenge.Status = core.StatusValid
|
||||
return challenge, nil
|
||||
}
|
||||
|
|
@ -583,11 +662,17 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI
|
|||
vStart := va.clk.Now()
|
||||
switch authz.Challenges[challengeIndex].Type {
|
||||
case core.ChallengeTypeSimpleHTTP:
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this case
|
||||
authz.Challenges[challengeIndex], err = va.validateSimpleHTTP(authz.Identifier, authz.Challenges[challengeIndex])
|
||||
case core.ChallengeTypeDVSNI:
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this case
|
||||
authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex])
|
||||
case core.ChallengeTypeDNS:
|
||||
authz.Challenges[challengeIndex], err = va.validateDNS(authz.Identifier, authz.Challenges[challengeIndex])
|
||||
case core.ChallengeTypeHTTP01:
|
||||
authz.Challenges[challengeIndex], err = va.validateHTTP01(authz.Identifier, authz.Challenges[challengeIndex])
|
||||
case core.ChallengeTypeTLSSNI01:
|
||||
authz.Challenges[challengeIndex], err = va.validateTLSSNI01(authz.Identifier, authz.Challenges[challengeIndex])
|
||||
case core.ChallengeTypeDNS01:
|
||||
authz.Challenges[challengeIndex], err = va.validateDNS01(authz.Identifier, authz.Challenges[challengeIndex])
|
||||
}
|
||||
va.stats.TimingDuration(fmt.Sprintf("VA.Validations.%s.%s", authz.Challenges[challengeIndex].Type, authz.Challenges[challengeIndex].Status), time.Since(vStart), 1.0)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,15 +65,21 @@ var ident = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "localhost"}
|
|||
|
||||
var log = mocks.UseMockLog()
|
||||
|
||||
const expectedToken = "THETOKEN"
|
||||
const pathWrongToken = "wrongtoken"
|
||||
// All paths that get assigned to tokens MUST be valid tokens
|
||||
const expectedToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
|
||||
const pathWrongToken = "i6lNAC4lOOLYCl-A08VJt9z_tKYvVk63Dumo8icsBjQ"
|
||||
const path404 = "404"
|
||||
const pathFound = "302"
|
||||
const pathMoved = "301"
|
||||
const pathRedirectLookup = "re-lookup"
|
||||
const pathRedirectLookupInvalid = "re-lookup-invalid"
|
||||
const pathFound = "GBq8SwWq3JsbREFdCamk5IX3KLsxW5ULeGs98Ajl_UM"
|
||||
const pathMoved = "5J4FIMrWNfmvHZo-QpKZngmuhqZGwRm21-oEgUDstJM"
|
||||
const pathRedirectPort = "port-redirect"
|
||||
const pathWait = "wait"
|
||||
const pathWaitLong = "wait-long"
|
||||
const pathReLookup = "7e-P57coLM7D3woNTp_xbJrtlkDYy6PWf3mSSbLwCr4"
|
||||
const pathReLookupInvalid = "re-lookup-invalid"
|
||||
const pathLooper = "looper"
|
||||
const pathValid = "valid"
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func createValidation(token string, enableTLS bool) string {
|
||||
payload, _ := json.Marshal(map[string]interface{}{
|
||||
"type": "simpleHttp",
|
||||
|
|
@ -85,6 +91,7 @@ func createValidation(token string, enableTLS bool) string {
|
|||
return obj.FullSerialize()
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server {
|
||||
m := http.NewServeMux()
|
||||
|
||||
|
|
@ -110,22 +117,22 @@ func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server {
|
|||
currentToken = pathFound
|
||||
}
|
||||
http.Redirect(w, r, pathMoved, 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, "wait") {
|
||||
} else if strings.HasSuffix(r.URL.Path, pathWait) {
|
||||
t.Logf("SIMPLESRV: Got a wait req\n")
|
||||
time.Sleep(time.Second * 3)
|
||||
} else if strings.HasSuffix(r.URL.Path, "wait-long") {
|
||||
} else if strings.HasSuffix(r.URL.Path, pathWaitLong) {
|
||||
t.Logf("SIMPLESRV: Got a wait-long req\n")
|
||||
time.Sleep(time.Second * 10)
|
||||
} else if strings.HasSuffix(r.URL.Path, "re-lookup") {
|
||||
} else if strings.HasSuffix(r.URL.Path, pathReLookup) {
|
||||
t.Logf("SIMPLESRV: Got a redirect req to a valid hostname\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = "re-lookup"
|
||||
currentToken = pathReLookup
|
||||
}
|
||||
http.Redirect(w, r, "http://other.valid/path", 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, "re-lookup-invalid") {
|
||||
} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) {
|
||||
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") {
|
||||
} else if strings.HasSuffix(r.URL.Path, pathLooper) {
|
||||
t.Logf("SIMPLESRV: Got a loop req\n")
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) {
|
||||
|
|
@ -174,6 +181,7 @@ func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server {
|
|||
return server
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
|
||||
encodedSig := core.B64enc(chall.Validation.Signatures[0].Signature)
|
||||
h := sha256.New()
|
||||
|
|
@ -221,17 +229,7 @@ func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
|
|||
return hs
|
||||
}
|
||||
|
||||
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")
|
||||
},
|
||||
}
|
||||
server.StartTLS()
|
||||
return server
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func TestSimpleHttpTLS(t *testing.T) {
|
||||
chall := core.Challenge{
|
||||
Type: core.ChallengeTypeSimpleHTTP,
|
||||
|
|
@ -246,18 +244,19 @@ func TestSimpleHttpTLS(t *testing.T) {
|
|||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPSPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPSPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "Error validating simpleHttp")
|
||||
logs := log.GetAllMatching(`^\[AUDIT\] Attempting to validate SimpleHTTPS for `)
|
||||
logs := log.GetAllMatching(`^\[AUDIT\] Attempting to validate simpleHttp for `)
|
||||
test.AssertEquals(t, len(logs), 1)
|
||||
test.AssertEquals(t, logs[0].Priority, syslog.LOG_NOTICE)
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func TestSimpleHttp(t *testing.T) {
|
||||
tls := false
|
||||
chall := core.Challenge{
|
||||
|
|
@ -288,16 +287,16 @@ func TestSimpleHttp(t *testing.T) {
|
|||
badPort = goodPort - 1
|
||||
}
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: badPort}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
invalidChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
|
||||
va = NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: goodPort}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
log.Clear()
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
|
|
@ -327,15 +326,15 @@ func TestSimpleHttp(t *testing.T) {
|
|||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "Failed to follow 301 redirect")
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathFound
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "Failed to follow 302 redirect")
|
||||
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(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
|
||||
ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}
|
||||
invalidChall, err = va.validateSimpleHTTP(ipIdentifier, chall)
|
||||
|
|
@ -360,6 +359,7 @@ func TestSimpleHttp(t *testing.T) {
|
|||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func TestSimpleHttpRedirectLookup(t *testing.T) {
|
||||
tls := false
|
||||
chall := core.Challenge{
|
||||
|
|
@ -374,15 +374,15 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
|
|||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathMoved
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
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(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2)
|
||||
|
||||
log.Clear()
|
||||
|
|
@ -390,12 +390,12 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
|
|||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
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(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 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
|
||||
chall.Token = pathReLookupInvalid
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, chall.Token)
|
||||
|
|
@ -403,11 +403,11 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
|
|||
test.AssertEquals(t, len(log.GetAllMatching(`No IPv4 addresses found for invalid.invalid`)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Token = pathRedirectLookup
|
||||
chall.Token = pathReLookup
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
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(`redirect from ".*/`+pathReLookup+`" 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)
|
||||
|
||||
|
|
@ -422,6 +422,7 @@ func TestSimpleHttpRedirectLookup(t *testing.T) {
|
|||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func TestSimpleHttpRedirectLoop(t *testing.T) {
|
||||
tls := false
|
||||
chall := core.Challenge{
|
||||
|
|
@ -435,8 +436,8 @@ func TestSimpleHttpRedirectLoop(t *testing.T) {
|
|||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateSimpleHTTP(ident, chall)
|
||||
|
|
@ -445,22 +446,7 @@ func TestSimpleHttpRedirectLoop(t *testing.T) {
|
|||
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
|
||||
}
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
|
||||
func TestDvsni(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
|
||||
|
|
@ -469,9 +455,9 @@ func TestDvsni(t *testing.T) {
|
|||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{DVSNIPort: port}, stats, clock.Default())
|
||||
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
|
||||
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateDvsni(ident, chall)
|
||||
|
|
@ -523,15 +509,15 @@ func TestDvsni(t *testing.T) {
|
|||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
func TestTLSError(t *testing.T) {
|
||||
func TestDVSNIWithTLSError(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
hs := brokenTLSSrv()
|
||||
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{DVSNIPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
invalidChall, err := va.validateDvsni(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
|
|
@ -539,19 +525,403 @@ func TestTLSError(t *testing.T) {
|
|||
test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem)
|
||||
}
|
||||
|
||||
func TestValidateHTTP(t *testing.T) {
|
||||
tls := false
|
||||
challHTTP := core.SimpleHTTPChallenge()
|
||||
challHTTP.TLS = &tls
|
||||
challHTTP.ValidationRecord = []core.ValidationRecord{}
|
||||
challHTTP.AccountKey = accountKey
|
||||
func httpSrv(t *testing.T, token string) *httptest.Server {
|
||||
m := http.NewServeMux()
|
||||
|
||||
hs := simpleSrv(t, challHTTP.Token, tls)
|
||||
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("HTTPSRV: Got a 404 req\n")
|
||||
http.NotFound(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathMoved) {
|
||||
t.Logf("HTTPSRV: Got a 301 redirect req\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = pathMoved
|
||||
}
|
||||
http.Redirect(w, r, pathValid, 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathFound) {
|
||||
t.Logf("HTTPSRV: Got a 302 redirect req\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = pathFound
|
||||
}
|
||||
http.Redirect(w, r, pathMoved, 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathWait) {
|
||||
t.Logf("HTTPSRV: Got a wait req\n")
|
||||
time.Sleep(time.Second * 3)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathWaitLong) {
|
||||
t.Logf("HTTPSRV: Got a wait-long req\n")
|
||||
time.Sleep(time.Second * 10)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathReLookup) {
|
||||
t.Logf("HTTPSRV: Got a redirect req to a valid hostname\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = pathReLookup
|
||||
}
|
||||
http.Redirect(w, r, "http://other.valid/path", 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) {
|
||||
t.Logf("HTTPSRV: 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, pathLooper) {
|
||||
t.Logf("HTTPSRV: Got a loop req\n")
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) {
|
||||
t.Logf("HTTPSRV: Got a port redirect req\n")
|
||||
http.Redirect(w, r, "http://other.valid:8080/path", 302)
|
||||
} else {
|
||||
t.Logf("HTTPSRV: Got a valid req\n")
|
||||
t.Logf("HTTPSRV: Path = %s\n", r.URL.Path)
|
||||
|
||||
keyAuthz, _ := core.NewKeyAuthorization(currentToken, accountKey)
|
||||
t.Logf("HTTPSRV: Key Authz = %s\n", keyAuthz.String())
|
||||
|
||||
fmt.Fprint(w, keyAuthz.String())
|
||||
currentToken = defaultToken
|
||||
}
|
||||
})
|
||||
|
||||
server := httptest.NewUnstartedServer(m)
|
||||
server.Start()
|
||||
return server
|
||||
}
|
||||
|
||||
func tlssniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(chall.KeyAuthorization.String()))
|
||||
Z := hex.EncodeToString(h.Sum(nil))
|
||||
ZName := fmt.Sprintf("%s.%s.acme.invalid", Z[:32], Z[32:])
|
||||
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1337),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"tests"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(0, 0, 1),
|
||||
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
|
||||
DNSNames: []string{ZName},
|
||||
}
|
||||
|
||||
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
||||
cert := &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: &TheKey,
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{*cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if clientHello.ServerName != ZName {
|
||||
time.Sleep(time.Second * 10)
|
||||
return nil, nil
|
||||
}
|
||||
return cert, nil
|
||||
},
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
hs.TLS = tlsConfig
|
||||
hs.StartTLS()
|
||||
return hs
|
||||
}
|
||||
|
||||
func TestHttp(t *testing.T) {
|
||||
chall := core.HTTPChallenge01(accountKey)
|
||||
err := setChallengeToken(&chall, expectedToken)
|
||||
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
|
||||
|
||||
// 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.
|
||||
// TODO(#661): add hs.Close back, see ticket for blocker
|
||||
hs := httpSrv(t, chall.Token)
|
||||
|
||||
goodPort, 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.
|
||||
badPort := goodPort + 1
|
||||
if badPort == 65536 {
|
||||
badPort = goodPort - 1
|
||||
}
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
invalidChall, err := va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
|
||||
va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
log.Clear()
|
||||
t.Logf("Trying to validate: %+v\n", chall)
|
||||
finChall, err := va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "Error validating http")
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, path404)
|
||||
invalidChall, err = va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Should have found a 404 for the challenge.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathWrongToken)
|
||||
// The "wrong token" will actually be the expectedToken. It's wrong
|
||||
// because it doesn't match pathWrongToken.
|
||||
invalidChall, err = va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Should have found the wrong token value.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathMoved)
|
||||
finChall, err = va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "Failed to follow 301 redirect")
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathFound)
|
||||
finChall, err = va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, "Failed to follow 302 redirect")
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
|
||||
ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}
|
||||
invalidChall, err = va.validateHTTP01(ipIdentifier, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
|
||||
|
||||
invalidChall, err = va.validateHTTP01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Domain name is invalid.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
|
||||
|
||||
setChallengeToken(&chall, pathWaitLong)
|
||||
started := time.Now()
|
||||
invalidChall, err = va.validateHTTP01(ident, chall)
|
||||
took := time.Since(started)
|
||||
// Check that the HTTP connection times out after 5 seconds and doesn't block for 10 seconds
|
||||
test.Assert(t, (took > (time.Second * 5)), "HTTP timed out before 5 seconds")
|
||||
test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Connection should've timed out")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
func TestHTTPRedirectLookup(t *testing.T) {
|
||||
chall := core.HTTPChallenge01(accountKey)
|
||||
err := setChallengeToken(&chall, expectedToken)
|
||||
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
|
||||
|
||||
hs := httpSrv(t, expectedToken)
|
||||
defer hs.Close()
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathMoved)
|
||||
finChall, err := va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathFound)
|
||||
finChall, err = va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathReLookupInvalid)
|
||||
finChall, err = va.validateHTTP01(ident, chall)
|
||||
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()
|
||||
setChallengeToken(&chall, pathReLookup)
|
||||
finChall, err = va.validateHTTP01(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathReLookup+`" 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()
|
||||
setChallengeToken(&chall, pathRedirectPort)
|
||||
finChall, err = va.validateHTTP01(ident, chall)
|
||||
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 TestHTTPRedirectLoop(t *testing.T) {
|
||||
chall := core.HTTPChallenge01(accountKey)
|
||||
err := setChallengeToken(&chall, "looper")
|
||||
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
|
||||
|
||||
hs := httpSrv(t, expectedToken)
|
||||
defer hs.Close()
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateHTTP01(ident, chall)
|
||||
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 TestTLSSNI(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSSNI01)
|
||||
|
||||
hs := tlssniSrv(t, chall)
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
|
||||
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
log.Clear()
|
||||
finChall, err := va.validateTLSSNI01(ident, chall)
|
||||
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)
|
||||
|
||||
log.Clear()
|
||||
invalidChall, err := va.validateTLSSNI01(core.AcmeIdentifier{
|
||||
Type: core.IdentifierType("ip"),
|
||||
Value: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)),
|
||||
}, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem)
|
||||
|
||||
log.Clear()
|
||||
invalidChall, err = va.validateTLSSNI01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Domain name was supposed to be invalid.")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem)
|
||||
|
||||
// Need to create a new authorized keys object to get an unknown SNI (from the signature value)
|
||||
chall.Token = core.NewToken()
|
||||
keyAuthorization, _ := core.NewKeyAuthorization(chall.Token, accountKey)
|
||||
chall.KeyAuthorization = &keyAuthorization
|
||||
|
||||
log.Clear()
|
||||
started := time.Now()
|
||||
invalidChall, err = va.validateTLSSNI01(ident, chall)
|
||||
took := time.Since(started)
|
||||
// Check that the HTTP connection times out after 5 seconds and doesn't block for 10 seconds
|
||||
test.Assert(t, (took > (time.Second * 5)), "HTTP timed out before 5 seconds")
|
||||
test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Connection should've timed out")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
|
||||
|
||||
// Take down validation server and check that validation fails.
|
||||
hs.Close()
|
||||
invalidChall, err = va.validateTLSSNI01(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem)
|
||||
}
|
||||
|
||||
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")
|
||||
},
|
||||
}
|
||||
server.StartTLS()
|
||||
return server
|
||||
}
|
||||
|
||||
func TestTLSError(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSSNI01)
|
||||
hs := brokenTLSSrv()
|
||||
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
invalidChall, err := va.validateTLSSNI01(ident, chall)
|
||||
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
|
||||
test.AssertError(t, err, "What cert was used?")
|
||||
test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem)
|
||||
}
|
||||
|
||||
func TestValidateHTTP(t *testing.T) {
|
||||
chall := core.HTTPChallenge01(accountKey)
|
||||
err := setChallengeToken(&chall, core.NewToken())
|
||||
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
|
||||
|
||||
hs := httpSrv(t, chall.Token)
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
|
|
@ -561,14 +931,14 @@ func TestValidateHTTP(t *testing.T) {
|
|||
ID: core.NewToken(),
|
||||
RegistrationID: 1,
|
||||
Identifier: ident,
|
||||
Challenges: []core.Challenge{challHTTP},
|
||||
Challenges: []core.Challenge{chall},
|
||||
}
|
||||
va.validate(authz, 0)
|
||||
|
||||
test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status)
|
||||
}
|
||||
|
||||
// challengeType == "dvsni" or "dns", since they're the same
|
||||
// challengeType == "tls-sni-00" or "dns-00", since they're the same
|
||||
func createChallenge(challengeType string) core.Challenge {
|
||||
chall := core.Challenge{
|
||||
Type: challengeType,
|
||||
|
|
@ -578,27 +948,45 @@ func createChallenge(challengeType string) core.Challenge {
|
|||
AccountKey: accountKey,
|
||||
}
|
||||
|
||||
keyAuthorization, _ := core.NewKeyAuthorization(chall.Token, accountKey)
|
||||
chall.KeyAuthorization = &keyAuthorization
|
||||
|
||||
// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
|
||||
validationPayload, _ := json.Marshal(map[string]interface{}{
|
||||
"type": chall.Type,
|
||||
"token": chall.Token,
|
||||
})
|
||||
|
||||
signer, _ := jose.NewSigner(jose.RS256, &TheKey)
|
||||
chall.Validation, _ = signer.Sign(validationPayload, "")
|
||||
|
||||
return chall
|
||||
}
|
||||
|
||||
func TestValidateDvsni(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
hs := dvsniSrv(t, chall)
|
||||
// setChallengeToken sets the token value both in the Token field and
|
||||
// in the serialized KeyAuthorization object.
|
||||
func setChallengeToken(ch *core.Challenge, token string) (err error) {
|
||||
ch.Token = token
|
||||
|
||||
keyAuthorization, err := core.NewKeyAuthorization(token, ch.AccountKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch.KeyAuthorization = &keyAuthorization
|
||||
return
|
||||
}
|
||||
|
||||
func TestValidateTLSSNI01(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSSNI01)
|
||||
hs := tlssniSrv(t, chall)
|
||||
defer hs.Close()
|
||||
|
||||
port, err := getPort(hs)
|
||||
test.AssertNotError(t, err, "failed to get test server port")
|
||||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{DVSNIPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
|
|
@ -613,14 +1001,14 @@ func TestValidateDvsni(t *testing.T) {
|
|||
test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status)
|
||||
}
|
||||
|
||||
func TestValidateDvsniNotSane(t *testing.T) {
|
||||
func TestValidateTLSSNINotSane(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) // no calls made
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chall := createChallenge(core.ChallengeTypeDVSNI)
|
||||
chall := createChallenge(core.ChallengeTypeTLSSNI01)
|
||||
|
||||
chall.Token = "not sane"
|
||||
|
||||
|
|
@ -638,20 +1026,20 @@ func TestValidateDvsniNotSane(t *testing.T) {
|
|||
func TestUpdateValidations(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
tls := false
|
||||
challHTTP := core.SimpleHTTPChallenge()
|
||||
challHTTP.TLS = &tls
|
||||
challHTTP.ValidationRecord = []core.ValidationRecord{}
|
||||
chall := core.HTTPChallenge01(accountKey)
|
||||
chall.ValidationRecord = []core.ValidationRecord{}
|
||||
err := setChallengeToken(&chall, core.NewToken())
|
||||
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
RegistrationID: 1,
|
||||
Identifier: ident,
|
||||
Challenges: []core.Challenge{challHTTP},
|
||||
Challenges: []core.Challenge{chall},
|
||||
}
|
||||
|
||||
started := time.Now()
|
||||
|
|
@ -694,7 +1082,7 @@ func TestCAAChecking(t *testing.T) {
|
|||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
va.IssuerDomain = "letsencrypt.org"
|
||||
for _, caaTest := range tests {
|
||||
present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain})
|
||||
|
|
@ -727,11 +1115,11 @@ func TestCAAChecking(t *testing.T) {
|
|||
func TestDNSValidationFailure(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS)
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
|
|
@ -753,7 +1141,7 @@ func TestDNSValidationInvalid(t *testing.T) {
|
|||
Value: "790DB180-A274-47A4-855F-31C428CB1072",
|
||||
}
|
||||
|
||||
chalDNS := core.DNSChallenge()
|
||||
chalDNS := core.DNSChallenge01(accountKey)
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
|
|
@ -764,7 +1152,7 @@ func TestDNSValidationInvalid(t *testing.T) {
|
|||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
|
|
@ -778,17 +1166,17 @@ func TestDNSValidationInvalid(t *testing.T) {
|
|||
func TestDNSValidationNotSane(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chal0 := core.DNSChallenge()
|
||||
chal0 := core.DNSChallenge01(accountKey)
|
||||
chal0.Token = ""
|
||||
|
||||
chal1 := core.DNSChallenge()
|
||||
chal1 := core.DNSChallenge01(accountKey)
|
||||
chal1.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_"
|
||||
|
||||
chal2 := core.DNSChallenge()
|
||||
chal2 := core.DNSChallenge01(accountKey)
|
||||
chal2.TLS = new(bool)
|
||||
*chal2.TLS = true
|
||||
|
||||
|
|
@ -809,11 +1197,11 @@ func TestDNSValidationNotSane(t *testing.T) {
|
|||
func TestDNSValidationServFail(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS)
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
|
||||
badIdent := core.AcmeIdentifier{
|
||||
Type: core.IdentifierDNS,
|
||||
|
|
@ -839,7 +1227,7 @@ func TestDNSValidationNoServer(t *testing.T) {
|
|||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS)
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
|
|
@ -860,11 +1248,11 @@ func TestDNSValidationNoServer(t *testing.T) {
|
|||
func TestDNSValidationLive(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
|
||||
va.DNSResolver = &mocks.MockDNS{}
|
||||
va.DNSResolver = &mocks.DNSResolver{}
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
goodChalDNS := core.DNSChallenge()
|
||||
goodChalDNS := core.DNSChallenge01(accountKey)
|
||||
// This token is set at _acme-challenge.good.bin.coffee
|
||||
goodChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w"
|
||||
|
||||
|
|
@ -891,7 +1279,7 @@ func TestDNSValidationLive(t *testing.T) {
|
|||
t.Logf("TestDNSValidationLive on Good did not succeed.")
|
||||
}
|
||||
|
||||
badChalDNS := core.DNSChallenge()
|
||||
badChalDNS := core.DNSChallenge01(accountKey)
|
||||
// This token is NOT set at _acme-challenge.bad.bin.coffee
|
||||
badChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w"
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,14 @@ const (
|
|||
IssuerPath = "/acme/issuer-cert"
|
||||
BuildIDPath = "/build"
|
||||
|
||||
// Not included in net/http
|
||||
// StatusRateLimited is not in net/http
|
||||
StatusRateLimited = 429
|
||||
)
|
||||
|
||||
// WebFrontEndImpl provides all the logic for Boulder's web-facing interface,
|
||||
// i.e., ACME. Its members configure the paths for various ACME functions,
|
||||
// plus a few other data items used in ACME. Its methods are primarily handlers
|
||||
// for HTTPS requests for the various ACME functions.
|
||||
type WebFrontEndImpl struct {
|
||||
RA core.RegistrationAuthority
|
||||
SA core.StorageGetter
|
||||
|
|
@ -113,7 +117,7 @@ func statusCodeFromError(err interface{}) int {
|
|||
type requestEvent struct {
|
||||
ID string `json:",omitempty"`
|
||||
RealIP string `json:",omitempty"`
|
||||
ForwardedFor string `json:",omitempty"`
|
||||
ClientAddr string `json:",omitempty"`
|
||||
Endpoint string `json:",omitempty"`
|
||||
Method string `json:",omitempty"`
|
||||
RequestTime time.Time `json:",omitempty"`
|
||||
|
|
@ -302,6 +306,8 @@ func addCacheHeader(w http.ResponseWriter, age float64) {
|
|||
w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%.f", age))
|
||||
}
|
||||
|
||||
// Directory is an HTTP request handler that simply provides the directory
|
||||
// object stored in the WFE's DirectoryJSON member.
|
||||
func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) {
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Write(wfe.DirectoryJSON)
|
||||
|
|
@ -553,8 +559,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
|
|||
|
||||
// Use an explicitly typed variable. Otherwise `go vet' incorrectly complains
|
||||
// that reg.ID is a string being passed to %d.
|
||||
var id int64 = reg.ID
|
||||
regURL := fmt.Sprintf("%s%d", wfe.RegBase, id)
|
||||
regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID)
|
||||
responseBody, err := json.Marshal(reg)
|
||||
if err != nil {
|
||||
logEvent.Error = err.Error()
|
||||
|
|
@ -730,13 +735,13 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
|
|||
}
|
||||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) logCsr(remoteAddr string, cr core.CertificateRequest, registration core.Registration) {
|
||||
func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateRequest, registration core.Registration) {
|
||||
var csrLog = struct {
|
||||
RemoteAddr string
|
||||
ClientAddr string
|
||||
CsrBase64 []byte
|
||||
Registration core.Registration
|
||||
}{
|
||||
RemoteAddr: remoteAddr,
|
||||
ClientAddr: getClientAddr(request),
|
||||
CsrBase64: cr.Bytes,
|
||||
Registration: registration,
|
||||
}
|
||||
|
|
@ -778,7 +783,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
wfe.logCsr(request.RemoteAddr, certificateRequest, reg)
|
||||
wfe.logCsr(request, certificateRequest, reg)
|
||||
// Check that the key in the CSR is good. This will also be checked in the CA
|
||||
// component, but we want to discard CSRs with bad keys as early as possible
|
||||
// because (a) it's an easy check and we can save unnecessary requests and
|
||||
|
|
@ -833,6 +838,8 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
|||
}
|
||||
}
|
||||
|
||||
// Challenge handles POST requests to challenge URLs. Such requests are clients'
|
||||
// responses to the server's challenges.
|
||||
func (wfe *WebFrontEndImpl) Challenge(
|
||||
response http.ResponseWriter,
|
||||
request *http.Request) {
|
||||
|
|
@ -1298,15 +1305,26 @@ func (wfe *WebFrontEndImpl) logRequestDetails(logEvent *requestEvent) {
|
|||
|
||||
func (wfe *WebFrontEndImpl) populateRequestEvent(request *http.Request) (logEvent requestEvent) {
|
||||
logEvent = requestEvent{
|
||||
ID: core.NewToken(),
|
||||
RealIP: request.Header.Get("X-Real-IP"),
|
||||
ForwardedFor: request.Header.Get("X-Forwarded-For"),
|
||||
Method: request.Method,
|
||||
RequestTime: time.Now(),
|
||||
Extra: make(map[string]interface{}, 0),
|
||||
ID: core.NewToken(),
|
||||
RealIP: request.Header.Get("X-Real-IP"),
|
||||
ClientAddr: getClientAddr(request),
|
||||
Method: request.Method,
|
||||
RequestTime: time.Now(),
|
||||
Extra: make(map[string]interface{}, 0),
|
||||
}
|
||||
if request.URL != nil {
|
||||
logEvent.Endpoint = request.URL.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Comma-separated list of HTTP clients involved in making this
|
||||
// request, starting with the original requestor and ending with the
|
||||
// remote end of our TCP connection (which is typically our own
|
||||
// proxy).
|
||||
func getClientAddr(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
return xff + "," + r.RemoteAddr
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ import (
|
|||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
|
||||
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/ra"
|
||||
|
|
@ -172,7 +171,7 @@ func (ca *MockCA) RevokeCertificate(serial string, reasonCode core.RevocationCod
|
|||
|
||||
type MockPA struct{}
|
||||
|
||||
func (pa *MockPA) ChallengesFor(identifier core.AcmeIdentifier) (challenges []core.Challenge, combinations [][]int) {
|
||||
func (pa *MockPA) ChallengesFor(identifier core.AcmeIdentifier, key *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +213,7 @@ func setupWFE(t *testing.T) WebFrontEndImpl {
|
|||
wfe.log.SyslogWriter = mocks.NewSyslogWriter()
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
wfe.stats, _ = statsd.NewNoopClient()
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
|
||||
|
|
@ -534,7 +533,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
wfe := setupWFE(t)
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter)
|
||||
mockLog := wfe.log.SyslogWriter.(*mocks.SyslogWriter)
|
||||
|
||||
// The mock CA we use always returns the same test certificate, with a Not
|
||||
// Before of 2015-09-22. Since we're currently using a real RA instead of a
|
||||
|
|
@ -549,10 +548,10 @@ func TestIssueCertificate(t *testing.T) {
|
|||
// authorized, etc.
|
||||
stats, _ := statsd.NewNoopClient(nil)
|
||||
ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{})
|
||||
ra.SA = &mocks.MockSA{}
|
||||
ra.SA = &mocks.StorageAuthority{}
|
||||
ra.CA = &MockCA{}
|
||||
ra.PA = &MockPA{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
wfe.RA = &ra
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
|
|
@ -671,7 +670,7 @@ func TestChallenge(t *testing.T) {
|
|||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
var key jose.JsonWebKey
|
||||
|
|
@ -707,7 +706,7 @@ func TestNewRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
wfe.stats, _ = statsd.NewNoopClient()
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
|
||||
|
|
@ -879,7 +878,7 @@ func makeRevokeRequestJSON() ([]byte, error) {
|
|||
// registration when GetRegistrationByKey is called, and we want to get a
|
||||
// NoSuchRegistrationError for tests that pass regCheck = false to verifyPOST.
|
||||
type mockSANoSuchRegistration struct {
|
||||
mocks.MockSA
|
||||
mocks.StorageAuthority
|
||||
}
|
||||
|
||||
func (msa mockSANoSuchRegistration) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) {
|
||||
|
|
@ -902,7 +901,7 @@ func TestRevokeCertificateCertKey(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
|
||||
|
||||
wfe := setupWFE(t)
|
||||
wfe.SA = &mockSANoSuchRegistration{mocks.MockSA{}}
|
||||
wfe.SA = &mockSANoSuchRegistration{mocks.StorageAuthority{}}
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
nonce, err := wfe.nonceService.Nonce()
|
||||
|
|
@ -992,7 +991,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
|
|||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mockSANoSuchRegistration{mocks.MockSA{}}
|
||||
wfe.SA = &mockSANoSuchRegistration{mocks.StorageAuthority{}}
|
||||
wfe.stats, _ = statsd.NewNoopClient()
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
|
@ -1013,7 +1012,7 @@ func TestAuthorization(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
wfe.stats, _ = statsd.NewNoopClient()
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
|
|
@ -1101,7 +1100,7 @@ func TestRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
wfe.stats, _ = statsd.NewNoopClient()
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
|
@ -1192,7 +1191,7 @@ func TestTermsRedirect(t *testing.T) {
|
|||
wfe := setupWFE(t)
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
wfe.stats, _ = statsd.NewNoopClient()
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
|
||||
|
|
@ -1228,59 +1227,64 @@ func TestGetCertificate(t *testing.T) {
|
|||
wfe := setupWFE(t)
|
||||
wfe.CertCacheDuration = time.Second * 10
|
||||
wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7
|
||||
wfe.SA = &mocks.MockSA{}
|
||||
wfe.SA = &mocks.StorageAuthority{}
|
||||
|
||||
certPemBytes, _ := ioutil.ReadFile("test/178.crt")
|
||||
certBlock, _ := pem.Decode(certPemBytes)
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
mockLog := wfe.log.SyslogWriter.(*mocks.SyslogWriter)
|
||||
mockLog.Clear()
|
||||
|
||||
// Valid serial, cached
|
||||
path, _ := url.Parse("/acme/cert/0000000000000000000000000000000000b2")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
req, _ := http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000b2", nil)
|
||||
req.RemoteAddr = "192.168.0.1"
|
||||
wfe.Certificate(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 200)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert")
|
||||
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match")
|
||||
|
||||
reqlogs := mockLog.GetAllMatching(`Successful request`)
|
||||
test.AssertEquals(t, len(reqlogs), 1)
|
||||
test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO)
|
||||
test.AssertContains(t, reqlogs[0].Message, `"ClientAddr":"192.168.0.1"`)
|
||||
|
||||
// Unused serial, no cache
|
||||
mockLog.Clear()
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/0000000000000000000000000000000000ff")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
req, _ = http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000ff", nil)
|
||||
req.RemoteAddr = "192.168.0.1"
|
||||
req.Header.Set("X-Forwarded-For", "192.168.99.99")
|
||||
wfe.Certificate(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
||||
reqlogs = mockLog.GetAllMatching(`Terminated request`)
|
||||
test.AssertEquals(t, len(reqlogs), 1)
|
||||
test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO)
|
||||
test.AssertContains(t, reqlogs[0].Message, `"ClientAddr":"192.168.99.99,192.168.0.1"`)
|
||||
|
||||
// Invalid serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/acme/cert/nothex", nil)
|
||||
wfe.Certificate(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
||||
// Invalid serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/nothex")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
|
||||
// Invalid serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/00000000000000")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
req, _ = http.NewRequest("GET", "/acme/cert/00000000000000", nil)
|
||||
wfe.Certificate(responseWriter, req)
|
||||
test.AssertEquals(t, responseWriter.Code, 404)
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`)
|
||||
}
|
||||
|
||||
func assertCsrLogged(t *testing.T, mockLog *mocks.MockSyslogWriter) {
|
||||
func assertCsrLogged(t *testing.T, mockLog *mocks.SyslogWriter) {
|
||||
matches := mockLog.GetAllMatching("^\\[AUDIT\\] Certificate request JSON=")
|
||||
test.Assert(t, len(matches) == 1,
|
||||
fmt.Sprintf("Incorrect number of certificate request log entries: %d",
|
||||
|
|
@ -1297,20 +1301,25 @@ func TestLogCsrPem(t *testing.T) {
|
|||
err := json.Unmarshal([]byte(certificateRequestJSON), &certificateRequest)
|
||||
test.AssertNotError(t, err, "Unable to parse certificateRequest")
|
||||
|
||||
mockSA := mocks.MockSA{}
|
||||
mockSA := mocks.StorageAuthority{}
|
||||
reg, err := mockSA.GetRegistration(789)
|
||||
test.AssertNotError(t, err, "Unable to get registration")
|
||||
|
||||
remoteAddr := "12.34.98.76"
|
||||
req, err := http.NewRequest("GET", "http://[::1]/", nil)
|
||||
test.AssertNotError(t, err, "NewRequest failed")
|
||||
req.RemoteAddr = "12.34.98.76"
|
||||
req.Header.Set("X-Forwarded-For", "10.0.0.1,172.16.0.1")
|
||||
|
||||
wfe.logCsr(remoteAddr, certificateRequest, reg)
|
||||
mockLog := wfe.log.SyslogWriter.(*mocks.SyslogWriter)
|
||||
mockLog.Clear()
|
||||
|
||||
wfe.logCsr(req, certificateRequest, reg)
|
||||
|
||||
mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter)
|
||||
matches := mockLog.GetAllMatching("Certificate request")
|
||||
test.Assert(t, len(matches) == 1,
|
||||
"Incorrect number of certificate request log entries")
|
||||
test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE)
|
||||
test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"RemoteAddr":"12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AQAB"},"agreement":"http://example.invalid/terms"}}`)
|
||||
test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"ClientAddr":"10.0.0.1,172.16.0.1,12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AQAB"},"agreement":"http://example.invalid/terms"}}`)
|
||||
}
|
||||
|
||||
func TestLengthRequired(t *testing.T) {
|
||||
|
|
@ -1325,7 +1334,7 @@ func TestLengthRequired(t *testing.T) {
|
|||
}
|
||||
|
||||
type mockSADifferentStoredKey struct {
|
||||
mocks.MockSA
|
||||
mocks.StorageAuthority
|
||||
}
|
||||
|
||||
func (sa mockSADifferentStoredKey) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) {
|
||||
|
|
@ -1340,7 +1349,7 @@ func (sa mockSADifferentStoredKey) GetRegistrationByKey(jwk jose.JsonWebKey) (co
|
|||
|
||||
func TestVerifyPOSTUsesStoredKey(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.SA = &mockSADifferentStoredKey{mocks.MockSA{}}
|
||||
wfe.SA = &mockSADifferentStoredKey{mocks.StorageAuthority{}}
|
||||
// signRequest signs with test1Key, but our special mock returns a
|
||||
// registration with test2Key
|
||||
_, _, _, err := wfe.verifyPOST(makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo")
|
||||
|
|
|
|||
Loading…
Reference in New Issue