Adds authorizations to orders. (#7)
Refactors the `acme` package to clearly separate out `core` objects that are used by Pebble internally vs those that are read to/from protocol messages. Few misc fixes: * fixed returning the parsedCSR for the order endpoint * fixed the pebble-client shell to not bail on the "meta" directory * updated the in-memory DB's "add" functions to return a count to avoid needing funcs like `countRegistration` Added the authorization types, challenge types, identifier types, and a "pending" status type. New orders now have a pending authorization created for each of the identifiers present in the CSR's SAN fields. Each authorization has a http-01 challenge created for it. Authorizations, orders and challenges are persisted in memory and have GET methods. TODO: Create TLS-SNI-02 and DNS-01 challenges
This commit is contained in:
parent
34529377db
commit
be500adb10
|
|
@ -1,12 +1,6 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v1"
|
"gopkg.in/square/go-jose.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,48 +13,49 @@ const (
|
||||||
ResourceNewOrder = Resource("new-order")
|
ResourceNewOrder = Resource("new-order")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusPending = "pending"
|
||||||
|
|
||||||
|
IdentifierDNS = "dns"
|
||||||
|
|
||||||
|
ChallengeHTTP01 = "http-01"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Identifier struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(@cpu) - Rename Registration to Account, update refs
|
// TODO(@cpu) - Rename Registration to Account, update refs
|
||||||
type Registration struct {
|
type Registration struct {
|
||||||
ID string `json:"id"`
|
Status string `json:"status"`
|
||||||
Key *jose.JsonWebKey `json:"key"`
|
Key *jose.JsonWebKey `json:"key"`
|
||||||
Contact []string `json:"contact"`
|
Contact []string `json:"contact"`
|
||||||
ToSAgreed bool `json:"terms-of-service-agreed"`
|
ToSAgreed bool `json:"terms-of-service-agreed"`
|
||||||
Orders string `json:"orders"`
|
Orders string `json:"orders"`
|
||||||
Status string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderRequest is used for new-order requests
|
// An Order is created to request issuance for a CSR
|
||||||
type OrderRequest struct {
|
type Order struct {
|
||||||
ID string `json:"id"`
|
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Expires string `json:"expires"`
|
Expires string `json:"expires"`
|
||||||
CSR string `json:"csr"`
|
CSR string `json:"csr"`
|
||||||
NotBefore string `json:"notBefore"`
|
NotBefore string `json:"notBefore"`
|
||||||
NotAfter string `json:"notAfter"`
|
NotAfter string `json:"notAfter"`
|
||||||
Authorizations []string `json:"authorizations"`
|
Authorizations []string `json:"authorizations"`
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order is constructed out of an OrderRequest and is an internal type
|
// An Authorization is created for each identifier in an order
|
||||||
type Order struct {
|
type Authorization struct {
|
||||||
OrderRequest
|
Status string `json:"status"`
|
||||||
ParsedCSR *x509.CertificateRequest
|
Identifier Identifier `json:"identifier"`
|
||||||
|
Challenges []string `json:"challenges"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(@cpu): Create an "Authorizations" type
|
// A Challenge is used to validate an Authorization
|
||||||
|
type Challenge struct {
|
||||||
// RandomString and NewToken come from Boulder core/util.go
|
Type string `json:"type"`
|
||||||
// RandomString returns a randomly generated string of the requested length.
|
URL string `json:"url"`
|
||||||
func RandomString(byteLength int) string {
|
Token string `json:"token"`
|
||||||
b := make([]byte, byteLength)
|
|
||||||
_, err := io.ReadFull(rand.Reader, b)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Error reading random bytes: %s", err))
|
|
||||||
}
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewToken produces a random string for Challenges, etc.
|
|
||||||
func NewToken() string {
|
|
||||||
return RandomString(32)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func userAgent() string {
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
server *url.URL
|
server *url.URL
|
||||||
directory map[string]string
|
directory map[string]interface{}
|
||||||
email string
|
email string
|
||||||
acctID string
|
acctID string
|
||||||
http *http.Client
|
http *http.Client
|
||||||
|
|
@ -98,7 +98,7 @@ func (c *client) updateDirectory() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var directory map[string]string
|
var directory map[string]interface{}
|
||||||
err = json.Unmarshal(respBody, &directory)
|
err = json.Unmarshal(respBody, &directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -109,7 +109,7 @@ func (c *client) updateDirectory() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) updateNonce() error {
|
func (c *client) updateNonce() error {
|
||||||
nonceURL := c.directory["new-nonce"]
|
nonceURL := c.directory["new-nonce"].(string)
|
||||||
if nonceURL == "" {
|
if nonceURL == "" {
|
||||||
return fmt.Errorf("Missing \"new-nonce\" entry in server directory")
|
return fmt.Errorf("Missing \"new-nonce\" entry in server directory")
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ func (c *client) updateNonce() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) register() error {
|
func (c *client) register() error {
|
||||||
regURL := c.directory["new-reg"]
|
regURL := c.directory["new-reg"].(string)
|
||||||
if regURL == "" {
|
if regURL == "" {
|
||||||
return fmt.Errorf("Missing \"new-reg\" entry in server directory")
|
return fmt.Errorf("Missing \"new-reg\" entry in server directory")
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +243,7 @@ func (c *client) readEndpoint() (string, error) {
|
||||||
fmt.Printf("$> Enter a directory endpoint to POST: ")
|
fmt.Printf("$> Enter a directory endpoint to POST: ")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
endpoint = c.directory[line]
|
endpoint = c.directory[line].(string)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/pebble/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
acme.Order
|
||||||
|
ID string
|
||||||
|
ParsedCSR *x509.CertificateRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registration struct {
|
||||||
|
acme.Registration
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Authorization struct {
|
||||||
|
acme.Authorization
|
||||||
|
ID string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Challenge struct {
|
||||||
|
acme.Challenge
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/letsencrypt/pebble/acme"
|
"github.com/letsencrypt/pebble/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pebble keeps all of its various objects (registrations, orders, etc)
|
// Pebble keeps all of its various objects (registrations, orders, etc)
|
||||||
|
|
@ -15,19 +15,25 @@ type memoryStore struct {
|
||||||
|
|
||||||
// Each Registration's ID is the hex encoding of a SHA256 sum over its public
|
// Each Registration's ID is the hex encoding of a SHA256 sum over its public
|
||||||
// key bytes.
|
// key bytes.
|
||||||
registrationsByID map[string]*acme.Registration
|
registrationsByID map[string]*core.Registration
|
||||||
|
|
||||||
ordersByID map[string]*acme.Order
|
ordersByID map[string]*core.Order
|
||||||
|
|
||||||
|
authorizationsByID map[string]*core.Authorization
|
||||||
|
|
||||||
|
challengesByID map[string]*core.Challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMemoryStore() *memoryStore {
|
func newMemoryStore() *memoryStore {
|
||||||
return &memoryStore{
|
return &memoryStore{
|
||||||
registrationsByID: make(map[string]*acme.Registration),
|
registrationsByID: make(map[string]*core.Registration),
|
||||||
ordersByID: make(map[string]*acme.Order),
|
ordersByID: make(map[string]*core.Order),
|
||||||
|
authorizationsByID: make(map[string]*core.Authorization),
|
||||||
|
challengesByID: make(map[string]*core.Challenge),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) getRegistrationByID(id string) *acme.Registration {
|
func (m *memoryStore) getRegistrationByID(id string) *core.Registration {
|
||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
if reg, present := m.registrationsByID[id]; present {
|
if reg, present := m.registrationsByID[id]; present {
|
||||||
|
|
@ -36,47 +42,41 @@ func (m *memoryStore) getRegistrationByID(id string) *acme.Registration {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) countRegistrations() int {
|
func (m *memoryStore) addRegistration(reg *core.Registration) (int, error) {
|
||||||
m.RLock()
|
|
||||||
defer m.RUnlock()
|
|
||||||
return len(m.registrationsByID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memoryStore) addRegistration(reg *acme.Registration) (*acme.Registration, error) {
|
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
regID := reg.ID
|
regID := reg.ID
|
||||||
if len(regID) == 0 {
|
if len(regID) == 0 {
|
||||||
return nil, fmt.Errorf("registration must have a non-empty ID to add to memoryStore")
|
return 0, fmt.Errorf("registration must have a non-empty ID to add to memoryStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, present := m.registrationsByID[regID]; present {
|
if _, present := m.registrationsByID[regID]; present {
|
||||||
return nil, fmt.Errorf("registration %q already exists", regID)
|
return 0, fmt.Errorf("registration %q already exists", regID)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.registrationsByID[regID] = reg
|
m.registrationsByID[regID] = reg
|
||||||
return reg, nil
|
return len(m.registrationsByID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) addOrder(order *acme.Order) (*acme.Order, error) {
|
func (m *memoryStore) addOrder(order *core.Order) (int, error) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
orderID := order.ID
|
orderID := order.ID
|
||||||
if len(orderID) == 0 {
|
if len(orderID) == 0 {
|
||||||
return nil, fmt.Errorf("order must have a non-empty ID to add to memoryStore")
|
return 0, fmt.Errorf("order must have a non-empty ID to add to memoryStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, present := m.ordersByID[orderID]; present {
|
if _, present := m.ordersByID[orderID]; present {
|
||||||
return nil, fmt.Errorf("order %q already exists", orderID)
|
return 0, fmt.Errorf("order %q already exists", orderID)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.ordersByID[orderID] = order
|
m.ordersByID[orderID] = order
|
||||||
return order, nil
|
return len(m.ordersByID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) getOrderByID(id string) *acme.Order {
|
func (m *memoryStore) getOrderByID(id string) *core.Order {
|
||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
if order, present := m.ordersByID[id]; present {
|
if order, present := m.ordersByID[id]; present {
|
||||||
|
|
@ -84,3 +84,55 @@ func (m *memoryStore) getOrderByID(id string) *acme.Order {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *memoryStore) addAuthorization(authz *core.Authorization) (int, error) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
authzID := authz.ID
|
||||||
|
if len(authzID) == 0 {
|
||||||
|
return 0, fmt.Errorf("authz must have a non-empty ID to add to memoryStore")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, present := m.authorizationsByID[authzID]; present {
|
||||||
|
return 0, fmt.Errorf("authz %q already exists", authzID)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.authorizationsByID[authzID] = authz
|
||||||
|
return len(m.authorizationsByID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryStore) getAuthorizationByID(id string) *core.Authorization {
|
||||||
|
m.RLock()
|
||||||
|
defer m.RUnlock()
|
||||||
|
if authz, present := m.authorizationsByID[id]; present {
|
||||||
|
return authz
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryStore) addChallenge(chal *core.Challenge) (int, error) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
chalID := chal.ID
|
||||||
|
if len(chalID) == 0 {
|
||||||
|
return 0, fmt.Errorf("challenge must have a non-empty ID to add to memoryStore")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, present := m.challengesByID[chalID]; present {
|
||||||
|
return 0, fmt.Errorf("challenge %q already exists", chalID)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.challengesByID[chalID] = chal
|
||||||
|
return len(m.challengesByID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryStore) getChallengeByID(id string) *core.Challenge {
|
||||||
|
m.RLock()
|
||||||
|
defer m.RUnlock()
|
||||||
|
if chal, present := m.challengesByID[id]; present {
|
||||||
|
return chal
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package wfe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomString and newToken come from Boulder core/util.go
|
||||||
|
// randomString returns a randomly generated string of the requested length.
|
||||||
|
func randomString(byteLength int) string {
|
||||||
|
b := make([]byte, byteLength)
|
||||||
|
_, err := io.ReadFull(rand.Reader, b)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Error reading random bytes: %s", err))
|
||||||
|
}
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newToken produces a random string for Challenges, etc.
|
||||||
|
func newToken() string {
|
||||||
|
return randomString(32)
|
||||||
|
}
|
||||||
224
wfe/wfe.go
224
wfe/wfe.go
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/letsencrypt/pebble/acme"
|
"github.com/letsencrypt/pebble/acme"
|
||||||
|
"github.com/letsencrypt/pebble/core"
|
||||||
"gopkg.in/square/go-jose.v1"
|
"gopkg.in/square/go-jose.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,6 +32,8 @@ const (
|
||||||
regPath = "/my-reg/"
|
regPath = "/my-reg/"
|
||||||
newOrderPath = "/order-plz"
|
newOrderPath = "/order-plz"
|
||||||
orderPath = "/my-order/"
|
orderPath = "/my-order/"
|
||||||
|
authzPath = "/authZ/"
|
||||||
|
challengePath = "/chalZ/"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestEvent struct {
|
type requestEvent struct {
|
||||||
|
|
@ -149,6 +152,8 @@ func (wfe *WebFrontEndImpl) Handler() http.Handler {
|
||||||
wfe.HandleFunc(m, newRegPath, wfe.NewRegistration, "POST")
|
wfe.HandleFunc(m, newRegPath, wfe.NewRegistration, "POST")
|
||||||
wfe.HandleFunc(m, newOrderPath, wfe.NewOrder, "POST")
|
wfe.HandleFunc(m, newOrderPath, wfe.NewOrder, "POST")
|
||||||
wfe.HandleFunc(m, orderPath, wfe.Order, "GET")
|
wfe.HandleFunc(m, orderPath, wfe.Order, "GET")
|
||||||
|
wfe.HandleFunc(m, authzPath, wfe.Authz, "GET")
|
||||||
|
wfe.HandleFunc(m, challengePath, wfe.Challenge, "GET")
|
||||||
|
|
||||||
// TODO(@cpu): Handle regPath for existing reg updates
|
// TODO(@cpu): Handle regPath for existing reg updates
|
||||||
return m
|
return m
|
||||||
|
|
@ -160,7 +165,6 @@ func (wfe *WebFrontEndImpl) Directory(
|
||||||
response http.ResponseWriter,
|
response http.ResponseWriter,
|
||||||
request *http.Request) {
|
request *http.Request) {
|
||||||
|
|
||||||
// TODO(@cpu): Add directory metadata (e.g. TOS url)
|
|
||||||
directoryEndpoints := map[string]string{
|
directoryEndpoints := map[string]string{
|
||||||
"new-nonce": noncePath,
|
"new-nonce": noncePath,
|
||||||
"new-reg": newRegPath,
|
"new-reg": newRegPath,
|
||||||
|
|
@ -358,6 +362,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newReg is the ACME registration information submitted by the client
|
||||||
var newReg acme.Registration
|
var newReg acme.Registration
|
||||||
err := json.Unmarshal(body, &newReg)
|
err := json.Unmarshal(body, &newReg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -366,15 +371,19 @@ func (wfe *WebFrontEndImpl) NewRegistration(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newReg.Key = key
|
// createdReg is the internal Pebble account object
|
||||||
regID, err := keyToID(newReg.Key)
|
createdReg := core.Registration{
|
||||||
|
Registration: newReg,
|
||||||
|
}
|
||||||
|
|
||||||
|
regID, err := keyToID(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(acme.MalformedProblem(err.Error()), response)
|
wfe.sendError(acme.MalformedProblem(err.Error()), response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newReg.ID = regID
|
createdReg.ID = regID
|
||||||
|
|
||||||
if existingReg := wfe.db.getRegistrationByID(newReg.ID); existingReg != nil {
|
if existingReg := wfe.db.getRegistrationByID(regID); existingReg != nil {
|
||||||
regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", regPath, existingReg.ID))
|
regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", regPath, existingReg.ID))
|
||||||
response.Header().Set("Location", regURL)
|
response.Header().Set("Location", regURL)
|
||||||
wfe.sendError(acme.Conflict("Registration key is already in use"), response)
|
wfe.sendError(acme.Conflict("Registration key is already in use"), response)
|
||||||
|
|
@ -390,23 +399,116 @@ func (wfe *WebFrontEndImpl) NewRegistration(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wfe.db.addRegistration(&newReg)
|
count, err := wfe.db.addRegistration(&createdReg)
|
||||||
wfe.log.Printf("There are now %d registrations in memory\n", wfe.db.countRegistrations())
|
if err != nil {
|
||||||
|
wfe.sendError(acme.InternalErrorProblem("Error saving registration"), response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wfe.log.Printf("There are now %d registrations in memory\n", count)
|
||||||
|
|
||||||
regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", regPath, newReg.ID))
|
regURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", regPath, regID))
|
||||||
|
|
||||||
response.Header().Add("Location", regURL)
|
response.Header().Add("Location", regURL)
|
||||||
err = wfe.writeJsonResponse(response, http.StatusCreated, newReg)
|
err = wfe.writeJsonResponse(response, http.StatusCreated, newReg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(acme.InternalErrorProblem("Error marshalling registration"), response)
|
wfe.sendError(acme.InternalErrorProblem("Error marshalling registration"), response)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wfe *WebFrontEndImpl) validateOrder(order *acme.Order) error {
|
func (wfe *WebFrontEndImpl) verifyOrder(order *core.Order, reg *core.Registration) *acme.ProblemDetails {
|
||||||
// TODO(@cpu) - Apply some more advanced policy decisions on the CSR
|
// Shouldn't happen - defensive check
|
||||||
|
if order == nil {
|
||||||
|
return acme.InternalErrorProblem("Order is nil")
|
||||||
|
}
|
||||||
|
if reg == nil {
|
||||||
|
return acme.InternalErrorProblem("Registration is nil")
|
||||||
|
}
|
||||||
|
csr := order.ParsedCSR
|
||||||
|
if csr == nil {
|
||||||
|
return acme.InternalErrorProblem("Parsed CSR is nil")
|
||||||
|
}
|
||||||
|
if len(csr.DNSNames) == 0 {
|
||||||
|
return acme.MalformedProblem("CSR has no names in it")
|
||||||
|
}
|
||||||
|
orderKeyID, err := keyToID(csr.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return acme.MalformedProblem("CSR has an invalid PublicKey")
|
||||||
|
}
|
||||||
|
if orderKeyID == reg.ID {
|
||||||
|
return acme.MalformedProblem("Certificate public key must be different than account key")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeAuthorizations populates an order with new authz's. The request parameter
|
||||||
|
// is required to make the authz URL's absolute based on the request host
|
||||||
|
func (wfe *WebFrontEndImpl) makeAuthorizations(order *core.Order, request *http.Request) error {
|
||||||
|
var auths []string
|
||||||
|
|
||||||
|
names := make([]string, len(order.ParsedCSR.DNSNames))
|
||||||
|
copy(names, order.ParsedCSR.DNSNames)
|
||||||
|
|
||||||
|
// Create one authz for each name in the CSR
|
||||||
|
for _, name := range names {
|
||||||
|
ident := acme.Identifier{
|
||||||
|
Type: acme.IdentifierDNS,
|
||||||
|
Value: name,
|
||||||
|
}
|
||||||
|
authz := &core.Authorization{
|
||||||
|
ID: newToken(),
|
||||||
|
Authorization: acme.Authorization{
|
||||||
|
Status: acme.StatusPending,
|
||||||
|
Identifier: ident,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
authz.URL = wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", authzPath, authz.ID))
|
||||||
|
// Create the challenges for this authz
|
||||||
|
err := wfe.makeChallenges(authz, request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Save the authorization in memory
|
||||||
|
count, err := wfe.db.addAuthorization(authz)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("There are now %d authorizations in the db\n", count)
|
||||||
|
authzURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", authzPath, authz.ID))
|
||||||
|
auths = append(auths, authzURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Authorizations = auths
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeChallenges populates an authz with new challenges. The request parameter
|
||||||
|
// is required to make the challenge URL's absolute based on the request host
|
||||||
|
func (wfe *WebFrontEndImpl) makeChallenges(authz *core.Authorization, request *http.Request) error {
|
||||||
|
var chals []string
|
||||||
|
|
||||||
|
// TODO(@cpu): construct challenges for DNS-01 and TLS-SNI-02
|
||||||
|
chal := &core.Challenge{
|
||||||
|
ID: newToken(),
|
||||||
|
Challenge: acme.Challenge{
|
||||||
|
Type: acme.ChallengeHTTP01,
|
||||||
|
Token: newToken(),
|
||||||
|
URL: authz.URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
count, err := wfe.db.addChallenge(chal)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("There are now %d challenges in the db\n", count)
|
||||||
|
chalURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", challengePath, chal.ID))
|
||||||
|
chals = append(chals, chalURL)
|
||||||
|
|
||||||
|
authz.Challenges = chals
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrder creates a new Order request and populates its authorizations
|
||||||
func (wfe *WebFrontEndImpl) NewOrder(
|
func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logEvent *requestEvent,
|
logEvent *requestEvent,
|
||||||
|
|
@ -419,6 +521,7 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute the registration ID for the signer's key
|
||||||
regID, err := keyToID(key)
|
regID, err := keyToID(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.log.Printf("keyToID err: %s\n", err.Error())
|
wfe.log.Printf("keyToID err: %s\n", err.Error())
|
||||||
|
|
@ -427,16 +530,17 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
}
|
}
|
||||||
wfe.log.Printf("received new-order req from reg ID %s\n", regID)
|
wfe.log.Printf("received new-order req from reg ID %s\n", regID)
|
||||||
|
|
||||||
var existingReg *acme.Registration
|
// Find the existing registration object for that key ID
|
||||||
|
var existingReg *core.Registration
|
||||||
if existingReg = wfe.db.getRegistrationByID(regID); existingReg == nil {
|
if existingReg = wfe.db.getRegistrationByID(regID); existingReg == nil {
|
||||||
wfe.sendError(
|
wfe.sendError(
|
||||||
acme.MalformedProblem(
|
acme.MalformedProblem("No existing registration for signer's public key"),
|
||||||
fmt.Sprintf("No existing registration with ID %q", regID)),
|
|
||||||
response)
|
response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var newOrder acme.OrderRequest
|
// Unpack the order request body
|
||||||
|
var newOrder acme.Order
|
||||||
err = json.Unmarshal(body, &newOrder)
|
err = json.Unmarshal(body, &newOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(
|
wfe.sendError(
|
||||||
|
|
@ -444,13 +548,13 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode and parse the CSR bytes from the order
|
||||||
csrBytes, err := base64.RawURLEncoding.DecodeString(newOrder.CSR)
|
csrBytes, err := base64.RawURLEncoding.DecodeString(newOrder.CSR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(
|
wfe.sendError(
|
||||||
acme.MalformedProblem("Error decoding Base64url-encoded CSR: "+err.Error()), response)
|
acme.MalformedProblem("Error decoding Base64url-encoded CSR: "+err.Error()), response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedCSR, err := x509.ParseCertificateRequest(csrBytes)
|
parsedCSR, err := x509.ParseCertificateRequest(csrBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(
|
wfe.sendError(
|
||||||
|
|
@ -458,30 +562,43 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newOrder.Expires = time.Now().AddDate(0, 0, 1).Format(time.RFC3339)
|
order := &core.Order{
|
||||||
|
ID: newToken(),
|
||||||
order := &acme.Order{
|
Order: acme.Order{
|
||||||
OrderRequest: newOrder,
|
Status: acme.StatusPending,
|
||||||
ParsedCSR: parsedCSR,
|
Expires: time.Now().AddDate(0, 0, 1).Format(time.RFC3339),
|
||||||
|
// Only the CSR, NotBefore and NotAfter fields of the client request are
|
||||||
|
// copied as-is
|
||||||
|
CSR: newOrder.CSR,
|
||||||
|
NotBefore: newOrder.NotBefore,
|
||||||
|
NotAfter: newOrder.NotAfter,
|
||||||
|
},
|
||||||
|
ParsedCSR: parsedCSR,
|
||||||
}
|
}
|
||||||
order.ID = acme.NewToken()
|
|
||||||
fmt.Printf("Order: %#v\n", order)
|
|
||||||
|
|
||||||
if err := wfe.validateOrder(order); err != nil {
|
// Verify the details of the order before creating authorizations
|
||||||
wfe.sendError(
|
if err := wfe.verifyOrder(order, existingReg); err != nil {
|
||||||
// TODO(@cpu) validateOrder should return a problem (e.g.
|
wfe.sendError(err, response)
|
||||||
// rejectedIdentifier, unsupportedIdentifier, etc) as appropriate
|
|
||||||
acme.MalformedProblem(
|
|
||||||
fmt.Sprintf("Error validating order: %s", err.Error())), response)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = wfe.db.addOrder(order)
|
// Create the authorizations for the order
|
||||||
|
err = wfe.makeAuthorizations(order, request)
|
||||||
|
if err != nil {
|
||||||
|
wfe.sendError(
|
||||||
|
acme.InternalErrorProblem("Error creating authorizations for order"), response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the order to the in-memory DB
|
||||||
|
count, err := wfe.db.addOrder(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(
|
wfe.sendError(
|
||||||
acme.InternalErrorProblem("Error saving order"), response)
|
acme.InternalErrorProblem("Error saving order"), response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Added order %q to the db\n", order.ID)
|
||||||
|
fmt.Printf("There are now %d orders in the db\n", count)
|
||||||
|
|
||||||
orderURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", orderPath, order.ID))
|
orderURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", orderPath, order.ID))
|
||||||
response.Header().Add("Location", orderURL)
|
response.Header().Add("Location", orderURL)
|
||||||
|
|
@ -492,6 +609,7 @@ func (wfe *WebFrontEndImpl) NewOrder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order retrieves the details of an existing order
|
||||||
func (wfe *WebFrontEndImpl) Order(
|
func (wfe *WebFrontEndImpl) Order(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logEvent *requestEvent,
|
logEvent *requestEvent,
|
||||||
|
|
@ -499,21 +617,63 @@ func (wfe *WebFrontEndImpl) Order(
|
||||||
request *http.Request) {
|
request *http.Request) {
|
||||||
|
|
||||||
orderID := strings.TrimPrefix(request.URL.Path, orderPath)
|
orderID := strings.TrimPrefix(request.URL.Path, orderPath)
|
||||||
fmt.Printf("Order ID: %#v\n", orderID)
|
|
||||||
|
|
||||||
order := wfe.db.getOrderByID(orderID)
|
order := wfe.db.getOrderByID(orderID)
|
||||||
if order == nil {
|
if order == nil {
|
||||||
response.WriteHeader(http.StatusNotFound)
|
response.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := wfe.writeJsonResponse(response, http.StatusOK, order)
|
// Return only the initial OrderRequest not the internal object with the
|
||||||
|
// parsedCSR
|
||||||
|
orderReq := order.Order
|
||||||
|
|
||||||
|
err := wfe.writeJsonResponse(response, http.StatusOK, orderReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wfe.sendError(acme.InternalErrorProblem("Error marshalling order"), response)
|
wfe.sendError(acme.InternalErrorProblem("Error marshalling order"), response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wfe *WebFrontEndImpl) Authz(
|
||||||
|
ctx context.Context,
|
||||||
|
logEvent *requestEvent,
|
||||||
|
response http.ResponseWriter,
|
||||||
|
request *http.Request) {
|
||||||
|
|
||||||
|
authzID := strings.TrimPrefix(request.URL.Path, authzPath)
|
||||||
|
authz := wfe.db.getAuthorizationByID(authzID)
|
||||||
|
if authz == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := wfe.writeJsonResponse(response, http.StatusOK, authz.Authorization)
|
||||||
|
if err != nil {
|
||||||
|
wfe.sendError(acme.InternalErrorProblem("Error marshalling authz"), response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wfe *WebFrontEndImpl) Challenge(
|
||||||
|
ctx context.Context,
|
||||||
|
logEvent *requestEvent,
|
||||||
|
response http.ResponseWriter,
|
||||||
|
request *http.Request) {
|
||||||
|
|
||||||
|
chalID := strings.TrimPrefix(request.URL.Path, challengePath)
|
||||||
|
chal := wfe.db.getChallengeByID(chalID)
|
||||||
|
if chal == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := wfe.writeJsonResponse(response, http.StatusOK, chal.Challenge)
|
||||||
|
if err != nil {
|
||||||
|
wfe.sendError(acme.InternalErrorProblem("Error marshalling challenge"), response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, status int, v interface{}) error {
|
func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, status int, v interface{}) error {
|
||||||
jsonReply, err := marshalIndent(v)
|
jsonReply, err := marshalIndent(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue