Merge branch 'master' into clean_up_new_reg_test
This commit is contained in:
commit
845e1261a4
|
|
@ -18,7 +18,6 @@ import (
|
||||||
blog "github.com/letsencrypt/boulder/log"
|
blog "github.com/letsencrypt/boulder/log"
|
||||||
"github.com/letsencrypt/boulder/ra"
|
"github.com/letsencrypt/boulder/ra"
|
||||||
"github.com/letsencrypt/boulder/rpc"
|
"github.com/letsencrypt/boulder/rpc"
|
||||||
"github.com/letsencrypt/boulder/wfe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -44,7 +43,6 @@ func main() {
|
||||||
cmd.FailOnError(err, "Couldn't create PA")
|
cmd.FailOnError(err, "Couldn't create PA")
|
||||||
|
|
||||||
rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger)
|
rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger)
|
||||||
rai.AuthzBase = c.Common.BaseURL + wfe.AuthzPath
|
|
||||||
rai.PA = pa
|
rai.PA = pa
|
||||||
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
|
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
|
||||||
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
|
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,8 @@ type ValidationRecord struct {
|
||||||
// challenge, we just throw all the elements into one bucket,
|
// challenge, we just throw all the elements into one bucket,
|
||||||
// together with the common metadata elements.
|
// together with the common metadata elements.
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
|
||||||
// The type of challenge
|
// The type of challenge
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
|
@ -255,7 +257,7 @@ type Challenge struct {
|
||||||
Validated *time.Time `json:"validated,omitempty"`
|
Validated *time.Time `json:"validated,omitempty"`
|
||||||
|
|
||||||
// A URI to which a response can be POSTed
|
// A URI to which a response can be POSTed
|
||||||
URI *AcmeURL `json:"uri"`
|
URI string `json:"uri"`
|
||||||
|
|
||||||
// Used by simpleHttp, dvsni, and dns challenges
|
// Used by simpleHttp, dvsni, and dns challenges
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
|
|
@ -431,6 +433,18 @@ type Authorization struct {
|
||||||
Combinations [][]int `json:"combinations,omitempty" db:"combinations"`
|
Combinations [][]int `json:"combinations,omitempty" db:"combinations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindChallenge will look for the given challenge inside this authorization. If
|
||||||
|
// found, it will return the index of that challenge within the Authorization's
|
||||||
|
// Challenges array. Otherwise it will return -1.
|
||||||
|
func (authz *Authorization) FindChallenge(challengeID int64) int {
|
||||||
|
for i, c := range authz.Challenges {
|
||||||
|
if c.ID == challengeID {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
// JSONBuffer fields get encoded and decoded JOSE-style, in base64url encoding
|
// JSONBuffer fields get encoded and decoded JOSE-style, in base64url encoding
|
||||||
// with stripped padding.
|
// with stripped padding.
|
||||||
type JSONBuffer []byte
|
type JSONBuffer []byte
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -31,8 +30,6 @@ type RegistrationAuthorityImpl struct {
|
||||||
DNSResolver core.DNSResolver
|
DNSResolver core.DNSResolver
|
||||||
clk clock.Clock
|
clk clock.Clock
|
||||||
log *blog.AuditLogger
|
log *blog.AuditLogger
|
||||||
|
|
||||||
AuthzBase string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegistrationAuthorityImpl constructs a new RA object.
|
// NewRegistrationAuthorityImpl constructs a new RA object.
|
||||||
|
|
@ -150,6 +147,11 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
|
||||||
// Create validations, but we have to update them with URIs later
|
// Create validations, but we have to update them with URIs later
|
||||||
challenges, combinations := ra.PA.ChallengesFor(identifier)
|
challenges, combinations := ra.PA.ChallengesFor(identifier)
|
||||||
|
|
||||||
|
for i, _ := range challenges {
|
||||||
|
// Add the account key used to generate the challenge
|
||||||
|
challenges[i].AccountKey = ®.Key
|
||||||
|
}
|
||||||
|
|
||||||
// Partially-filled object
|
// Partially-filled object
|
||||||
authz = core.Authorization{
|
authz = core.Authorization{
|
||||||
Identifier: identifier,
|
Identifier: identifier,
|
||||||
|
|
@ -165,33 +167,19 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization
|
||||||
// InternalServerError since the user-data was validated before being
|
// InternalServerError since the user-data was validated before being
|
||||||
// passed to the SA.
|
// passed to the SA.
|
||||||
err = core.InternalServerError(fmt.Sprintf("Invalid authorization request: %s", err))
|
err = core.InternalServerError(fmt.Sprintf("Invalid authorization request: %s", err))
|
||||||
return authz, err
|
return core.Authorization{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct all the challenge URIs
|
// Check each challenge for sanity.
|
||||||
for i := range authz.Challenges {
|
for _, challenge := range authz.Challenges {
|
||||||
// Ignoring these errors because we construct the URLs to be correct
|
if !challenge.IsSane(false) {
|
||||||
challengeURI, _ := core.ParseAcmeURL(ra.AuthzBase + authz.ID + "?challenge=" + strconv.Itoa(i))
|
|
||||||
authz.Challenges[i].URI = challengeURI
|
|
||||||
|
|
||||||
// Add the account key used to generate the challenge
|
|
||||||
authz.Challenges[i].AccountKey = ®.Key
|
|
||||||
|
|
||||||
if !authz.Challenges[i].IsSane(false) {
|
|
||||||
// InternalServerError because we generated these challenges, they should
|
// InternalServerError because we generated these challenges, they should
|
||||||
// be OK.
|
// be OK.
|
||||||
err = core.InternalServerError(fmt.Sprintf("Challenge didn't pass sanity check: %+v", authz.Challenges[i]))
|
err = core.InternalServerError(fmt.Sprintf("Challenge didn't pass sanity check: %+v", challenge))
|
||||||
return authz, err
|
return core.Authorization{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the authorization object, then return it
|
|
||||||
err = ra.SA.UpdatePendingAuthorization(authz)
|
|
||||||
if err != nil {
|
|
||||||
// InternalServerError because we created the authorization just above,
|
|
||||||
// and adding Sane challenges should not break it.
|
|
||||||
err = core.InternalServerError(err.Error())
|
|
||||||
}
|
|
||||||
return authz, err
|
return authz, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,6 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
||||||
ra.VA = va
|
ra.VA = va
|
||||||
ra.CA = &ca
|
ra.CA = &ca
|
||||||
ra.PA = pa
|
ra.PA = pa
|
||||||
ra.AuthzBase = "http://acme.invalid/authz/"
|
|
||||||
ra.DNSResolver = &mocks.MockDNS{}
|
ra.DNSResolver = &mocks.MockDNS{}
|
||||||
|
|
||||||
AuthzInitial.RegistrationID = Registration.ID
|
AuthzInitial.RegistrationID = Registration.ID
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,7 @@ func (rpc *AmqpRPCServer) processMessage(msg amqp.Delivery) {
|
||||||
CorrelationId: msg.CorrelationId,
|
CorrelationId: msg.CorrelationId,
|
||||||
Type: msg.Type,
|
Type: msg.Type,
|
||||||
Body: jsonResponse, // XXX-JWS: jws.Sign(privKey, body)
|
Body: jsonResponse, // XXX-JWS: jws.Sign(privKey, body)
|
||||||
|
Expiration: "30000",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,7 +487,12 @@ func NewAmqpRPCClient(clientQueuePrefix, serverQueue string, channel *amqp.Chann
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
clientQueue := fmt.Sprintf("%s.%s", clientQueuePrefix, hostname)
|
randID := make([]byte, 3)
|
||||||
|
_, err = rand.Read(randID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientQueue := fmt.Sprintf("%s.%s.%x", clientQueuePrefix, hostname, randID)
|
||||||
|
|
||||||
rpc = &AmqpRPCCLient{
|
rpc = &AmqpRPCCLient{
|
||||||
serverQueue: serverQueue,
|
serverQueue: serverQueue,
|
||||||
|
|
@ -558,6 +564,7 @@ func (rpc *AmqpRPCCLient) Dispatch(method string, body []byte) chan []byte {
|
||||||
ReplyTo: rpc.clientQueue,
|
ReplyTo: rpc.clientQueue,
|
||||||
Type: method,
|
Type: method,
|
||||||
Body: body, // XXX-JWS: jws.Sign(privKey, body)
|
Body: body, // XXX-JWS: jws.Sign(privKey, body)
|
||||||
|
Expiration: "30000",
|
||||||
})
|
})
|
||||||
|
|
||||||
return responseChan
|
return responseChan
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
|
||||||
|
ALTER TABLE `challenges` DROP COLUMN `uri`;
|
||||||
|
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
||||||
|
ALTER TABLE `challenges` ADD COLUMN (
|
||||||
|
`uri` varchar(255)
|
||||||
|
);
|
||||||
16
sa/model.go
16
sa/model.go
|
|
@ -36,7 +36,6 @@ type challModel struct {
|
||||||
Status core.AcmeStatus `db:"status"`
|
Status core.AcmeStatus `db:"status"`
|
||||||
Error []byte `db:"error"`
|
Error []byte `db:"error"`
|
||||||
Validated *time.Time `db:"validated"`
|
Validated *time.Time `db:"validated"`
|
||||||
URI string `db:"uri"`
|
|
||||||
Token string `db:"token"`
|
Token string `db:"token"`
|
||||||
TLS *bool `db:"tls"`
|
TLS *bool `db:"tls"`
|
||||||
Validation []byte `db:"validation"`
|
Validation []byte `db:"validation"`
|
||||||
|
|
@ -85,6 +84,7 @@ func modelToRegistration(rm *regModel) (core.Registration, error) {
|
||||||
|
|
||||||
func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
|
func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
|
||||||
cm := challModel{
|
cm := challModel{
|
||||||
|
ID: c.ID,
|
||||||
AuthorizationID: authID,
|
AuthorizationID: authID,
|
||||||
Type: c.Type,
|
Type: c.Type,
|
||||||
Status: c.Status,
|
Status: c.Status,
|
||||||
|
|
@ -108,12 +108,6 @@ func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
|
||||||
}
|
}
|
||||||
cm.Error = errJSON
|
cm.Error = errJSON
|
||||||
}
|
}
|
||||||
if c.URI != nil {
|
|
||||||
if len(c.URI.String()) > 255 {
|
|
||||||
return nil, fmt.Errorf("URI is too long to store in the database")
|
|
||||||
}
|
|
||||||
cm.URI = c.URI.String()
|
|
||||||
}
|
|
||||||
if len(c.ValidationRecord) > 0 {
|
if len(c.ValidationRecord) > 0 {
|
||||||
vrJSON, err := json.Marshal(c.ValidationRecord)
|
vrJSON, err := json.Marshal(c.ValidationRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -139,19 +133,13 @@ func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
|
||||||
|
|
||||||
func modelToChallenge(cm *challModel) (core.Challenge, error) {
|
func modelToChallenge(cm *challModel) (core.Challenge, error) {
|
||||||
c := core.Challenge{
|
c := core.Challenge{
|
||||||
|
ID: cm.ID,
|
||||||
Type: cm.Type,
|
Type: cm.Type,
|
||||||
Status: cm.Status,
|
Status: cm.Status,
|
||||||
Validated: cm.Validated,
|
Validated: cm.Validated,
|
||||||
Token: cm.Token,
|
Token: cm.Token,
|
||||||
TLS: cm.TLS,
|
TLS: cm.TLS,
|
||||||
}
|
}
|
||||||
if len(cm.URI) > 0 {
|
|
||||||
uri, err := core.ParseAcmeURL(cm.URI)
|
|
||||||
if err != nil {
|
|
||||||
return core.Challenge{}, err
|
|
||||||
}
|
|
||||||
c.URI = uri
|
|
||||||
}
|
|
||||||
if len(cm.Validation) > 0 {
|
if len(cm.Validation) > 0 {
|
||||||
val, err := jose.ParseSigned(string(cm.Validation))
|
val, err := jose.ParseSigned(string(cm.Validation))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -440,17 +440,28 @@ func (ssa *SQLStorageAuthority) NewPendingAuthorization(authz core.Authorization
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range authz.Challenges {
|
for i, c := range authz.Challenges {
|
||||||
chall, err := challengeToModel(&c, pendingAuthz.ID)
|
challModel, err := challengeToModel(&c, pendingAuthz.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return core.Authorization{}, err
|
return core.Authorization{}, err
|
||||||
}
|
}
|
||||||
err = tx.Insert(chall)
|
// Magic happens here: Gorp will modify challModel, setting challModel.ID
|
||||||
|
// to the auto-increment primary key. This is important because we want
|
||||||
|
// the challenge objects inside the Authorization we return to know their
|
||||||
|
// IDs, so they can have proper URLs.
|
||||||
|
// See https://godoc.org/github.com/coopernurse/gorp#DbMap.Insert
|
||||||
|
err = tx.Insert(challModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return core.Authorization{}, err
|
return core.Authorization{}, err
|
||||||
}
|
}
|
||||||
|
challenge, err := modelToChallenge(challModel)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return core.Authorization{}, err
|
||||||
|
}
|
||||||
|
authz.Challenges[i] = challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,7 @@ func CreateDomainAuthWithRegId(t *testing.T, domainName string, sa *SQLStorageAu
|
||||||
test.Assert(t, authz.ID != "", "ID shouldn't be blank")
|
test.Assert(t, authz.ID != "", "ID shouldn't be blank")
|
||||||
|
|
||||||
// prepare challenge for auth
|
// prepare challenge for auth
|
||||||
u, err := core.ParseAcmeURL(domainName)
|
chall := core.Challenge{Type: "simpleHttp", Status: core.StatusValid, URI: domainName, Token: "THISWOULDNTBEAGOODTOKEN"}
|
||||||
test.AssertNotError(t, err, "Couldn't parse domainName "+domainName)
|
|
||||||
chall := core.Challenge{Type: "simpleHttp", Status: core.StatusValid, URI: u, Token: "THISWOULDNTBEAGOODTOKEN"}
|
|
||||||
combos := make([][]int, 1)
|
combos := make([][]int, 1)
|
||||||
combos[0] = []int{0, 1}
|
combos[0] = []int{0, 1}
|
||||||
exp := time.Now().AddDate(0, 0, 1) // expire in 1 day
|
exp := time.Now().AddDate(0, 0, 1) // expire in 1 day
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -32,6 +31,7 @@ const (
|
||||||
RegPath = "/acme/reg/"
|
RegPath = "/acme/reg/"
|
||||||
NewAuthzPath = "/acme/new-authz"
|
NewAuthzPath = "/acme/new-authz"
|
||||||
AuthzPath = "/acme/authz/"
|
AuthzPath = "/acme/authz/"
|
||||||
|
ChallengePath = "/acme/challenge/"
|
||||||
NewCertPath = "/acme/new-cert"
|
NewCertPath = "/acme/new-cert"
|
||||||
CertPath = "/acme/cert/"
|
CertPath = "/acme/cert/"
|
||||||
RevokeCertPath = "/acme/revoke-cert"
|
RevokeCertPath = "/acme/revoke-cert"
|
||||||
|
|
@ -52,6 +52,7 @@ type WebFrontEndImpl struct {
|
||||||
RegBase string
|
RegBase string
|
||||||
NewAuthz string
|
NewAuthz string
|
||||||
AuthzBase string
|
AuthzBase string
|
||||||
|
ChallengeBase string
|
||||||
NewCert string
|
NewCert string
|
||||||
CertBase string
|
CertBase string
|
||||||
|
|
||||||
|
|
@ -195,6 +196,7 @@ func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) {
|
||||||
wfe.RegBase = wfe.BaseURL + RegPath
|
wfe.RegBase = wfe.BaseURL + RegPath
|
||||||
wfe.NewAuthz = wfe.BaseURL + NewAuthzPath
|
wfe.NewAuthz = wfe.BaseURL + NewAuthzPath
|
||||||
wfe.AuthzBase = wfe.BaseURL + AuthzPath
|
wfe.AuthzBase = wfe.BaseURL + AuthzPath
|
||||||
|
wfe.ChallengeBase = wfe.BaseURL + ChallengePath
|
||||||
wfe.NewCert = wfe.BaseURL + NewCertPath
|
wfe.NewCert = wfe.BaseURL + NewCertPath
|
||||||
wfe.CertBase = wfe.BaseURL + CertPath
|
wfe.CertBase = wfe.BaseURL + CertPath
|
||||||
|
|
||||||
|
|
@ -212,18 +214,22 @@ func (wfe *WebFrontEndImpl) Handler() (http.Handler, error) {
|
||||||
wfe.DirectoryJSON = directoryJSON
|
wfe.DirectoryJSON = directoryJSON
|
||||||
|
|
||||||
m := http.NewServeMux()
|
m := http.NewServeMux()
|
||||||
wfe.HandleFunc(m, "/", wfe.Index, "GET")
|
|
||||||
wfe.HandleFunc(m, DirectoryPath, wfe.Directory, "GET")
|
wfe.HandleFunc(m, DirectoryPath, wfe.Directory, "GET")
|
||||||
wfe.HandleFunc(m, NewRegPath, wfe.NewRegistration, "POST")
|
wfe.HandleFunc(m, NewRegPath, wfe.NewRegistration, "POST")
|
||||||
wfe.HandleFunc(m, NewAuthzPath, wfe.NewAuthorization, "POST")
|
wfe.HandleFunc(m, NewAuthzPath, wfe.NewAuthorization, "POST")
|
||||||
wfe.HandleFunc(m, NewCertPath, wfe.NewCertificate, "POST")
|
wfe.HandleFunc(m, NewCertPath, wfe.NewCertificate, "POST")
|
||||||
wfe.HandleFunc(m, RegPath, wfe.Registration, "POST")
|
wfe.HandleFunc(m, RegPath, wfe.Registration, "POST")
|
||||||
wfe.HandleFunc(m, AuthzPath, wfe.Authorization, "GET", "POST")
|
wfe.HandleFunc(m, AuthzPath, wfe.Authorization, "GET")
|
||||||
|
wfe.HandleFunc(m, ChallengePath, wfe.Challenge, "GET", "POST")
|
||||||
wfe.HandleFunc(m, CertPath, wfe.Certificate, "GET")
|
wfe.HandleFunc(m, CertPath, wfe.Certificate, "GET")
|
||||||
wfe.HandleFunc(m, RevokeCertPath, wfe.RevokeCertificate, "POST")
|
wfe.HandleFunc(m, RevokeCertPath, wfe.RevokeCertificate, "POST")
|
||||||
wfe.HandleFunc(m, TermsPath, wfe.Terms, "GET")
|
wfe.HandleFunc(m, TermsPath, wfe.Terms, "GET")
|
||||||
wfe.HandleFunc(m, IssuerPath, wfe.Issuer, "GET")
|
wfe.HandleFunc(m, IssuerPath, wfe.Issuer, "GET")
|
||||||
wfe.HandleFunc(m, BuildIDPath, wfe.BuildID, "GET")
|
wfe.HandleFunc(m, BuildIDPath, wfe.BuildID, "GET")
|
||||||
|
// We don't use our special HandleFunc for "/" because it matches everything,
|
||||||
|
// meaning we can wind up returning 405 when we mean to return 404. See
|
||||||
|
// https://github.com/letsencrypt/boulder/issues/717
|
||||||
|
m.HandleFunc("/", wfe.Index)
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,16 +249,22 @@ func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := template.Must(template.New("body").Parse(`<html>
|
if request.Method != "GET" {
|
||||||
|
logEvent.Error = "Bad method"
|
||||||
|
response.Header().Set("Allow", "GET")
|
||||||
|
response.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Header().Set("Content-Type", "text/html")
|
||||||
|
response.Write([]byte(fmt.Sprintf(`<html>
|
||||||
<body>
|
<body>
|
||||||
This is an <a href="https://github.com/letsencrypt/acme-spec/">ACME</a>
|
This is an <a href="https://github.com/letsencrypt/acme-spec/">ACME</a>
|
||||||
Certificate Authority running <a href="https://github.com/letsencrypt/boulder">Boulder</a>,
|
Certificate Authority running <a href="https://github.com/letsencrypt/boulder">Boulder</a>.
|
||||||
New registration is available at <a href="{{.NewReg}}">{{.NewReg}}</a>.
|
JSON directory is available at <a href="%s">%s</a>.
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`))
|
`, DirectoryPath, DirectoryPath)))
|
||||||
tmpl.Execute(response, wfe)
|
|
||||||
response.Header().Set("Content-Type", "text/html")
|
|
||||||
addCacheHeader(response, wfe.IndexCacheDuration.Seconds())
|
addCacheHeader(response, wfe.IndexCacheDuration.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +277,7 @@ func addCacheHeader(w http.ResponseWriter, age float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) {
|
func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) {
|
||||||
|
response.Header().Set("Content-Type", "application/json")
|
||||||
response.Write(wfe.DirectoryJSON)
|
response.Write(wfe.DirectoryJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -554,8 +567,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
|
||||||
|
|
||||||
// Make a URL for this authz, then blow away the ID and RegID before serializing
|
// Make a URL for this authz, then blow away the ID and RegID before serializing
|
||||||
authzURL := wfe.AuthzBase + string(authz.ID)
|
authzURL := wfe.AuthzBase + string(authz.ID)
|
||||||
authz.ID = ""
|
wfe.prepAuthorizationForDisplay(&authz)
|
||||||
authz.RegistrationID = 0
|
|
||||||
responseBody, err := json.Marshal(authz)
|
responseBody, err := json.Marshal(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logEvent.Error = err.Error()
|
logEvent.Error = err.Error()
|
||||||
|
|
@ -773,46 +785,93 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
|
||||||
wfe.Stats.Inc("Certificates", 1, 1.0)
|
wfe.Stats.Inc("Certificates", 1, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wfe *WebFrontEndImpl) challenge(
|
func (wfe *WebFrontEndImpl) Challenge(
|
||||||
response http.ResponseWriter,
|
response http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request) {
|
||||||
authz core.Authorization,
|
logEvent := wfe.populateRequestEvent(request)
|
||||||
logEvent *requestEvent) {
|
defer wfe.logRequestDetails(&logEvent)
|
||||||
|
|
||||||
// Check that the requested challenge exists within the authorization
|
notFound := func() {
|
||||||
found := false
|
wfe.sendError(response, "No such registration", request.URL.Path, http.StatusNotFound)
|
||||||
var challengeIndex int
|
|
||||||
var challenge core.Challenge
|
|
||||||
for i, challenge := range authz.Challenges {
|
|
||||||
tempURL := challenge.URI
|
|
||||||
if tempURL.Path == request.URL.Path && tempURL.RawQuery == request.URL.RawQuery {
|
|
||||||
found = true
|
|
||||||
challengeIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
// Challenge URIs are of the form /acme/challenge/<auth id>/<challenge id>.
|
||||||
logEvent.Error = "Unable to find challenge"
|
// Here we parse out the id components. TODO: Use a better tool to parse out
|
||||||
wfe.sendError(response, logEvent.Error, request.URL.RawQuery, http.StatusNotFound)
|
// URL structure: https://github.com/letsencrypt/boulder/issues/437
|
||||||
|
slug := strings.Split(request.URL.Path[len(ChallengePath):], "/")
|
||||||
|
if len(slug) != 2 {
|
||||||
|
notFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
authorizationID := slug[0]
|
||||||
|
challengeID, err := strconv.ParseInt(slug[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
notFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logEvent.Extra["AuthorizationID"] = authorizationID
|
||||||
|
logEvent.Extra["ChallengeID"] = challengeID
|
||||||
|
|
||||||
|
authz, err := wfe.SA.GetAuthorization(authorizationID)
|
||||||
|
if err != nil {
|
||||||
|
notFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check that the requested challenge exists within the authorization
|
||||||
|
challengeIndex := authz.FindChallenge(challengeID)
|
||||||
|
if challengeIndex == -1 {
|
||||||
|
notFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
challenge := authz.Challenges[challengeIndex]
|
||||||
|
|
||||||
|
logEvent.Extra["ChallengeType"] = challenge.Type
|
||||||
|
logEvent.Extra["AuthorizationRegistrationID"] = authz.RegistrationID
|
||||||
|
logEvent.Extra["AuthorizationIdentifier"] = authz.Identifier
|
||||||
|
logEvent.Extra["AuthorizationStatus"] = authz.Status
|
||||||
|
logEvent.Extra["AuthorizationExpires"] = authz.Expires
|
||||||
|
|
||||||
switch request.Method {
|
switch request.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
wfe.getChallenge(response, request, authz, challenge, logEvent)
|
wfe.getChallenge(response, request, authz, &challenge, &logEvent)
|
||||||
|
|
||||||
case "POST":
|
case "POST":
|
||||||
wfe.postChallenge(response, request, authz, challengeIndex, logEvent)
|
wfe.postChallenge(response, request, authz, challengeIndex, &logEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepChallengeForDisplay takes a core.Challenge and prepares it for display to
|
||||||
|
// the client by filling in its URI field and clearing its AccountKey and ID
|
||||||
|
// fields.
|
||||||
|
// TODO: Come up with a cleaner way to do this.
|
||||||
|
// https://github.com/letsencrypt/boulder/issues/761
|
||||||
|
func (wfe *WebFrontEndImpl) prepChallengeForDisplay(authz core.Authorization, challenge *core.Challenge) {
|
||||||
|
challenge.URI = fmt.Sprintf("%s%s/%d", wfe.ChallengeBase, authz.ID, challenge.ID)
|
||||||
|
challenge.AccountKey = nil
|
||||||
|
// 0 is considered "empty" for the purpose of the JSON omitempty tag.
|
||||||
|
challenge.ID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepAuthorizationForDisplay takes a core.Authorization and prepares it for
|
||||||
|
// display to the client by clearing its ID and RegistrationID fields, and
|
||||||
|
// preparing all its challenges.
|
||||||
|
func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(authz *core.Authorization) {
|
||||||
|
for i, _ := range authz.Challenges {
|
||||||
|
wfe.prepChallengeForDisplay(*authz, &authz.Challenges[i])
|
||||||
|
}
|
||||||
|
authz.ID = ""
|
||||||
|
authz.RegistrationID = 0
|
||||||
|
}
|
||||||
|
|
||||||
func (wfe *WebFrontEndImpl) getChallenge(
|
func (wfe *WebFrontEndImpl) getChallenge(
|
||||||
response http.ResponseWriter,
|
response http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
authz core.Authorization,
|
authz core.Authorization,
|
||||||
challenge core.Challenge,
|
challenge *core.Challenge,
|
||||||
logEvent *requestEvent) {
|
logEvent *requestEvent) {
|
||||||
|
|
||||||
|
wfe.prepChallengeForDisplay(authz, challenge)
|
||||||
|
|
||||||
jsonReply, err := json.Marshal(challenge)
|
jsonReply, err := json.Marshal(challenge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logEvent.Error = err.Error()
|
logEvent.Error = err.Error()
|
||||||
|
|
@ -823,7 +882,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
|
||||||
}
|
}
|
||||||
|
|
||||||
authzURL := wfe.AuthzBase + string(authz.ID)
|
authzURL := wfe.AuthzBase + string(authz.ID)
|
||||||
response.Header().Add("Location", challenge.URI.String())
|
response.Header().Add("Location", challenge.URI)
|
||||||
response.Header().Set("Content-Type", "application/json")
|
response.Header().Set("Content-Type", "application/json")
|
||||||
response.Header().Add("Link", link(authzURL, "up"))
|
response.Header().Add("Link", link(authzURL, "up"))
|
||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusAccepted)
|
||||||
|
|
@ -890,6 +949,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
||||||
|
|
||||||
// assumption: UpdateAuthorization does not modify order of challenges
|
// assumption: UpdateAuthorization does not modify order of challenges
|
||||||
challenge := updatedAuthorization.Challenges[challengeIndex]
|
challenge := updatedAuthorization.Challenges[challengeIndex]
|
||||||
|
wfe.prepChallengeForDisplay(authz, &challenge)
|
||||||
jsonReply, err := json.Marshal(challenge)
|
jsonReply, err := json.Marshal(challenge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logEvent.Error = err.Error()
|
logEvent.Error = err.Error()
|
||||||
|
|
@ -899,7 +959,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
||||||
}
|
}
|
||||||
|
|
||||||
authzURL := wfe.AuthzBase + string(authz.ID)
|
authzURL := wfe.AuthzBase + string(authz.ID)
|
||||||
response.Header().Add("Location", challenge.URI.String())
|
response.Header().Add("Location", challenge.URI)
|
||||||
response.Header().Set("Content-Type", "application/json")
|
response.Header().Set("Content-Type", "application/json")
|
||||||
response.Header().Add("Link", link(authzURL, "up"))
|
response.Header().Add("Link", link(authzURL, "up"))
|
||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusAccepted)
|
||||||
|
|
@ -1013,31 +1073,8 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
|
||||||
logEvent.Extra["AuthorizationStatus"] = authz.Status
|
logEvent.Extra["AuthorizationStatus"] = authz.Status
|
||||||
logEvent.Extra["AuthorizationExpires"] = authz.Expires
|
logEvent.Extra["AuthorizationExpires"] = authz.Expires
|
||||||
|
|
||||||
// If there is a fragment, then this is actually a request to a challenge URI
|
wfe.prepAuthorizationForDisplay(&authz)
|
||||||
if len(request.URL.RawQuery) != 0 {
|
|
||||||
wfe.challenge(response, request, authz, &logEvent)
|
|
||||||
} else if request.Method == "GET" {
|
|
||||||
wfe.GetAuthorization(response, request, authz, &logEvent)
|
|
||||||
} else {
|
|
||||||
// For challenges, POST and GET are allowed. For authorizations only GET is
|
|
||||||
// allowed.
|
|
||||||
// TODO(jsha): Split challenge updates into a different path so we can use
|
|
||||||
// the HandleFunc functionality for declaring allowed methods.
|
|
||||||
// https://github.com/letsencrypt/boulder/issues/638
|
|
||||||
logEvent.Error = "Method not allowed"
|
|
||||||
response.Header().Set("Allow", "GET")
|
|
||||||
wfe.sendError(response, logEvent.Error, request.Method, http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wfe *WebFrontEndImpl) GetAuthorization(
|
|
||||||
response http.ResponseWriter,
|
|
||||||
request *http.Request,
|
|
||||||
authz core.Authorization,
|
|
||||||
logEvent *requestEvent) {
|
|
||||||
// Blank out ID and regID
|
|
||||||
authz.ID = ""
|
|
||||||
authz.RegistrationID = 0
|
|
||||||
jsonReply, err := json.Marshal(authz)
|
jsonReply, err := json.Marshal(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logEvent.Error = err.Error()
|
logEvent.Error = err.Error()
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,20 @@ func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration,
|
||||||
func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) {
|
func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) {
|
||||||
if id == "valid" {
|
if id == "valid" {
|
||||||
exp := time.Now().AddDate(100, 0, 0)
|
exp := time.Now().AddDate(100, 0, 0)
|
||||||
return core.Authorization{Status: core.StatusValid, RegistrationID: 1, Expires: &exp, Identifier: core.AcmeIdentifier{Type: "dns", Value: "not-an-example.com"}}, nil
|
return core.Authorization{
|
||||||
|
ID: "valid",
|
||||||
|
Status: core.StatusValid,
|
||||||
|
RegistrationID: 1,
|
||||||
|
Expires: &exp,
|
||||||
|
Identifier: core.AcmeIdentifier{Type: "dns", Value: "not-an-example.com"},
|
||||||
|
Challenges: []core.Challenge{
|
||||||
|
core.Challenge{
|
||||||
|
ID: 23,
|
||||||
|
Type: "dns",
|
||||||
|
URI: "http://localhost:4300/acme/challenge/valid/23",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
return core.Authorization{}, nil
|
return core.Authorization{}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -343,6 +356,7 @@ func setupWFE(t *testing.T) WebFrontEndImpl {
|
||||||
wfe.RegBase = wfe.BaseURL + RegPath
|
wfe.RegBase = wfe.BaseURL + RegPath
|
||||||
wfe.NewAuthz = wfe.BaseURL + NewAuthzPath
|
wfe.NewAuthz = wfe.BaseURL + NewAuthzPath
|
||||||
wfe.AuthzBase = wfe.BaseURL + AuthzPath
|
wfe.AuthzBase = wfe.BaseURL + AuthzPath
|
||||||
|
wfe.ChallengeBase = wfe.BaseURL + ChallengePath
|
||||||
wfe.NewCert = wfe.BaseURL + NewCertPath
|
wfe.NewCert = wfe.BaseURL + NewCertPath
|
||||||
wfe.CertBase = wfe.BaseURL + CertPath
|
wfe.CertBase = wfe.BaseURL + CertPath
|
||||||
wfe.SubscriberAgreementURL = agreementURL
|
wfe.SubscriberAgreementURL = agreementURL
|
||||||
|
|
@ -458,11 +472,11 @@ func TestStandardHeaders(t *testing.T) {
|
||||||
path string
|
path string
|
||||||
allowed []string
|
allowed []string
|
||||||
}{
|
}{
|
||||||
{"/", []string{"GET"}},
|
|
||||||
{wfe.NewReg, []string{"POST"}},
|
{wfe.NewReg, []string{"POST"}},
|
||||||
{wfe.RegBase, []string{"POST"}},
|
{wfe.RegBase, []string{"POST"}},
|
||||||
{wfe.NewAuthz, []string{"POST"}},
|
{wfe.NewAuthz, []string{"POST"}},
|
||||||
{wfe.AuthzBase, []string{"GET", "POST"}},
|
{wfe.AuthzBase, []string{"GET"}},
|
||||||
|
{wfe.ChallengeBase, []string{"GET", "POST"}},
|
||||||
{wfe.NewCert, []string{"POST"}},
|
{wfe.NewCert, []string{"POST"}},
|
||||||
{wfe.CertBase, []string{"GET"}},
|
{wfe.CertBase, []string{"GET"}},
|
||||||
{wfe.SubscriberAgreementURL, []string{"GET"}},
|
{wfe.SubscriberAgreementURL, []string{"GET"}},
|
||||||
|
|
@ -484,6 +498,28 @@ func TestStandardHeaders(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndexPOST(t *testing.T) {
|
||||||
|
wfe := setupWFE(t)
|
||||||
|
responseWriter := httptest.NewRecorder()
|
||||||
|
url, _ := url.Parse("/")
|
||||||
|
wfe.Index(responseWriter, &http.Request{
|
||||||
|
Method: "POST",
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
|
test.AssertEquals(t, responseWriter.Code, http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPOST404(t *testing.T) {
|
||||||
|
wfe := setupWFE(t)
|
||||||
|
responseWriter := httptest.NewRecorder()
|
||||||
|
url, _ := url.Parse("/foobar")
|
||||||
|
wfe.Index(responseWriter, &http.Request{
|
||||||
|
Method: "POST",
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
|
test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIndex(t *testing.T) {
|
func TestIndex(t *testing.T) {
|
||||||
wfe := setupWFE(t)
|
wfe := setupWFE(t)
|
||||||
wfe.IndexCacheDuration = time.Second * 10
|
wfe.IndexCacheDuration = time.Second * 10
|
||||||
|
|
@ -497,8 +533,8 @@ func TestIndex(t *testing.T) {
|
||||||
})
|
})
|
||||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
||||||
test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n")
|
test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n")
|
||||||
test.Assert(t, strings.Contains(responseWriter.Body.String(), wfe.NewReg),
|
test.Assert(t, strings.Contains(responseWriter.Body.String(), DirectoryPath),
|
||||||
"new-reg not found")
|
"directory path not found")
|
||||||
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10")
|
||||||
|
|
||||||
responseWriter.Body.Reset()
|
responseWriter.Body.Reset()
|
||||||
|
|
@ -525,6 +561,7 @@ func TestDirectory(t *testing.T) {
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
URL: url,
|
URL: url,
|
||||||
})
|
})
|
||||||
|
test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
|
||||||
test.AssertEquals(t, responseWriter.Code, http.StatusOK)
|
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"}`)
|
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"}`)
|
||||||
}
|
}
|
||||||
|
|
@ -675,37 +712,21 @@ func TestChallenge(t *testing.T) {
|
||||||
`), &key)
|
`), &key)
|
||||||
test.AssertNotError(t, err, "Could not unmarshal testing key")
|
test.AssertNotError(t, err, "Could not unmarshal testing key")
|
||||||
|
|
||||||
challengeURL := "/acme/authz/asdf?challenge=foo"
|
challengeURL := "/acme/challenge/valid/23"
|
||||||
challengeAcme := (*core.AcmeURL)(mustParseURL(challengeURL))
|
wfe.Challenge(responseWriter,
|
||||||
authz := core.Authorization{
|
|
||||||
ID: "asdf",
|
|
||||||
Identifier: core.AcmeIdentifier{
|
|
||||||
Type: "dns",
|
|
||||||
Value: "letsencrypt.org",
|
|
||||||
},
|
|
||||||
Challenges: []core.Challenge{
|
|
||||||
core.Challenge{
|
|
||||||
Type: "dns",
|
|
||||||
URI: challengeAcme,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RegistrationID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
wfe.challenge(responseWriter,
|
|
||||||
makePostRequestWithPath(challengeURL,
|
makePostRequestWithPath(challengeURL,
|
||||||
signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService)),
|
signRequest(t, `{"resource":"challenge"}`, &wfe.nonceService)))
|
||||||
authz, &requestEvent{})
|
|
||||||
|
|
||||||
|
test.AssertEquals(t, responseWriter.Code, 202)
|
||||||
test.AssertEquals(
|
test.AssertEquals(
|
||||||
t, responseWriter.Header().Get("Location"),
|
t, responseWriter.Header().Get("Location"),
|
||||||
"/acme/authz/asdf?challenge=foo")
|
challengeURL)
|
||||||
test.AssertEquals(
|
test.AssertEquals(
|
||||||
t, responseWriter.Header().Get("Link"),
|
t, responseWriter.Header().Get("Link"),
|
||||||
`</acme/authz/asdf>;rel="up"`)
|
`</acme/authz/valid>;rel="up"`)
|
||||||
test.AssertEquals(
|
test.AssertEquals(
|
||||||
t, responseWriter.Body.String(),
|
t, responseWriter.Body.String(),
|
||||||
`{"type":"dns","uri":"/acme/authz/asdf?challenge=foo"}`)
|
`{"type":"dns","uri":"/acme/challenge/valid/23"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRegistration(t *testing.T) {
|
func TestNewRegistration(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue