Merge branch 'master' into percent-v
This commit is contained in:
commit
246b9bf0fb
|
|
@ -3,7 +3,7 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package core
|
||||
package bdns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -113,6 +113,15 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// DNSResolver defines methods used for DNS resolution
|
||||
type DNSResolver interface {
|
||||
ExchangeOne(string, uint16) (*dns.Msg, time.Duration, error)
|
||||
LookupTXT(string) ([]string, time.Duration, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, error)
|
||||
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
|
||||
LookupMX(string) ([]string, time.Duration, error)
|
||||
}
|
||||
|
||||
// DNSResolverImpl represents a client that talks to an external resolver
|
||||
type DNSResolverImpl struct {
|
||||
DNSClient *dns.Client
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package core
|
||||
package bdns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
package dns
|
||||
// Copyright 2015 ISRG. All rights reserved
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package bdns
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
package dns
|
||||
// Copyright 2015 ISRG. All rights reserved
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package bdns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
|
@ -10,7 +10,7 @@ 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/core"
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
|
||||
|
|
@ -63,9 +63,9 @@ func main() {
|
|||
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
|
||||
if !c.Common.DNSAllowLoopbackAddresses {
|
||||
rai.DNSResolver = core.NewDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver})
|
||||
rai.DNSResolver = bdns.NewDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver})
|
||||
} else {
|
||||
rai.DNSResolver = core.NewTestDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver})
|
||||
rai.DNSResolver = bdns.NewTestDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver})
|
||||
}
|
||||
|
||||
rai.VA = vac
|
||||
|
|
|
|||
|
|
@ -10,9 +10,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/bdns"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/rpc"
|
||||
"github.com/letsencrypt/boulder/va"
|
||||
|
|
@ -46,11 +46,12 @@ func main() {
|
|||
dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse DNS timeout")
|
||||
if !c.Common.DNSAllowLoopbackAddresses {
|
||||
vai.DNSResolver = core.NewDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver})
|
||||
vai.DNSResolver = bdns.NewDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver})
|
||||
} else {
|
||||
vai.DNSResolver = core.NewTestDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver})
|
||||
vai.DNSResolver = bdns.NewTestDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver})
|
||||
}
|
||||
vai.UserAgent = c.VA.UserAgent
|
||||
vai.IssuerDomain = c.VA.IssuerDomain
|
||||
|
||||
amqpConf := c.VA.AMQP
|
||||
rac, err := rpc.NewRegistrationAuthorityClient(clientName, amqpConf, stats)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ type Config struct {
|
|||
|
||||
UserAgent string
|
||||
|
||||
IssuerDomain string
|
||||
|
||||
PortConfig va.PortConfig
|
||||
|
||||
MaxConcurrentRPCServerRequests int64
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"time"
|
||||
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
|
||||
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
)
|
||||
|
||||
|
|
@ -140,15 +139,6 @@ type CertificateAuthorityDatabase interface {
|
|||
Begin() (*gorp.Transaction, error)
|
||||
}
|
||||
|
||||
// DNSResolver defines methods used for DNS resolution
|
||||
type DNSResolver interface {
|
||||
ExchangeOne(string, uint16) (*dns.Msg, time.Duration, error)
|
||||
LookupTXT(string) ([]string, time.Duration, error)
|
||||
LookupHost(string) ([]net.IP, time.Duration, error)
|
||||
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
|
||||
LookupMX(string) ([]string, time.Duration, error)
|
||||
}
|
||||
|
||||
// Publisher defines the public interface for the Boulder Publisher
|
||||
type Publisher interface {
|
||||
SubmitToCT([]byte) error
|
||||
|
|
|
|||
47
core/util.go
47
core/util.go
|
|
@ -28,6 +28,7 @@ import (
|
|||
"io/ioutil"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
|
@ -35,6 +36,7 @@ import (
|
|||
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
)
|
||||
|
||||
// Package Variables Variables
|
||||
|
|
@ -123,6 +125,51 @@ func (e TooManyRPCRequestsError) Error() string { return string(e) }
|
|||
func (e ServiceUnavailableError) Error() string { return string(e) }
|
||||
func (e BadNonceError) Error() string { return string(e) }
|
||||
|
||||
// statusTooManyRequests is the HTTP status code meant for rate limiting
|
||||
// errors. It's not currently in the net/http library so we add it here.
|
||||
const statusTooManyRequests = 429
|
||||
|
||||
// ProblemDetailsForError turns an error into a ProblemDetails with the special
|
||||
// case of returning the same error back if its already a ProblemDetails. If the
|
||||
// error is of an type unknown to ProblemDetailsForError, it will return a
|
||||
// ServerInternal ProblemDetails.
|
||||
func ProblemDetailsForError(err error, msg string) *probs.ProblemDetails {
|
||||
switch e := err.(type) {
|
||||
case *probs.ProblemDetails:
|
||||
return e
|
||||
case MalformedRequestError, SyntaxError:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case NotSupportedError:
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.ServerInternalProblem,
|
||||
Detail: fmt.Sprintf("%s :: %s", msg, err),
|
||||
HTTPStatus: http.StatusNotImplemented,
|
||||
}
|
||||
case UnauthorizedError:
|
||||
return probs.Unauthorized(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case NotFoundError:
|
||||
return probs.NotFound(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case LengthRequiredError:
|
||||
prob := probs.Malformed("missing Content-Length header")
|
||||
prob.HTTPStatus = http.StatusLengthRequired
|
||||
return prob
|
||||
case SignatureValidationError:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case RateLimitedError:
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.RateLimitedProblem,
|
||||
Detail: fmt.Sprintf("%s :: %s", msg, err),
|
||||
HTTPStatus: statusTooManyRequests,
|
||||
}
|
||||
case BadNonceError:
|
||||
return probs.BadNonce(fmt.Sprintf("%s :: %s", msg, err))
|
||||
default:
|
||||
// Internal server error messages may include sensitive data, so we do
|
||||
// not include it.
|
||||
return probs.ServerInternal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Base64 functions
|
||||
|
||||
func pad(x string) string {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ import (
|
|||
"math"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
|
@ -128,3 +130,39 @@ func TestUnmarshalAcmeURL(t *testing.T) {
|
|||
t.Errorf("Expected error parsing ':', but got nil err.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProblemDetailsFromError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
err error
|
||||
statusCode int
|
||||
problem probs.ProblemType
|
||||
}{
|
||||
{InternalServerError("foo"), 500, probs.ServerInternalProblem},
|
||||
{NotSupportedError("foo"), 501, probs.ServerInternalProblem},
|
||||
{MalformedRequestError("foo"), 400, probs.MalformedProblem},
|
||||
{UnauthorizedError("foo"), 403, probs.UnauthorizedProblem},
|
||||
{NotFoundError("foo"), 404, probs.MalformedProblem},
|
||||
{SyntaxError("foo"), 400, probs.MalformedProblem},
|
||||
{SignatureValidationError("foo"), 400, probs.MalformedProblem},
|
||||
{RateLimitedError("foo"), 429, probs.RateLimitedProblem},
|
||||
{LengthRequiredError("foo"), 411, probs.MalformedProblem},
|
||||
{BadNonceError("foo"), 400, probs.BadNonceProblem},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
p := ProblemDetailsForError(c.err, "k")
|
||||
if p.HTTPStatus != c.statusCode {
|
||||
t.Errorf("Incorrect status code for %s. Expected %d, got %d", reflect.TypeOf(c.err).Name(), c.statusCode, p.HTTPStatus)
|
||||
}
|
||||
if probs.ProblemType(p.Type) != c.problem {
|
||||
t.Errorf("Expected problem urn %#v, got %#v", c.problem, p.Type)
|
||||
}
|
||||
}
|
||||
|
||||
expected := &probs.ProblemDetails{
|
||||
Type: probs.MalformedProblem,
|
||||
HTTPStatus: 200,
|
||||
Detail: "gotcha",
|
||||
}
|
||||
p := ProblemDetailsForError(expected, "k")
|
||||
test.AssertDeepEquals(t, expected, p)
|
||||
}
|
||||
|
|
|
|||
121
probs/probs.go
121
probs/probs.go
|
|
@ -1,6 +1,9 @@
|
|||
package probs
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Error types that can be used in ACME payloads
|
||||
const (
|
||||
|
|
@ -20,11 +23,121 @@ type ProblemType string
|
|||
// ProblemDetails objects represent problem documents
|
||||
// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
|
||||
type ProblemDetails struct {
|
||||
Type ProblemType `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
HTTPStatus int `json:"status,omitempty"`
|
||||
Type ProblemType `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
// HTTPStatus is the HTTP status code the ProblemDetails should probably be sent
|
||||
// as.
|
||||
HTTPStatus int `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (pd *ProblemDetails) Error() string {
|
||||
return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
|
||||
}
|
||||
|
||||
// statusTooManyRequests is the HTTP status code meant for rate limiting
|
||||
// errors. It's not currently in the net/http library so we add it here.
|
||||
const statusTooManyRequests = 429
|
||||
|
||||
// ProblemDetailsToStatusCode inspects the given ProblemDetails to figure out
|
||||
// what HTTP status code it should represent. It should only be used by the WFE
|
||||
// but is included in this package because of its reliance on ProblemTypes.
|
||||
func ProblemDetailsToStatusCode(prob *ProblemDetails) int {
|
||||
if prob.HTTPStatus != 0 {
|
||||
return prob.HTTPStatus
|
||||
}
|
||||
switch prob.Type {
|
||||
case ConnectionProblem, MalformedProblem, TLSProblem, UnknownHostProblem, BadNonceProblem:
|
||||
return http.StatusBadRequest
|
||||
case ServerInternalProblem:
|
||||
return http.StatusInternalServerError
|
||||
case UnauthorizedProblem:
|
||||
return http.StatusForbidden
|
||||
case RateLimitedProblem:
|
||||
return statusTooManyRequests
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// BadNonce returns a ProblemDetails with a BadNonceProblem and a 400 Bad
|
||||
// Request status code.
|
||||
func BadNonce(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: BadNonceProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict returns a ProblemDetails with a MalformedProblem and a 409 Conflict
|
||||
// status code.
|
||||
func Conflict(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusConflict,
|
||||
}
|
||||
}
|
||||
|
||||
// Malformed returns a ProblemDetails with a MalformedProblem and a 400 Bad
|
||||
// Request status code.
|
||||
func Malformed(detail string, args ...interface{}) *ProblemDetails {
|
||||
if len(args) > 0 {
|
||||
detail = fmt.Sprintf(detail, args...)
|
||||
}
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound returns a ProblemDetails with a MalformedProblem and a 404 Not Found
|
||||
// status code.
|
||||
func NotFound(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
// ServerInternal returns a ProblemDetails with a ServerInternalProblem and a
|
||||
// 500 Internal Server Failure status code.
|
||||
func ServerInternal(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: ServerInternalProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
// Unauthorized returns a ProblemDetails with an UnauthorizedProblem and a 403
|
||||
// Forbidden status code.
|
||||
func Unauthorized(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: UnauthorizedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
|
||||
// MethodNotAllowed returns a ProblemDetails representing a disallowed HTTP
|
||||
// method error.
|
||||
func MethodNotAllowed() *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: "Method not allowed",
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
}
|
||||
}
|
||||
|
||||
// ContentLengthRequired returns a ProblemDetails representing a missing
|
||||
// Content-Length header error
|
||||
func ContentLengthRequired() *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: "missing Content-Length header",
|
||||
HTTPStatus: http.StatusLengthRequired,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import (
|
|||
|
||||
func TestProblemDetails(t *testing.T) {
|
||||
pd := &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: "Wat? o.O"}
|
||||
Type: MalformedProblem,
|
||||
Detail: "Wat? o.O",
|
||||
HTTPStatus: 403,
|
||||
}
|
||||
test.AssertEquals(t, pd.Error(), "urn:acme:error:malformed :: Wat? o.O")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/net/publicsuffix"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/dns"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ type RegistrationAuthorityImpl struct {
|
|||
SA core.StorageAuthority
|
||||
PA core.PolicyAuthority
|
||||
stats statsd.Statter
|
||||
DNSResolver core.DNSResolver
|
||||
DNSResolver bdns.DNSResolver
|
||||
clk clock.Clock
|
||||
log *blog.AuditLogger
|
||||
dc *DomainCheck
|
||||
|
|
@ -81,7 +81,7 @@ func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, sta
|
|||
var errUnparseableEmail = errors.New("not a valid e-mail address")
|
||||
var errEmptyDNSResponse = errors.New("empty DNS response")
|
||||
|
||||
func validateEmail(address string, resolver core.DNSResolver) (rtt time.Duration, count int64, err error) {
|
||||
func validateEmail(address string, resolver bdns.DNSResolver) (rtt time.Duration, count int64, err error) {
|
||||
_, err = mail.ParseAddress(address)
|
||||
if err != nil {
|
||||
return time.Duration(0), 0, errUnparseableEmail
|
||||
|
|
@ -101,7 +101,7 @@ func validateEmail(address string, resolver core.DNSResolver) (rtt time.Duration
|
|||
}
|
||||
}
|
||||
if err != nil {
|
||||
problem := dns.ProblemDetailsFromDNSError(err)
|
||||
problem := bdns.ProblemDetailsFromDNSError(err)
|
||||
err = core.MalformedRequestError(problem.Detail)
|
||||
}
|
||||
rtt = rtt1 + rtt2
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
|
||||
"va": {
|
||||
"userAgent": "boulder",
|
||||
"issuerDomain": "happy-hacker-ca.invalid",
|
||||
"debugAddr": "localhost:8004",
|
||||
"portConfig": {
|
||||
"httpPort": 5002,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,23 @@ func dnsHandler(w dns.ResponseWriter, r *dns.Msg) {
|
|||
record.Preference = 10
|
||||
|
||||
m.Answer = append(m.Answer, record)
|
||||
case dns.TypeCAA:
|
||||
if q.Name == "bad-caa-reserved.com." || q.Name == "good-caa-reserved.com." {
|
||||
record := new(dns.CAA)
|
||||
record.Hdr = dns.RR_Header{
|
||||
Name: q.Name,
|
||||
Rrtype: dns.TypeCAA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 0,
|
||||
}
|
||||
record.Tag = "issue"
|
||||
if q.Name == "bad-caa-reserved.com." {
|
||||
record.Value = "sad-hacker-ca.invalid"
|
||||
} else if q.Name == "good-caa-reserved.com." {
|
||||
record.Value = "happy-hacker-ca.invalid"
|
||||
}
|
||||
m.Answer = append(m.Answer, record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,18 +153,6 @@ def verify_ct_submission(expectedSubmissions, url):
|
|||
die(ExitStatus.CTFailure)
|
||||
|
||||
def run_node_test():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.connect(('localhost', 4000))
|
||||
except socket.error, e:
|
||||
print("Cannot connect to WFE")
|
||||
die(ExitStatus.Error)
|
||||
|
||||
os.chdir('test/js')
|
||||
|
||||
if subprocess.Popen('npm install', shell=True).wait() != 0:
|
||||
print("\n Installing NPM modules failed")
|
||||
die(ExitStatus.Error)
|
||||
cert_file = os.path.join(tempdir, "cert.der")
|
||||
cert_file_pem = os.path.join(tempdir, "cert.pem")
|
||||
key_file = os.path.join(tempdir, "key.pem")
|
||||
|
|
@ -203,6 +191,26 @@ def run_node_test():
|
|||
wait_for_ocsp_revoked(cert_file_pem, "../test-ca.pem", ee_ocsp_url)
|
||||
return 0
|
||||
|
||||
def run_caa_node_test():
|
||||
cert_file = os.path.join(tempdir, "cert.der")
|
||||
key_file = os.path.join(tempdir, "key.pem")
|
||||
|
||||
def runNode(domain):
|
||||
return subprocess.Popen('''
|
||||
node test.js --email foo@letsencrypt.org --agree true \
|
||||
--domains %s --new-reg http://localhost:4000/acme/new-reg \
|
||||
--certKey %s --cert %s
|
||||
''' % (domain, key_file, cert_file),
|
||||
shell=True).wait()
|
||||
|
||||
if runNode("bad-caa-reserved.com") == 0:
|
||||
print("\nIssused certificate for domain with bad CAA records")
|
||||
die(ExitStatus.NodeFailure)
|
||||
|
||||
if runNode("good-caa-reserved.com") != 0:
|
||||
print("\nDidn't issue certificate for domain with good CAA records")
|
||||
die(ExitStatus.NodeFailure)
|
||||
|
||||
|
||||
def run_client_tests():
|
||||
root = os.environ.get("LETSENCRYPT_PATH")
|
||||
|
|
@ -251,7 +259,12 @@ def main():
|
|||
die(ExitStatus.Error)
|
||||
|
||||
if args.run_all or args.run_node:
|
||||
os.chdir('test/js')
|
||||
if subprocess.Popen('npm install', shell=True).wait() != 0:
|
||||
print("\n Installing NPM modules failed")
|
||||
die(ExitStatus.Error)
|
||||
run_node_test()
|
||||
run_caa_node_test()
|
||||
|
||||
# Simulate a disconnection from RabbitMQ to make sure reconnects work.
|
||||
startservers.bounce_forward()
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
bdns "github.com/letsencrypt/boulder/dns"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ var validationTimeout = time.Second * 5
|
|||
type ValidationAuthorityImpl struct {
|
||||
RA core.RegistrationAuthority
|
||||
log *blog.AuditLogger
|
||||
DNSResolver core.DNSResolver
|
||||
DNSResolver bdns.DNSResolver
|
||||
IssuerDomain string
|
||||
SafeBrowsing SafeBrowsing
|
||||
httpPort int
|
||||
|
|
@ -560,15 +560,17 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI
|
|||
}
|
||||
|
||||
func (va *ValidationAuthorityImpl) validateChallengeAndCAA(identifier core.AcmeIdentifier, challenge core.Challenge, regID int64) (core.Challenge, error) {
|
||||
ch := make(chan *probs.ProblemDetails, 1)
|
||||
go func() {
|
||||
ch <- va.checkCAA(identifier, regID)
|
||||
}()
|
||||
|
||||
result, err := va.validateChallenge(identifier, challenge)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Checking CAA happens after challenge validation because DNS errors affect
|
||||
// both, and giving a DNS error on validation makes more sense than a DNS
|
||||
// error on CAA.
|
||||
problemDetails := va.checkCAA(identifier, regID)
|
||||
problemDetails := <-ch
|
||||
if problemDetails != nil {
|
||||
result.Error = problemDetails
|
||||
result.Status = core.StatusInvalid
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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/bdns"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
|
@ -795,7 +796,7 @@ func TestDNSValidationServFail(t *testing.T) {
|
|||
func TestDNSValidationNoServer(t *testing.T) {
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
va := NewValidationAuthorityImpl(&PortConfig{}, nil, stats, clock.Default())
|
||||
va.DNSResolver = core.NewTestDNSResolverImpl(time.Second*5, []string{})
|
||||
va.DNSResolver = bdns.NewTestDNSResolverImpl(time.Second*5, []string{})
|
||||
mockRA := &MockRegistrationAuthority{}
|
||||
va.RA = mockRA
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
func TestRejectsNone(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
_, _, _, prob := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "none",
|
||||
|
|
@ -24,17 +24,17 @@ func TestRejectsNone(t *testing.T) {
|
|||
"signature": ""
|
||||
}
|
||||
`), true, "foo")
|
||||
if err == nil {
|
||||
if prob == nil {
|
||||
t.Fatalf("verifyPOST did not reject JWS with alg: 'none'")
|
||||
}
|
||||
if err.Error() != "algorithm 'none' in JWS header not acceptable" {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %s", err)
|
||||
if prob.Detail != "algorithm 'none' in JWS header not acceptable" {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %#v", prob)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectsHS256(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
_, _, _, prob := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "HS256",
|
||||
|
|
@ -48,12 +48,12 @@ func TestRejectsHS256(t *testing.T) {
|
|||
"signature": ""
|
||||
}
|
||||
`), true, "foo")
|
||||
if err == nil {
|
||||
if prob == nil {
|
||||
t.Fatalf("verifyPOST did not reject JWS with alg: 'HS256'")
|
||||
}
|
||||
expected := "algorithm 'HS256' in JWS header not acceptable"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got '%s', wanted %s", err, expected)
|
||||
if prob.Detail != expected {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got '%s', wanted %s", prob, expected)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,12 +40,6 @@ const (
|
|||
TermsPath = "/terms"
|
||||
IssuerPath = "/acme/issuer-cert"
|
||||
BuildIDPath = "/build"
|
||||
|
||||
// StatusRateLimited is not in net/http
|
||||
StatusRateLimited = 429
|
||||
// statusBadNonce is used as a sentinel value to force sendError to send
|
||||
// the proper error type
|
||||
statusBadNonce = 1400
|
||||
)
|
||||
|
||||
// WebFrontEndImpl provides all the logic for Boulder's web-facing interface,
|
||||
|
|
@ -95,34 +89,6 @@ type WebFrontEndImpl struct {
|
|||
ShutdownKillTimeout time.Duration
|
||||
}
|
||||
|
||||
func statusCodeFromError(err error) int {
|
||||
// Populate these as needed. We probably should trim the error list in util.go
|
||||
switch err.(type) {
|
||||
case core.MalformedRequestError:
|
||||
return http.StatusBadRequest
|
||||
case core.NotSupportedError:
|
||||
return http.StatusNotImplemented
|
||||
case core.SyntaxError:
|
||||
return http.StatusBadRequest
|
||||
case core.UnauthorizedError:
|
||||
return http.StatusForbidden
|
||||
case core.NotFoundError:
|
||||
return http.StatusNotFound
|
||||
case core.LengthRequiredError:
|
||||
return http.StatusLengthRequired
|
||||
case core.SignatureValidationError:
|
||||
return http.StatusBadRequest
|
||||
case core.InternalServerError:
|
||||
return http.StatusInternalServerError
|
||||
case core.RateLimitedError:
|
||||
return StatusRateLimited
|
||||
case core.BadNonceError:
|
||||
return statusBadNonce
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// NewWebFrontEndImpl constructs a web service for Boulder
|
||||
func NewWebFrontEndImpl(stats statsd.Statter, clk clock.Clock) (WebFrontEndImpl, error) {
|
||||
logger := blog.GetAuditLogger()
|
||||
|
|
@ -201,9 +167,8 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfe
|
|||
}
|
||||
|
||||
if !methodsMap[request.Method] {
|
||||
msg := "Method not allowed"
|
||||
response.Header().Set("Allow", methodsStr)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -318,8 +283,7 @@ func parseIDFromPath(path string) string {
|
|||
}
|
||||
|
||||
const (
|
||||
unknownKey = "No registration exists matching provided key"
|
||||
malformedJWS = "Unable to read/verify body"
|
||||
unknownKey = "No registration exists matching provided key"
|
||||
)
|
||||
|
||||
// verifyPOST reads and parses the request body, looks up the Registration
|
||||
|
|
@ -334,44 +298,39 @@ const (
|
|||
// the key itself. verifyPOST also appends its errors to requestEvent.Errors so
|
||||
// code calling it does not need to if they imediately return a response to the
|
||||
// user.
|
||||
func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) {
|
||||
var err error
|
||||
func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, *probs.ProblemDetails) {
|
||||
// TODO: We should return a pointer to a registration, which can be nil,
|
||||
// rather the a registration value with a sentinel value.
|
||||
// https://github.com/letsencrypt/boulder/issues/877
|
||||
reg := core.Registration{ID: 0}
|
||||
|
||||
if _, ok := request.Header["Content-Length"]; !ok {
|
||||
err = core.LengthRequiredError("Content-Length header is required for POST.")
|
||||
wfe.stats.Inc("WFE.HTTP.ClientErrors.LengthRequiredError", 1, 1.0)
|
||||
logEvent.AddError("missing Content-Length header on POST")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.ContentLengthRequired()
|
||||
}
|
||||
|
||||
// Read body
|
||||
if request.Body == nil {
|
||||
err = core.MalformedRequestError("No body on POST")
|
||||
wfe.stats.Inc("WFE.Errors.NoPOSTBody", 1, 1.0)
|
||||
logEvent.AddError("no body on POST")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("No body on POST")
|
||||
}
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
err = core.InternalServerError("unable to read request body")
|
||||
wfe.stats.Inc("WFE.Errors.UnableToReadRequestBody", 1, 1.0)
|
||||
logEvent.AddError("unable to read request body")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.ServerInternal("unable to read request body")
|
||||
}
|
||||
|
||||
body := string(bodyBytes)
|
||||
// Parse as JWS
|
||||
parsedJws, err := jose.ParseSigned(body)
|
||||
if err != nil {
|
||||
puberr := core.SignatureValidationError("Parse error reading JWS")
|
||||
wfe.stats.Inc("WFE.Errors.UnableToParseJWS", 1, 1.0)
|
||||
logEvent.AddError("could not JSON parse body into JWS: %s", err)
|
||||
return nil, nil, reg, puberr
|
||||
return nil, nil, reg, probs.Malformed("Parse error reading JWS")
|
||||
}
|
||||
|
||||
// Verify JWS
|
||||
|
|
@ -381,24 +340,21 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
// *anyway*, so it could always lie about what key was used by faking
|
||||
// the signature itself.
|
||||
if len(parsedJws.Signatures) > 1 {
|
||||
err = core.SignatureValidationError("Too many signatures in POST body")
|
||||
wfe.stats.Inc("WFE.Errors.TooManyJWSSignaturesInPOST", 1, 1.0)
|
||||
logEvent.AddError("too many signatures in POST body: %d", len(parsedJws.Signatures))
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("Too many signatures in POST body")
|
||||
}
|
||||
if len(parsedJws.Signatures) == 0 {
|
||||
err = core.SignatureValidationError("POST JWS not signed")
|
||||
wfe.stats.Inc("WFE.Errors.JWSNotSignedInPOST", 1, 1.0)
|
||||
logEvent.AddError("no signatures in POST body")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("POST JWS not signed")
|
||||
}
|
||||
|
||||
submittedKey := parsedJws.Signatures[0].Header.JsonWebKey
|
||||
if submittedKey == nil {
|
||||
err = core.SignatureValidationError("No JWK in JWS header")
|
||||
wfe.stats.Inc("WFE.Errors.NoJWKInJWSSignatureHeader", 1, 1.0)
|
||||
logEvent.AddError("no JWK in JWS signature header in POST body")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("No JWK in JWS header")
|
||||
}
|
||||
|
||||
var key *jose.JsonWebKey
|
||||
|
|
@ -413,14 +369,18 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
if err = core.GoodKey(submittedKey.Key); err != nil {
|
||||
wfe.stats.Inc("WFE.Errors.JWKRejectedByGoodKey", 1, 1.0)
|
||||
logEvent.AddError("JWK in request was rejected by GoodKey: %s", err)
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed(err.Error())
|
||||
}
|
||||
key = submittedKey
|
||||
} else if err != nil {
|
||||
// For all other errors, or if regCheck is true, return error immediately.
|
||||
wfe.stats.Inc("WFE.Errors.UnableToGetRegistrationByKey", 1, 1.0)
|
||||
logEvent.AddError("unable to fetch registration by the given JWK: %s", err)
|
||||
return nil, nil, reg, err
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
return nil, nil, reg, probs.Unauthorized(unknownKey)
|
||||
}
|
||||
|
||||
return nil, nil, reg, core.ProblemDetailsForError(err, "")
|
||||
} else {
|
||||
// If the lookup was successful, use that key.
|
||||
key = ®.Key
|
||||
|
|
@ -430,19 +390,18 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
|
||||
if statName, err := checkAlgorithm(key, parsedJws); err != nil {
|
||||
wfe.stats.Inc(statName, 1, 1.0)
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed(err.Error())
|
||||
}
|
||||
|
||||
payload, err := parsedJws.Verify(key)
|
||||
if err != nil {
|
||||
puberr := core.SignatureValidationError("JWS verification error")
|
||||
wfe.stats.Inc("WFE.Errors.JWSVerificationFailed", 1, 1.0)
|
||||
n := len(body)
|
||||
if n > 100 {
|
||||
n = 100
|
||||
}
|
||||
logEvent.AddError("verification of JWS with the JWK failed: %v; body: %s", err, body[:n])
|
||||
return nil, nil, reg, puberr
|
||||
return nil, nil, reg, probs.Malformed("JWS verification error")
|
||||
}
|
||||
|
||||
// Check that the request has a known anti-replay nonce
|
||||
|
|
@ -450,13 +409,11 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
if err != nil || len(nonce) == 0 {
|
||||
wfe.stats.Inc("WFE.Errors.JWSMissingNonce", 1, 1.0)
|
||||
logEvent.AddError("JWS is missing an anti-replay nonce")
|
||||
err = core.BadNonceError("JWS has no anti-replay nonce")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.BadNonce("JWS has no anti-replay nonce")
|
||||
} else if !wfe.nonceService.Valid(nonce) {
|
||||
wfe.stats.Inc("WFE.Errors.JWSInvalidNonce", 1, 1.0)
|
||||
logEvent.AddError("JWS has an invalid anti-replay nonce")
|
||||
err = core.BadNonceError(fmt.Sprintf("JWS has invalid anti-replay nonce"))
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.BadNonce("JWS has invalid anti-replay nonce")
|
||||
}
|
||||
|
||||
// Check that the "resource" field is present and has the correct value
|
||||
|
|
@ -467,69 +424,41 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
if err != nil {
|
||||
wfe.stats.Inc("WFE.Errors.UnparsableJWSPayload", 1, 1.0)
|
||||
logEvent.AddError("unable to JSON parse resource from JWS payload: %s", err)
|
||||
puberr := core.SignatureValidationError("Request payload did not parse as JSON")
|
||||
return nil, nil, reg, puberr
|
||||
return nil, nil, reg, probs.Malformed("Request payload did not parse as JSON")
|
||||
}
|
||||
if parsedRequest.Resource == "" {
|
||||
wfe.stats.Inc("WFE.Errors.NoResourceInJWSPayload", 1, 1.0)
|
||||
logEvent.AddError("JWS request payload does not specifiy a resource")
|
||||
err = core.MalformedRequestError("Request payload does not specify a resource")
|
||||
return nil, nil, reg, err
|
||||
logEvent.AddError("JWS request payload does not specify a resource")
|
||||
return nil, nil, reg, probs.Malformed("Request payload does not specify a resource")
|
||||
} else if resource != core.AcmeResource(parsedRequest.Resource) {
|
||||
wfe.stats.Inc("WFE.Errors.MismatchedResourceInJWSPayload", 1, 1.0)
|
||||
logEvent.AddError("JWS request payload does not match resource")
|
||||
err = core.MalformedRequestError(fmt.Sprintf("JWS resource payload does not match the HTTP resource: %s != %s", parsedRequest.Resource, resource))
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("JWS resource payload does not match the HTTP resource: %s != %s", parsedRequest.Resource, resource)
|
||||
}
|
||||
|
||||
return []byte(payload), key, reg, nil
|
||||
}
|
||||
|
||||
// Notify the client of an error condition and log it for audit purposes.
|
||||
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, msg string, detail error, code int) {
|
||||
problem := probs.ProblemDetails{Detail: msg, HTTPStatus: code}
|
||||
switch code {
|
||||
case http.StatusPreconditionFailed:
|
||||
fallthrough
|
||||
case http.StatusForbidden:
|
||||
problem.Type = probs.UnauthorizedProblem
|
||||
case http.StatusConflict:
|
||||
fallthrough
|
||||
case http.StatusMethodNotAllowed:
|
||||
fallthrough
|
||||
case http.StatusNotFound:
|
||||
fallthrough
|
||||
case http.StatusBadRequest:
|
||||
fallthrough
|
||||
case http.StatusLengthRequired:
|
||||
problem.Type = probs.MalformedProblem
|
||||
case StatusRateLimited:
|
||||
problem.Type = probs.RateLimitedProblem
|
||||
case statusBadNonce:
|
||||
problem.Type = probs.BadNonceProblem
|
||||
problem.HTTPStatus = http.StatusBadRequest
|
||||
code = http.StatusBadRequest
|
||||
default: // Either http.StatusInternalServerError or an unexpected code
|
||||
problem.Type = probs.ServerInternalProblem
|
||||
}
|
||||
// sendError sends an error response represented by the given ProblemDetails,
|
||||
// and, if the ProblemDetails.Type is ServerInternalProblem, audit logs the
|
||||
// internal ierr.
|
||||
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, prob *probs.ProblemDetails, ierr error) {
|
||||
code := probs.ProblemDetailsToStatusCode(prob)
|
||||
|
||||
// Record details to the log event
|
||||
logEvent.AddError(msg)
|
||||
logEvent.AddError(fmt.Sprintf("%d :: %s :: %s", prob.HTTPStatus, prob.Type, prob.Detail))
|
||||
|
||||
// Only audit log internal errors so users cannot purposefully cause
|
||||
// auditable events.
|
||||
if problem.Type == probs.ServerInternalProblem {
|
||||
if prob.Type == probs.ServerInternalProblem {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
wfe.log.Audit(fmt.Sprintf("Internal error - %s - %s", msg, detail))
|
||||
} else if statusCodeFromError(detail) != http.StatusInternalServerError {
|
||||
// If not an internal error and problem is a custom error type
|
||||
problem.Detail += fmt.Sprintf(" :: %s", detail)
|
||||
wfe.log.Audit(fmt.Sprintf("Internal error - %s - %s", prob.Detail, ierr))
|
||||
}
|
||||
|
||||
problemDoc, err := json.Marshal(problem)
|
||||
problemDoc, err := json.Marshal(prob)
|
||||
if err != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
wfe.log.Audit(fmt.Sprintf("Could not marshal error message: %s - %+v", err, problem))
|
||||
wfe.log.Audit(fmt.Sprintf("Could not marshal error message: %s - %+v", err, prob))
|
||||
problemDoc = []byte("{\"detail\": \"Problem marshalling error message.\"}")
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +469,7 @@ func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *re
|
|||
response.Write(problemDoc)
|
||||
|
||||
wfe.stats.Inc(fmt.Sprintf("WFE.HTTP.ErrorCodes.%d", code), 1, 1.0)
|
||||
problemSegments := strings.Split(string(problem.Type), ":")
|
||||
problemSegments := strings.Split(string(prob.Type), ":")
|
||||
if len(problemSegments) > 0 {
|
||||
wfe.stats.Inc(fmt.Sprintf("WFE.HTTP.ProblemTypes.%s", problemSegments[len(problemSegments)-1]), 1, 1.0)
|
||||
}
|
||||
|
|
@ -553,28 +482,29 @@ func link(url, relation string) string {
|
|||
// NewRegistration is used by clients to submit a new registration/account
|
||||
func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
body, key, _, err := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg)
|
||||
if err != nil {
|
||||
body, key, _, prob := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
wfe.sendError(response, logEvent, malformedJWS, err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil {
|
||||
response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID))
|
||||
wfe.sendError(response, logEvent, "Registration key is already in use", nil, http.StatusConflict)
|
||||
// TODO(#595): check for missing registration err
|
||||
wfe.sendError(response, logEvent, probs.Conflict("Registration key is already in use"), err)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.Registration
|
||||
err = json.Unmarshal(body, &init)
|
||||
err := json.Unmarshal(body, &init)
|
||||
if err != nil {
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling JSON", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
|
||||
return
|
||||
}
|
||||
if len(init.Agreement) > 0 && init.Agreement != wfe.SubscriberAgreementURL {
|
||||
msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed(msg), nil)
|
||||
return
|
||||
}
|
||||
init.Key = *key
|
||||
|
|
@ -585,7 +515,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
init.InitialIP = net.ParseIP(host)
|
||||
} else {
|
||||
logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr)
|
||||
wfe.sendError(response, logEvent, "couldn't parse the remote (that is, the client's) address", nil, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("couldn't parse the remote (that is, the client's) address"), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -593,7 +523,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
reg, err := wfe.RA.NewRegistration(init)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to create new registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new registration", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new registration"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Requester = reg.ID
|
||||
|
|
@ -604,9 +534,10 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID)
|
||||
responseBody, err := json.Marshal(reg)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we just created this registration, it should be OK.
|
||||
// ServerInternal because we just created this registration, and it
|
||||
// should be OK.
|
||||
logEvent.AddError("unable to marshal registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error marshaling registration", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling registration"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -623,30 +554,24 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
|
||||
// NewAuthorization is used by clients to submit a new ID Authorization
|
||||
func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewAuthz)
|
||||
if err != nil {
|
||||
body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceNewAuthz)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := statusCodeFromError(err)
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if currReg.Agreement == "" {
|
||||
wfe.sendError(response, logEvent, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Must agree to subscriber agreement before any further actions"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.Authorization
|
||||
if err = json.Unmarshal(body, &init); err != nil {
|
||||
if err := json.Unmarshal(body, &init); err != nil {
|
||||
logEvent.AddError("unable to JSON unmarshal Authorization: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling JSON", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["Identifier"] = init.Identifier
|
||||
|
|
@ -655,7 +580,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response ht
|
|||
authz, err := wfe.RA.NewAuthorization(init, currReg.ID)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to create new authz: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new authz", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new authz"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["AuthzID"] = authz.ID
|
||||
|
|
@ -665,8 +590,8 @@ func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response ht
|
|||
wfe.prepAuthorizationForDisplay(&authz)
|
||||
responseBody, err := json.Marshal(authz)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we generated the authz, it should be OK
|
||||
wfe.sendError(response, logEvent, "Error marshaling authz", err, http.StatusInternalServerError)
|
||||
// ServerInternal because we generated the authz, it should be OK
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling authz"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -685,10 +610,10 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
|
||||
// We don't ask verifyPOST to verify there is a correponding registration,
|
||||
// because anyone with the right private key can revoke a certificate.
|
||||
body, requestKey, registration, err := wfe.verifyPOST(logEvent, request, false, core.ResourceRevokeCert)
|
||||
if err != nil {
|
||||
body, requestKey, registration, prob := wfe.verifyPOST(logEvent, request, false, core.ResourceRevokeCert)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
wfe.sendError(response, logEvent, malformedJWS, err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -696,29 +621,30 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
CertificateDER core.JSONBuffer `json:"certificate"`
|
||||
}
|
||||
var revokeRequest RevokeRequest
|
||||
if err = json.Unmarshal(body, &revokeRequest); err != nil {
|
||||
if err := json.Unmarshal(body, &revokeRequest); err != nil {
|
||||
logEvent.AddError(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body)))
|
||||
wfe.sendError(response, logEvent, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Unable to JSON parse revoke request"), err)
|
||||
return
|
||||
}
|
||||
providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to parse revoke certificate DER: %s", err)
|
||||
wfe.sendError(response, logEvent, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Unable to parse certificate DER"), err)
|
||||
return
|
||||
}
|
||||
|
||||
serial := core.SerialToString(providedCert.SerialNumber)
|
||||
logEvent.Extra["ProvidedCertificateSerial"] = serial
|
||||
cert, err := wfe.SA.GetCertificate(serial)
|
||||
// TODO(#991): handle db errors better
|
||||
if err != nil || !bytes.Equal(cert.DER, revokeRequest.CertificateDER) {
|
||||
wfe.sendError(response, logEvent, "No such certificate", err, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("No such certificate"), err)
|
||||
return
|
||||
}
|
||||
parsedCertificate, err := x509.ParseCertificate(cert.DER)
|
||||
if err != nil {
|
||||
// InternalServerError because this is a failure to decode from our DB.
|
||||
wfe.sendError(response, logEvent, "Invalid certificate", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("invalid parse of stored certificate"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["RetrievedCertificateSerial"] = core.SerialToString(parsedCertificate.SerialNumber)
|
||||
|
|
@ -729,14 +655,15 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
certStatus, err := wfe.SA.GetCertificateStatus(serial)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to get certificate status: %s", err)
|
||||
wfe.sendError(response, logEvent, "Certificate status not yet available", err, http.StatusNotFound)
|
||||
// TODO(#991): handle db errors
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate status not yet available"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["CertificateStatus"] = certStatus.Status
|
||||
|
||||
if certStatus.Status == core.OCSPStatusRevoked {
|
||||
logEvent.AddError("Certificate already revoked: %#v", serial)
|
||||
wfe.sendError(response, logEvent, "Certificate already revoked", nil, http.StatusConflict)
|
||||
wfe.sendError(response, logEvent, probs.Conflict("Certificate already revoked"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -744,9 +671,8 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
|
||||
registration.ID == cert.RegistrationID) {
|
||||
wfe.sendError(response, logEvent,
|
||||
"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.",
|
||||
nil,
|
||||
http.StatusForbidden)
|
||||
probs.Unauthorized("Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."),
|
||||
nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -754,7 +680,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
err = wfe.RA.RevokeCertificateWithReg(*parsedCertificate, 0, registration.ID)
|
||||
if err != nil {
|
||||
logEvent.AddError("failed to revoke certificate: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to revoke certificate", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Failed to revoke certificate"), err)
|
||||
} else {
|
||||
wfe.log.Debug(fmt.Sprintf("Revoked %v", serial))
|
||||
response.WriteHeader(http.StatusOK)
|
||||
|
|
@ -777,30 +703,24 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
|
|||
// NewCertificate is used by clients to request the issuance of a cert for an
|
||||
// authorized identifier.
|
||||
func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
body, _, reg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewCert)
|
||||
if err != nil {
|
||||
body, _, reg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceNewCert)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := statusCodeFromError(err)
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if reg.Agreement == "" {
|
||||
wfe.sendError(response, logEvent, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Must agree to subscriber agreement before any further actions"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var certificateRequest core.CertificateRequest
|
||||
if err = json.Unmarshal(body, &certificateRequest); err != nil {
|
||||
if err := json.Unmarshal(body, &certificateRequest); err != nil {
|
||||
logEvent.AddError("unable to JSON unmarshal CertificateRequest: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling certificate request", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling certificate request"), err)
|
||||
return
|
||||
}
|
||||
wfe.logCsr(request, certificateRequest, reg)
|
||||
|
|
@ -810,9 +730,9 @@ func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http
|
|||
// bytes on the wire, and (b) the CA logs all rejections as audit events, but
|
||||
// a bad key from the client is just a malformed request and doesn't need to
|
||||
// be audited.
|
||||
if err = core.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
|
||||
if err := core.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
|
||||
logEvent.AddError("CSR public key failed GoodKey: %s", err)
|
||||
wfe.sendError(response, logEvent, "Invalid key in certificate request", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Invalid key in certificate request :: %s", err), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["CSRDNSNames"] = certificateRequest.CSR.DNSNames
|
||||
|
|
@ -828,7 +748,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http
|
|||
cert, err := wfe.RA.NewCertificate(certificateRequest, reg.ID)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to create new cert: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new cert", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new cert"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -839,7 +759,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http
|
|||
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to parse certificate: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new cert", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Unable to parse certificate"), err)
|
||||
return
|
||||
}
|
||||
serial := parsedCertificate.SerialNumber
|
||||
|
|
@ -864,7 +784,7 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
request *http.Request) {
|
||||
|
||||
notFound := func() {
|
||||
wfe.sendError(response, logEvent, "No such challenge", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil)
|
||||
}
|
||||
|
||||
// Challenge URIs are of the form /acme/challenge/<auth id>/<challenge id>.
|
||||
|
|
@ -886,6 +806,7 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
|
||||
authz, err := wfe.SA.GetAuthorization(authorizationID)
|
||||
if err != nil {
|
||||
// TODO(#1198): handle db errors etc
|
||||
notFound()
|
||||
return
|
||||
}
|
||||
|
|
@ -893,7 +814,7 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
// After expiring, challenges are inaccessible
|
||||
if authz.Expires == nil || authz.Expires.Before(wfe.clk.Now()) {
|
||||
logEvent.AddError("Authorization %v expired in the past (%v)", authz.ID, *authz.Expires)
|
||||
wfe.sendError(response, logEvent, "Expired authorization", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -957,7 +878,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
|
|||
// InternalServerError because this is a failure to decode data passed in
|
||||
// by the caller, which got it from the DB.
|
||||
logEvent.AddError("unable to marshal challenge: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to marshal challenge", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -979,23 +900,17 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
authz core.Authorization,
|
||||
challengeIndex int,
|
||||
logEvent *requestEvent) {
|
||||
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge)
|
||||
if err != nil {
|
||||
body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := http.StatusBadRequest
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if currReg.Agreement == "" {
|
||||
wfe.sendError(response, logEvent, "Registration didn't agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Registration didn't agree to subscriber agreement before any further actions"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1003,16 +918,18 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
// the registration ID on the authz object
|
||||
if currReg.ID != authz.RegistrationID {
|
||||
logEvent.AddError("User registration id: %d != Authorization registration id: %v", currReg.ID, authz.RegistrationID)
|
||||
wfe.sendError(response, logEvent, "User registration ID doesn't match registration ID in authorization",
|
||||
wfe.sendError(response,
|
||||
logEvent,
|
||||
probs.Unauthorized("User registration ID doesn't match registration ID in authorization"),
|
||||
nil,
|
||||
http.StatusForbidden)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var challengeUpdate core.Challenge
|
||||
if err = json.Unmarshal(body, &challengeUpdate); err != nil {
|
||||
if err := json.Unmarshal(body, &challengeUpdate); err != nil {
|
||||
logEvent.AddError("error JSON unmarshalling challenge response: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling challenge response", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling challenge response"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1020,7 +937,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
updatedAuthorization, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeUpdate)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to update challenge: %s", err)
|
||||
wfe.sendError(response, logEvent, "Unable to update challenge", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update challenge"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1029,9 +946,9 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
wfe.prepChallengeForDisplay(authz, &challenge)
|
||||
jsonReply, err := json.Marshal(challenge)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we made the challenges, they should be OK
|
||||
// ServerInternal because we made the challenges, they should be OK
|
||||
logEvent.AddError("failed to marshal challenge: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to marshal challenge", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1050,16 +967,10 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
// Registration is used by a client to submit an update to their registration.
|
||||
func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration)
|
||||
if err != nil {
|
||||
body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := statusCodeFromError(err)
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1069,16 +980,16 @@ func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.R
|
|||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
logEvent.AddError("registration ID must be an integer, was %#v", idStr)
|
||||
wfe.sendError(response, logEvent, "Registration ID must be an integer", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Registration ID must be an integer"), err)
|
||||
return
|
||||
} else if id <= 0 {
|
||||
msg := fmt.Sprintf("Registration ID must be a positive non-zero integer, was %d", id)
|
||||
logEvent.AddError(msg)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed(msg), nil)
|
||||
return
|
||||
} else if id != currReg.ID {
|
||||
logEvent.AddError("Request signing key did not match registration key: %d != %d", id, currReg.ID)
|
||||
wfe.sendError(response, logEvent, "Request signing key did not match registration key", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Request signing key did not match registration key"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1086,14 +997,14 @@ func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.R
|
|||
err = json.Unmarshal(body, &update)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to JSON parse registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling registration", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling registration"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL {
|
||||
msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL)
|
||||
logEvent.AddError(msg)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed(msg), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1107,15 +1018,15 @@ func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.R
|
|||
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to update registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Unable to update registration", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update registration"), err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonReply, err := json.Marshal(updatedReg)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we just generated the reg, it should be OK
|
||||
// ServerInternal because we just generated the reg, it should be OK
|
||||
logEvent.AddError("unable to marshal updated registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to marshal registration", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal registration"), err)
|
||||
return
|
||||
}
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
|
|
@ -1135,7 +1046,8 @@ func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.
|
|||
authz, err := wfe.SA.GetAuthorization(id)
|
||||
if err != nil {
|
||||
logEvent.AddError("No such authorization at id %s", id)
|
||||
wfe.sendError(response, logEvent, "Unable to find authorization", err, http.StatusNotFound)
|
||||
// TODO(#1199): handle db errors
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Unable to find authorization"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["AuthorizationID"] = authz.ID
|
||||
|
|
@ -1148,7 +1060,7 @@ func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.
|
|||
if authz.Expires == nil || authz.Expires.Before(wfe.clk.Now()) {
|
||||
msg := fmt.Sprintf("Authorization %v expired in the past (%v)", authz.ID, *authz.Expires)
|
||||
logEvent.AddError(msg)
|
||||
wfe.sendError(response, logEvent, "Expired authorization", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1158,7 +1070,7 @@ func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.
|
|||
if err != nil {
|
||||
// InternalServerError because this is a failure to decode from our DB.
|
||||
logEvent.AddError("Failed to JSON marshal authz: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to JSON marshal authz", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to JSON marshal authz"), err)
|
||||
return
|
||||
}
|
||||
response.Header().Add("Link", link(wfe.NewCert, "next"))
|
||||
|
|
@ -1181,27 +1093,28 @@ func (wfe *WebFrontEndImpl) Certificate(logEvent *requestEvent, response http.Re
|
|||
// digits.
|
||||
if !strings.HasPrefix(path, CertPath) {
|
||||
logEvent.AddError("this request path should not have gotten to Certificate: %#v is not a prefix of %#v", path, CertPath)
|
||||
wfe.sendError(response, logEvent, "Certificate not found", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
serial := path[len(CertPath):]
|
||||
if !core.ValidSerial(serial) {
|
||||
logEvent.AddError("certificate serial provided was not valid: %s", serial)
|
||||
wfe.sendError(response, logEvent, "Certificate not found", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["RequestedSerial"] = serial
|
||||
|
||||
cert, err := wfe.SA.GetCertificate(serial)
|
||||
// TODO(#991): handle db errors
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to get certificate by serial id %#v: %s", serial, err)
|
||||
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
|
||||
wfe.sendError(response, logEvent, "Multiple certificates with same short serial", err, http.StatusConflict)
|
||||
wfe.sendError(response, logEvent, probs.Conflict("Multiple certificates with same short serial"), err)
|
||||
} else {
|
||||
addNoCacheHeader(response)
|
||||
wfe.sendError(response, logEvent, "Certificate not found", err, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -27,6 +26,7 @@ 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/probs"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
|
@ -571,14 +571,14 @@ func TestIssueCertificate(t *testing.T) {
|
|||
})
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`)
|
||||
|
||||
// POST, but body that isn't valid JWS
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter, makePostRequest("hi"))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`)
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -586,7 +586,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
makePostRequest(signRequest(t, "foo", wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`)
|
||||
|
||||
// Valid, signed JWS body, payload is '{}'
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -595,7 +595,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
signRequest(t, "{}", wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload does not specify a resource","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload does not specify a resource","status":400}`)
|
||||
|
||||
// Valid, signed JWS body, payload is '{"resource":"new-cert"}'
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -755,7 +755,7 @@ func TestBadNonce(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to sign body")
|
||||
wfe.NewRegistration(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:badNonce","detail":"Unable to read/verify body :: JWS has no anti-replay nonce","status":400}`)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:badNonce","detail":"JWS has no anti-replay nonce","status":400}`)
|
||||
}
|
||||
|
||||
func TestNewRegistration(t *testing.T) {
|
||||
|
|
@ -799,19 +799,19 @@ func TestNewRegistration(t *testing.T) {
|
|||
"Content-Length": []string{"0"},
|
||||
},
|
||||
},
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`,
|
||||
},
|
||||
|
||||
// POST, but body that isn't valid JWS
|
||||
{
|
||||
makePostRequestWithPath(NewRegPath, "hi"),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`,
|
||||
},
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
{
|
||||
makePostRequestWithPath(NewRegPath, fooBody.FullSerialize()),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`,
|
||||
},
|
||||
|
||||
// Same signed body, but payload modified by one byte, breaking signature.
|
||||
|
|
@ -831,7 +831,7 @@ func TestNewRegistration(t *testing.T) {
|
|||
"signature": "RjUQ679fxJgeAJlxqgvDP_sfGZnJ-1RgWF2qmcbnBWljs6h1qp63pLnJOl13u81bP_bCSjaWkelGG8Ymx_X-aQ"
|
||||
}
|
||||
`),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}`,
|
||||
},
|
||||
{
|
||||
makePostRequestWithPath(NewRegPath, wrongAgreementBody.FullSerialize()),
|
||||
|
|
@ -1062,12 +1062,12 @@ func TestAuthorization(t *testing.T) {
|
|||
"Content-Length": []string{"0"},
|
||||
},
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`)
|
||||
|
||||
// POST, but body that isn't valid JWS
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest("hi"))
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`)
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -1075,7 +1075,7 @@ func TestAuthorization(t *testing.T) {
|
|||
makePostRequest(signRequest(t, "foo", wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`)
|
||||
|
||||
// Same signed body, but payload modified by one byte, breaking signature.
|
||||
// should fail JWS verification.
|
||||
|
|
@ -1096,7 +1096,7 @@ func TestAuthorization(t *testing.T) {
|
|||
`))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}`)
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter,
|
||||
|
|
@ -1167,7 +1167,7 @@ func TestRegistration(t *testing.T) {
|
|||
wfe.Registration(newRequestEvent(), responseWriter, makePostRequestWithPath("/2", "invalid"))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`)
|
||||
responseWriter.Body.Reset()
|
||||
|
||||
key, err := jose.LoadPrivateKey([]byte(test2KeyPrivatePEM))
|
||||
|
|
@ -1350,13 +1350,13 @@ func TestLogCsrPem(t *testing.T) {
|
|||
|
||||
func TestLengthRequired(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), &http.Request{
|
||||
_, _, _, prob := wfe.verifyPOST(newRequestEvent(), &http.Request{
|
||||
Method: "POST",
|
||||
URL: mustParseURL("/"),
|
||||
}, false, "resource")
|
||||
test.Assert(t, err != nil, "No error returned for request body missing Content-Length.")
|
||||
_, ok := err.(core.LengthRequiredError)
|
||||
test.Assert(t, ok, "Error code for missing content-length wasn't 411.")
|
||||
test.Assert(t, prob != nil, "No error returned for request body missing Content-Length.")
|
||||
test.AssertEquals(t, probs.MalformedProblem, prob.Type)
|
||||
test.AssertEquals(t, http.StatusLengthRequired, prob.HTTPStatus)
|
||||
}
|
||||
|
||||
type mockSADifferentStoredKey struct {
|
||||
|
|
@ -1400,30 +1400,6 @@ func TestBadKeyCSR(t *testing.T) {
|
|||
`{"type":"urn:acme:error:malformed","detail":"Invalid key in certificate request :: Key too small: 512","status":400}`)
|
||||
}
|
||||
|
||||
func TestStatusCodeFromError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
err error
|
||||
statusCode int
|
||||
}{
|
||||
{core.InternalServerError("foo"), 500},
|
||||
{core.NotSupportedError("foo"), 501},
|
||||
{core.MalformedRequestError("foo"), 400},
|
||||
{core.UnauthorizedError("foo"), 403},
|
||||
{core.NotFoundError("foo"), 404},
|
||||
{core.SyntaxError("foo"), 400},
|
||||
{core.SignatureValidationError("foo"), 400},
|
||||
{core.RateLimitedError("foo"), 429},
|
||||
{core.LengthRequiredError("foo"), 411},
|
||||
{core.BadNonceError("foo"), statusBadNonce},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
got := statusCodeFromError(c.err)
|
||||
if got != c.statusCode {
|
||||
t.Errorf("Incorrect status code for %s. Expected %d, got %d", reflect.TypeOf(c.err).Name(), c.statusCode, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRequestEvent() *requestEvent {
|
||||
return &requestEvent{Extra: make(map[string]interface{})}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue