Merge master
This commit is contained in:
commit
5face2bf08
|
|
@ -6,8 +6,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
|
@ -36,6 +39,9 @@ func main() {
|
|||
rai := ra.NewRegistrationAuthorityImpl()
|
||||
rai.AuthzBase = c.Common.BaseURL + wfe.AuthzPath
|
||||
rai.MaxKeySize = c.Common.MaxKeySize
|
||||
raDNSTimeout, err := time.ParseDuration(c.RA.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
|
||||
rai.DNSResolver = core.NewDNSResolverImpl(raDNSTimeout, []string{c.RA.DNSResolver})
|
||||
|
||||
go cmd.ProfileCmd("RA", stats)
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,15 @@ func main() {
|
|||
wfe.Stats = stats
|
||||
wfe.SubscriberAgreementURL = c.SubscriberAgreementURL
|
||||
|
||||
wfe.CertCacheDuration, err = time.ParseDuration(c.WFE.CertCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate caching duration")
|
||||
wfe.CertNoCacheExpirationWindow, err = time.ParseDuration(c.WFE.CertNoCacheExpirationWindow)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate expiration no-cache window")
|
||||
wfe.IndexCacheDuration, err = time.ParseDuration(c.WFE.IndexCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse index caching duration")
|
||||
wfe.IssuerCacheDuration, err = time.ParseDuration(c.WFE.IssuerCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse issuer caching duration")
|
||||
|
||||
wfe.IssuerCert, err = cmd.LoadCert(c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.Common.IssuerCert))
|
||||
|
||||
|
|
@ -120,7 +129,8 @@ func main() {
|
|||
|
||||
// Set up paths
|
||||
wfe.BaseURL = c.Common.BaseURL
|
||||
h := wfe.Handler()
|
||||
h, err := wfe.Handler()
|
||||
cmd.FailOnError(err, "Problem setting up HTTP handlers")
|
||||
|
||||
auditlogger.Info(app.VersionString())
|
||||
|
||||
|
|
|
|||
|
|
@ -82,12 +82,24 @@ func main() {
|
|||
cmd.FailOnError(err, "Unable to create SA")
|
||||
sa.SetSQLDebug(c.SQL.SQLDebug)
|
||||
|
||||
wfei.CertCacheDuration, err = time.ParseDuration(c.WFE.CertCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate caching duration")
|
||||
wfei.CertNoCacheExpirationWindow, err = time.ParseDuration(c.WFE.CertNoCacheExpirationWindow)
|
||||
cmd.FailOnError(err, "Couldn't parse certificate expiration no-cache window")
|
||||
wfei.IndexCacheDuration, err = time.ParseDuration(c.WFE.IndexCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse index caching duration")
|
||||
wfei.IssuerCacheDuration, err = time.ParseDuration(c.WFE.IssuerCacheDuration)
|
||||
cmd.FailOnError(err, "Couldn't parse issuer caching duration")
|
||||
|
||||
ra := ra.NewRegistrationAuthorityImpl()
|
||||
raDNSTimeout, err := time.ParseDuration(c.RA.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
|
||||
ra.DNSResolver = core.NewDNSResolverImpl(raDNSTimeout, []string{c.RA.DNSResolver})
|
||||
|
||||
va := va.NewValidationAuthorityImpl(c.CA.TestMode)
|
||||
dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse DNS timeout")
|
||||
va.DNSResolver = core.NewDNSResolverImpl(dnsTimeout, []string{c.VA.DNSResolver})
|
||||
vaDNSTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse VA DNS timeout")
|
||||
va.DNSResolver = core.NewDNSResolverImpl(vaDNSTimeout, []string{c.VA.DNSResolver})
|
||||
va.UserAgent = c.VA.UserAgent
|
||||
|
||||
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBConnect)
|
||||
|
|
@ -122,7 +134,8 @@ func main() {
|
|||
// Set up paths
|
||||
ra.AuthzBase = c.Common.BaseURL + wfe.AuthzPath
|
||||
wfei.BaseURL = c.Common.BaseURL
|
||||
h := wfei.Handler()
|
||||
h, err := wfei.Handler()
|
||||
cmd.FailOnError(err, "Problem setting up HTTP handlers")
|
||||
|
||||
ra.MaxKeySize = c.Common.MaxKeySize
|
||||
ca.MaxKeySize = c.Common.MaxKeySize
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"crypto/x509"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
|
|
@ -24,8 +23,6 @@ import (
|
|||
"github.com/letsencrypt/boulder/sa"
|
||||
)
|
||||
|
||||
const ocspResponseLimit int = 128
|
||||
|
||||
// FatalError indicates the updater should stop execution
|
||||
type FatalError string
|
||||
|
||||
|
|
@ -191,7 +188,7 @@ func main() {
|
|||
|
||||
app.App.Flags = append(app.App.Flags, cli.IntFlag{
|
||||
Name: "limit",
|
||||
Value: ocspResponseLimit,
|
||||
Value: 100,
|
||||
EnvVar: "OCSP_LIMIT",
|
||||
Usage: "Count of responses to process per run",
|
||||
})
|
||||
|
|
@ -251,12 +248,10 @@ func main() {
|
|||
oldestLastUpdatedTime := time.Now().Add(-dur)
|
||||
auditlogger.Info(fmt.Sprintf("Searching for OCSP responses older than %s", oldestLastUpdatedTime))
|
||||
|
||||
count := int(math.Min(float64(ocspResponseLimit), float64(c.OCSPUpdater.ResponseLimit)))
|
||||
|
||||
// When we choose to batch responses, it may be best to restrict count here,
|
||||
// change the transaction to survive the whole findStaleResponses, and to
|
||||
// loop this method call however many times is appropriate.
|
||||
err = updater.findStaleResponses(oldestLastUpdatedTime, count)
|
||||
err = updater.findStaleResponses(oldestLastUpdatedTime, c.OCSPUpdater.ResponseLimit)
|
||||
if err != nil {
|
||||
auditlogger.WarningErr(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ type Config struct {
|
|||
BaseURL string
|
||||
ListenAddress string
|
||||
|
||||
CertCacheDuration string
|
||||
CertNoCacheExpirationWindow string
|
||||
IndexCacheDuration string
|
||||
IssuerCacheDuration string
|
||||
|
||||
// DebugAddr is the address to run the /debug handlers on.
|
||||
DebugAddr string
|
||||
}
|
||||
|
|
@ -85,6 +90,9 @@ type Config struct {
|
|||
}
|
||||
|
||||
RA struct {
|
||||
DNSResolver string
|
||||
DNSTimeout string
|
||||
|
||||
// DebugAddr is the address to run the /debug handlers on.
|
||||
DebugAddr string
|
||||
}
|
||||
|
|
|
|||
22
core/dns.go
22
core/dns.go
|
|
@ -168,3 +168,25 @@ func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time
|
|||
}
|
||||
return CAAs, rtt, nil
|
||||
}
|
||||
|
||||
// LookupMX sends a DNS query to find a MX record associated hostname and returns the
|
||||
// record target.
|
||||
func (dnsResolver *DNSResolverImpl) LookupMX(hostname string) ([]string, time.Duration, error) {
|
||||
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeMX)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
err = fmt.Errorf("DNS failure: %d-%s for MX query", r.Rcode, dns.RcodeToString[r.Rcode])
|
||||
return nil, rtt, err
|
||||
}
|
||||
|
||||
var results []string
|
||||
for _, answer := range r.Answer {
|
||||
if mx, ok := answer.(*dns.MX); ok {
|
||||
results = append(results, mx.Mx)
|
||||
}
|
||||
}
|
||||
|
||||
return results, rtt, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,4 +145,5 @@ type DNSResolver interface {
|
|||
LookupHost(string) ([]net.IP, time.Duration, time.Duration, error)
|
||||
LookupCNAME(string) (string, time.Duration, error)
|
||||
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
|
||||
LookupMX(string) ([]string, time.Duration, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,3 +101,14 @@ func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error)
|
|||
}
|
||||
return results, 0, nil
|
||||
}
|
||||
|
||||
// LookupMX is a mock
|
||||
func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) {
|
||||
switch domain {
|
||||
case "letsencrypt.org":
|
||||
fallthrough
|
||||
case "email.com":
|
||||
return []string{"mail.email.com"}, 0, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
|
@ -27,11 +26,12 @@ import (
|
|||
// NOTE: All of the fields in RegistrationAuthorityImpl need to be
|
||||
// populated, or there is a risk of panic.
|
||||
type RegistrationAuthorityImpl struct {
|
||||
CA core.CertificateAuthority
|
||||
VA core.ValidationAuthority
|
||||
SA core.StorageAuthority
|
||||
PA core.PolicyAuthority
|
||||
log *blog.AuditLogger
|
||||
CA core.CertificateAuthority
|
||||
VA core.ValidationAuthority
|
||||
SA core.StorageAuthority
|
||||
PA core.PolicyAuthority
|
||||
DNSResolver core.DNSResolver
|
||||
log *blog.AuditLogger
|
||||
|
||||
AuthzBase string
|
||||
MaxKeySize int
|
||||
|
|
@ -53,7 +53,7 @@ func lastPathSegment(url core.AcmeURL) string {
|
|||
return allButLastPathSegment.ReplaceAllString(url.Path, "")
|
||||
}
|
||||
|
||||
func validateEmail(address string) (err error) {
|
||||
func validateEmail(address string, resolver core.DNSResolver) (err error) {
|
||||
_, err = mail.ParseAddress(address)
|
||||
if err != nil {
|
||||
err = core.MalformedRequestError(fmt.Sprintf("%s is not a valid e-mail address", address))
|
||||
|
|
@ -61,8 +61,8 @@ func validateEmail(address string) (err error) {
|
|||
}
|
||||
splitEmail := strings.SplitN(address, "@", -1)
|
||||
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
|
||||
var mx []*net.MX
|
||||
mx, err = net.LookupMX(domain)
|
||||
var mx []string
|
||||
mx, _, err = resolver.LookupMX(domain)
|
||||
if err != nil || len(mx) == 0 {
|
||||
err = core.MalformedRequestError(fmt.Sprintf("No MX record for domain %s", domain))
|
||||
return
|
||||
|
|
@ -70,13 +70,13 @@ func validateEmail(address string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func validateContacts(contacts []core.AcmeURL) (err error) {
|
||||
func validateContacts(contacts []core.AcmeURL, resolver core.DNSResolver) (err error) {
|
||||
for _, contact := range contacts {
|
||||
switch contact.Scheme {
|
||||
case "tel":
|
||||
continue
|
||||
case "mailto":
|
||||
err = validateEmail(contact.Opaque)
|
||||
err = validateEmail(contact.Opaque, resolver)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re
|
|||
}
|
||||
reg.MergeUpdate(init)
|
||||
|
||||
err = validateContacts(reg.Contact)
|
||||
err = validateContacts(reg.Contact, ra.DNSResolver)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -367,7 +367,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
|
|||
func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
|
||||
base.MergeUpdate(update)
|
||||
|
||||
err = validateContacts(base.Contact)
|
||||
err = validateContacts(base.Contact, ra.DNSResolver)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
|
|||
ra.PA = pa
|
||||
ra.AuthzBase = "http://acme.invalid/authz/"
|
||||
ra.MaxKeySize = 4096
|
||||
ra.DNSResolver = &mocks.MockDNS{}
|
||||
|
||||
AuthzInitial.RegistrationID = Registration.ID
|
||||
|
||||
|
|
@ -230,38 +231,38 @@ func TestValidateContacts(t *testing.T) {
|
|||
invalidEmail, _ := url.Parse("mailto:admin@example.com")
|
||||
malformedEmail, _ := url.Parse("mailto:admin.com")
|
||||
|
||||
err := validateContacts([]core.AcmeURL{})
|
||||
err := validateContacts([]core.AcmeURL{}, &mocks.MockDNS{})
|
||||
test.AssertNotError(t, err, "No Contacts")
|
||||
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*tel)})
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*tel)}, &mocks.MockDNS{})
|
||||
test.AssertNotError(t, err, "Simple Telephone")
|
||||
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*validEmail)})
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*validEmail)}, &mocks.MockDNS{})
|
||||
test.AssertNotError(t, err, "Valid Email")
|
||||
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*invalidEmail)})
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*invalidEmail)}, &mocks.MockDNS{})
|
||||
test.AssertError(t, err, "Invalid Email")
|
||||
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*malformedEmail)})
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*malformedEmail)}, &mocks.MockDNS{})
|
||||
test.AssertError(t, err, "Malformed Email")
|
||||
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*ansible)})
|
||||
err = validateContacts([]core.AcmeURL{core.AcmeURL(*ansible)}, &mocks.MockDNS{})
|
||||
test.AssertError(t, err, "Unknown scehme")
|
||||
}
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
err := validateEmail("an email`")
|
||||
err := validateEmail("an email`", &mocks.MockDNS{})
|
||||
test.AssertError(t, err, "Malformed")
|
||||
|
||||
err = validateEmail("a@not.a.domain")
|
||||
err = validateEmail("a@not.a.domain", &mocks.MockDNS{})
|
||||
test.AssertError(t, err, "Cannot resolve")
|
||||
t.Logf("No Resolve: %s", err)
|
||||
|
||||
err = validateEmail("a@example.com")
|
||||
err = validateEmail("a@example.com", &mocks.MockDNS{})
|
||||
test.AssertError(t, err, "No MX Record")
|
||||
t.Logf("No MX: %s", err)
|
||||
|
||||
err = validateEmail("a@email.com")
|
||||
err = validateEmail("a@email.com", &mocks.MockDNS{})
|
||||
test.AssertNotError(t, err, "Valid")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4000",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
@ -110,6 +114,8 @@
|
|||
},
|
||||
|
||||
"ra": {
|
||||
"dnsResolver": "8.8.8.8:53",
|
||||
"dnsTimeout": "10s",
|
||||
"debugAddr": "localhost:8002"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4000",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
@ -97,6 +101,8 @@
|
|||
},
|
||||
|
||||
"ra": {
|
||||
"dnsResolver": "8.8.8.8:53",
|
||||
"dnsTimeout": "10s",
|
||||
"debugAddr": "localhost:8002"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
|
||||
"wfe": {
|
||||
"listenAddress": "127.0.0.1:4300",
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "8765h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"debugAddr": "localhost:8000"
|
||||
},
|
||||
|
||||
|
|
@ -101,6 +105,8 @@
|
|||
},
|
||||
|
||||
"ra": {
|
||||
"dnsResolver": "127.0.0.1:8053",
|
||||
"dnsTimeout": "10s",
|
||||
"debugAddr": "localhost:8002"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -150,9 +150,14 @@ func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentif
|
|||
}
|
||||
return originalDial("tcp", net.JoinHostPort(addr.String(), port))
|
||||
}
|
||||
logRedirect := func(req *http.Request, via []*http.Request) error {
|
||||
va.log.Info(fmt.Sprintf("validateSimpleHTTP [%s] redirect from %q to %q", identifier, via[len(via)-1].URL.String(), req.URL.String()))
|
||||
return nil
|
||||
}
|
||||
client := http.Client{
|
||||
Transport: &tr,
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &tr,
|
||||
CheckRedirect: logRedirect,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
httpResponse, err := client.Do(httpRequest)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ var log = mocks.UseMockLog()
|
|||
const expectedToken = "THETOKEN"
|
||||
const pathWrongToken = "wrongtoken"
|
||||
const path404 = "404"
|
||||
const pathFound = "302"
|
||||
const pathMoved = "301"
|
||||
|
||||
func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableTLS bool) {
|
||||
m := http.NewServeMux()
|
||||
|
|
@ -68,6 +70,12 @@ func simpleSrv(t *testing.T, token string, stopChan, waitChan chan bool, enableT
|
|||
} else if strings.HasSuffix(r.URL.Path, pathWrongToken) {
|
||||
t.Logf("SIMPLESRV: Got a wrongtoken req\n")
|
||||
fmt.Fprintf(w, "wrongtoken")
|
||||
} else if strings.HasSuffix(r.URL.Path, pathMoved) {
|
||||
t.Logf("SIMPLESRV: Got a 301 redirect req\n")
|
||||
http.Redirect(w, r, "valid", 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathFound) {
|
||||
t.Logf("SIMPLESRV: Got a 302 redirect req\n")
|
||||
http.Redirect(w, r, pathMoved, 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, "wait") {
|
||||
t.Logf("SIMPLESRV: Got a wait req\n")
|
||||
time.Sleep(time.Second * 3)
|
||||
|
|
@ -246,6 +254,21 @@ func TestSimpleHttp(t *testing.T) {
|
|||
test.AssertNotError(t, err, chall.Path)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Path = pathMoved
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Path)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Path = pathFound
|
||||
finChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
test.AssertEquals(t, finChall.Status, core.StatusValid)
|
||||
test.AssertNotError(t, err, chall.Path)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
|
||||
|
||||
log.Clear()
|
||||
chall.Path = path404
|
||||
invalidChall, err = va.validateSimpleHTTP(ident, chall)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
// Paths are the ACME-spec identified URL path-segments for various methods
|
||||
const (
|
||||
DirectoryPath = "/directory"
|
||||
NewRegPath = "/acme/new-reg"
|
||||
RegPath = "/acme/reg/"
|
||||
NewAuthzPath = "/acme/new-authz"
|
||||
|
|
@ -58,6 +59,9 @@ type WebFrontEndImpl struct {
|
|||
NewCert string
|
||||
CertBase string
|
||||
|
||||
// JSON encoded endpoint directory
|
||||
DirectoryJSON []byte
|
||||
|
||||
// Issuer certificate (DER) for /acme/issuer-cert
|
||||
IssuerCert []byte
|
||||
|
||||
|
|
@ -66,6 +70,12 @@ type WebFrontEndImpl struct {
|
|||
|
||||
// Register of anti-replay nonces
|
||||
nonceService core.NonceService
|
||||
|
||||
// Cache settings
|
||||
CertCacheDuration time.Duration
|
||||
CertNoCacheExpirationWindow time.Duration
|
||||
IndexCacheDuration time.Duration
|
||||
IssuerCacheDuration time.Duration
|
||||
}
|
||||
|
||||
func statusCodeFromError(err interface{}) int {
|
||||
|
|
@ -182,7 +192,7 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h fun
|
|||
|
||||
// Handler returns an http.Handler that uses various functions for
|
||||
// various ACME-specified paths.
|
||||
func (wfe *WebFrontEndImpl) Handler() http.Handler {
|
||||
func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) {
|
||||
wfe.NewReg = wfe.BaseURL + NewRegPath
|
||||
wfe.RegBase = wfe.BaseURL + RegPath
|
||||
wfe.NewAuthz = wfe.BaseURL + NewAuthzPath
|
||||
|
|
@ -190,8 +200,22 @@ func (wfe *WebFrontEndImpl) Handler() http.Handler {
|
|||
wfe.NewCert = wfe.BaseURL + NewCertPath
|
||||
wfe.CertBase = wfe.BaseURL + CertPath
|
||||
|
||||
// Only generate directory once
|
||||
directory := map[string]string{
|
||||
"new-reg": wfe.NewReg,
|
||||
"new-authz": wfe.NewAuthz,
|
||||
"new-cert": wfe.NewCert,
|
||||
"revoke-cert": wfe.BaseURL + RevokeCertPath,
|
||||
}
|
||||
directoryJSON, err := json.Marshal(directory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wfe.DirectoryJSON = directoryJSON
|
||||
|
||||
m := http.NewServeMux()
|
||||
wfe.HandleFunc(m, "/", wfe.Index, "GET")
|
||||
wfe.HandleFunc(m, DirectoryPath, wfe.Directory, "GET")
|
||||
wfe.HandleFunc(m, NewRegPath, wfe.NewRegistration, "POST")
|
||||
wfe.HandleFunc(m, NewAuthzPath, wfe.NewAuthorization, "POST")
|
||||
wfe.HandleFunc(m, NewCertPath, wfe.NewCertificate, "POST")
|
||||
|
|
@ -202,7 +226,7 @@ func (wfe *WebFrontEndImpl) Handler() http.Handler {
|
|||
wfe.HandleFunc(m, TermsPath, wfe.Terms, "GET")
|
||||
wfe.HandleFunc(m, IssuerPath, wfe.Issuer, "GET")
|
||||
wfe.HandleFunc(m, BuildIDPath, wfe.BuildID, "GET")
|
||||
return m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Method implementations
|
||||
|
|
@ -231,6 +255,19 @@ func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Re
|
|||
`))
|
||||
tmpl.Execute(response, wfe)
|
||||
response.Header().Set("Content-Type", "text/html")
|
||||
addCacheHeader(response, wfe.IndexCacheDuration.Seconds())
|
||||
}
|
||||
|
||||
func addNoCacheHeader(w http.ResponseWriter) {
|
||||
w.Header().Add("Cache-Control", "public, max-age=0, no-cache")
|
||||
}
|
||||
|
||||
func addCacheHeader(w http.ResponseWriter, age float64) {
|
||||
w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%.f", age))
|
||||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write(wfe.DirectoryJSON)
|
||||
}
|
||||
|
||||
// The ID is always the last slash-separated token in the path
|
||||
|
|
@ -933,12 +970,14 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
|
|||
if !strings.HasPrefix(path, CertPath) {
|
||||
logEvent.Error = "Certificate not found"
|
||||
wfe.sendError(response, logEvent.Error, path, http.StatusNotFound)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
serial := path[len(CertPath):]
|
||||
if len(serial) != 16 || !allHex.Match([]byte(serial)) {
|
||||
logEvent.Error = "Certificate not found"
|
||||
wfe.sendError(response, logEvent.Error, serial, http.StatusNotFound)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
wfe.log.Debug(fmt.Sprintf("Requested certificate ID %s", serial))
|
||||
|
|
@ -950,11 +989,14 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
|
|||
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
|
||||
wfe.sendError(response, "Multiple certificates with same short serial", err, http.StatusConflict)
|
||||
} else {
|
||||
wfe.sendError(response, "Not found", err, http.StatusNotFound)
|
||||
addNoCacheHeader(response)
|
||||
wfe.sendError(response, "Certificate not found", err, http.StatusNotFound)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
addCacheHeader(response, wfe.CertCacheDuration.Seconds())
|
||||
|
||||
// TODO Content negotiation
|
||||
response.Header().Set("Content-Type", "application/pkix-cert")
|
||||
response.Header().Add("Link", link(IssuerPath, "up"))
|
||||
|
|
@ -985,6 +1027,8 @@ func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.R
|
|||
logEvent := wfe.populateRequestEvent(request)
|
||||
defer wfe.logRequestDetails(&logEvent)
|
||||
|
||||
addCacheHeader(response, wfe.IssuerCacheDuration.Seconds())
|
||||
|
||||
// TODO Content negotiation
|
||||
response.Header().Set("Content-Type", "application/pkix-cert")
|
||||
response.WriteHeader(http.StatusOK)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
package wfe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
|
|
@ -193,13 +194,12 @@ func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) {
|
|||
RegistrationID: 1,
|
||||
DER: certBlock.Bytes,
|
||||
}, nil
|
||||
} else {
|
||||
return core.Certificate{}, errors.New("No cert")
|
||||
}
|
||||
return core.Certificate{}, errors.New("No cert")
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetCertificateByShortSerial(string) (core.Certificate, error) {
|
||||
return core.Certificate{}, nil
|
||||
func (sa *MockSA) GetCertificateByShortSerial(serial string) (core.Certificate, error) {
|
||||
return sa.GetCertificate("0000000000000000" + serial)
|
||||
}
|
||||
|
||||
func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, error) {
|
||||
|
|
@ -412,7 +412,8 @@ func TestHandleFunc(t *testing.T) {
|
|||
|
||||
func TestStandardHeaders(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
mux := wfe.Handler()
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
cases := []struct {
|
||||
path string
|
||||
|
|
@ -446,6 +447,7 @@ func TestStandardHeaders(t *testing.T) {
|
|||
|
||||
func TestIndex(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.IndexCacheDuration = time.Second * 10
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
|
|
@ -458,21 +460,42 @@ func TestIndex(t *testing.T) {
|
|||
test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n")
|
||||
test.Assert(t, strings.Contains(responseWriter.Body.String(), wfe.NewReg),
|
||||
"new-reg not found")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
responseWriter.Header().Del("Cache-Control")
|
||||
url, _ = url.Parse("/foo")
|
||||
wfe.Index(responseWriter, &http.Request{
|
||||
URL: url,
|
||||
})
|
||||
//test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), "404 page not found\n")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "")
|
||||
}
|
||||
|
||||
func TestDirectory(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.BaseURL = "http://localhost:4300"
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
url, _ := url.Parse("/directory")
|
||||
mux.ServeHTTP(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: url,
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"new-authz":"http://localhost:4300/acme/new-authz","new-cert":"http://localhost:4300/acme/new-cert","new-reg":"http://localhost:4300/acme/new-reg","revoke-cert":"http://localhost:4300/acme/revoke-cert"}`)
|
||||
}
|
||||
|
||||
// TODO: Write additional test cases for:
|
||||
// - RA returns with a failure
|
||||
func TestIssueCertificate(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
mux := wfe.Handler()
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
// TODO: Use a mock RA so we can test various conditions of authorized, not authorized, etc.
|
||||
ra := ra.NewRegistrationAuthorityImpl()
|
||||
|
|
@ -659,7 +682,8 @@ func TestChallenge(t *testing.T) {
|
|||
|
||||
func TestNewRegistration(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
mux := wfe.Handler()
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
|
@ -903,7 +927,8 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) {
|
|||
|
||||
func TestAuthorization(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
mux := wfe.Handler()
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
|
@ -982,13 +1007,14 @@ func TestAuthorization(t *testing.T) {
|
|||
test.AssertEquals(t, responseWriter.Body.String(), "{\"identifier\":{\"type\":\"dns\",\"value\":\"test.com\"}}")
|
||||
|
||||
var authz core.Authorization
|
||||
err := json.Unmarshal([]byte(responseWriter.Body.String()), &authz)
|
||||
err = json.Unmarshal([]byte(responseWriter.Body.String()), &authz)
|
||||
test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object")
|
||||
}
|
||||
|
||||
func TestRegistration(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
mux := wfe.Handler()
|
||||
mux, err := wfe.Handler()
|
||||
test.AssertNotError(t, err, "Problem setting up HTTP handlers")
|
||||
|
||||
wfe.RA = &MockRegistrationAuthority{}
|
||||
wfe.SA = &MockSA{}
|
||||
|
|
@ -1110,3 +1136,75 @@ func TestTermsRedirect(t *testing.T) {
|
|||
agreementURL)
|
||||
test.AssertEquals(t, responseWriter.Code, 302)
|
||||
}
|
||||
|
||||
func TestIssuer(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.IssuerCacheDuration = time.Second * 10
|
||||
wfe.IssuerCert = []byte{0, 0, 1}
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
wfe.Issuer(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||
test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), wfe.IssuerCert) == 0, "Incorrect bytes returned")
|
||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||
}
|
||||
|
||||
func TestGetCertificate(t *testing.T) {
|
||||
wfe := setupWFE(t)
|
||||
wfe.CertCacheDuration = time.Second * 10
|
||||
wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7
|
||||
wfe.SA = &MockSA{}
|
||||
|
||||
certPemBytes, _ := ioutil.ReadFile("test/178.crt")
|
||||
certBlock, _ := pem.Decode(certPemBytes)
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
// Valid short serial, cached
|
||||
path, _ := url.Parse("/acme/cert/00000000000000b2")
|
||||
wfe.Certificate(responseWriter, &http.Request{
|
||||
Method: "GET",
|
||||
URL: path,
|
||||
})
|
||||
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")
|
||||
|
||||
// Unused short serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/00000000000000ff")
|
||||
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 short 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 short serial, no cache
|
||||
responseWriter = httptest.NewRecorder()
|
||||
path, _ = url.Parse("/acme/cert/00000000000000")
|
||||
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"}`)
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue