Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-validation & true async issuance #17

Merged
merged 7 commits into from
Apr 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@ randomize keys/certificates used for issuance.

Pebble aims to address the need for ACME clients to have an easier to use,
self-contained version of Boulder to test their clients against while developing
ACME-04 support. Boulder is multi-process, requires heavy dependencies (MariaDB,
ACME-06 support. Boulder is multi-process, requires heavy dependencies (MariaDB,
RabbitMQ, etc), and is operationally complex to integrate with other projects.

Where possible Pebble aims to produce code that can be used to inform the
pending Boulder support for ACME-06, through contribution of code as well as
design lessons learned. Development of Pebble is meant to be rapid, and to
produce a minimum working prototype on a short schedule.

In places where the ACME specification allows customization/CA choice Pebble
aims to make choices different from Boulder. For instance, Pebble changes the
path structures for its resources and directory endpoints to differ from
Boulder. The goal is to emphasize client specification compatibility and to
avoid "over-fitting" on Boulder and the Let's Encrypt production service.

Lastly, Pebble will enforce it's test-only usage by aggressively building in
guardrails that make using it in a production setting impossible or very
inconvenient. Pebble will not support non-volatile storage or persistence
Expand All @@ -48,3 +54,17 @@ clients are not hardcoding URLs.)
## Usage

`pebble -config ./test/config/pebble-config.json`

## Issuance

The easiest way to test issue with Pebble is to use `chisel2` from the
`acme-v2` certbot branch (this is a work in progress).

1. `git clone -b acme-v2 https://github.com/certbot/certbot`
2. `cd certbot`
3. `letsencrypt-auto-source/letsencrypt-auto --os-packages-only`
4. `./tools/venv.sh`
5. `. venev/bin/activate`
6. `python ./certbot/tools/chisel2.py example.com`


19 changes: 7 additions & 12 deletions acme/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,11 @@ type Authorization struct {

// A Challenge is used to validate an Authorization
type Challenge struct {
Type string `json:"type"`
URI string `json:"uri"`
Token string `json:"token"`
Status string `json:"status"`
Validated string `json:"validated,omitempty"`
ValidationRecords []ValidationRecord
KeyAuthorization string `json:"keyAuthorization,omitempty"`
Error *ProblemDetails `json:"error,omitempty"`
}

type ValidationRecord struct {
URL string
Type string `json:"type"`
URI string `json:"uri"`
Token string `json:"token"`
Status string `json:"status"`
Validated string `json:"validated,omitempty"`
KeyAuthorization string `json:"keyAuthorization,omitempty"`
Error *ProblemDetails `json:"error,omitempty"`
}
43 changes: 42 additions & 1 deletion ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"math/big"
"time"

"github.com/letsencrypt/pebble/acme"
"github.com/letsencrypt/pebble/core"
"github.com/letsencrypt/pebble/db"
)
Expand Down Expand Up @@ -152,7 +153,7 @@ func (ca *CAImpl) newIntermediateIssuer() error {
return nil
}

func (ca *CAImpl) NewCertificate(domains []string, key crypto.PublicKey) (*core.Certificate, error) {
func (ca *CAImpl) newCertificate(domains []string, key crypto.PublicKey) (*core.Certificate, error) {
var cn string
if len(domains) > 0 {
cn = domains[0]
Expand Down Expand Up @@ -218,3 +219,43 @@ func New(log *log.Logger, db *db.MemoryStore) *CAImpl {
}
return ca
}

func (ca *CAImpl) CompleteOrder(order *core.Order) {
// Update the order to reflect that we're now processing it
order.Lock()
defer order.Unlock()

// Check the authorizations - this is done by the VA before calling
// CompleteOrder but we do it again for robustness sake.
for _, authz := range order.AuthorizationObjects {
// Lock the authorization for reading
authz.RLock()
if authz.Status != acme.StatusValid {
return
}
authz.RUnlock()
}

if order.Status != acme.StatusPending {
ca.log.Printf("Error: Asked to complete order %s is not status pending, was status %s",
order.ID, order.Status)
return
}

ca.log.Printf("Order %s is fully authorized. Ready to issue", order.ID)
order.Status = acme.StatusProcessing

csr := order.ParsedCSR
// issue a certificate for the csr
cert, err := ca.newCertificate(csr.DNSNames, csr.PublicKey)
if err != nil {
ca.log.Printf("Error: unable to issue order: %s", err.Error())
return
}
ca.log.Printf("Issued certificate serial %s for order %s\n", cert.ID, order.ID)

// Update the order to valid status and store a cert ID for the wfe to use to
// render the certificate URL for the order
order.Status = acme.StatusValid
order.CertificateObject = cert
}
4 changes: 2 additions & 2 deletions cmd/pebble/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ func main() {

clk := clock.Default()
db := db.NewMemoryStore()
va := va.New(logger, clk, c.Pebble.HTTPPort)
ca := ca.New(logger, db)
wfe := wfe.New(logger, clk, db, va, ca)
va := va.New(logger, clk, c.Pebble.HTTPPort, ca)
wfe := wfe.New(logger, clk, db, va)
muxHandler := wfe.Handler()

srv := &http.Server{
Expand Down
11 changes: 11 additions & 0 deletions core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import (
"encoding/base64"
"encoding/pem"
"fmt"
"sync"
"time"

"github.com/letsencrypt/pebble/acme"
"gopkg.in/square/go-jose.v2"
)

type Order struct {
sync.RWMutex
acme.Order
ID string
ParsedCSR *x509.CertificateRequest
ExpiresDate time.Time
AuthorizationObjects []*Authorization
CertificateObject *Certificate
}

type Registration struct {
Expand All @@ -28,6 +31,7 @@ type Registration struct {
}

type Authorization struct {
sync.RWMutex
acme.Authorization
ID string
URL string
Expand All @@ -36,6 +40,7 @@ type Authorization struct {
}

type Challenge struct {
sync.RWMutex
acme.Challenge
ID string
Authz *Authorization
Expand Down Expand Up @@ -98,3 +103,9 @@ func (c Certificate) Chain() []byte {
// Return the chain, leaf cert first
return bytes.Join(chain, nil)
}

type ValidationRecord struct {
URL string
Error *acme.ProblemDetails
ValidatedAt time.Time
}
6 changes: 6 additions & 0 deletions db/memorystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ func (m *MemoryStore) AddOrder(order *core.Order) (int, error) {
m.Lock()
defer m.Unlock()

order.RLock()
orderID := order.ID
if len(orderID) == 0 {
return 0, fmt.Errorf("order must have a non-empty ID to add to MemoryStore")
}
order.RUnlock()

if _, present := m.ordersByID[orderID]; present {
return 0, fmt.Errorf("order %q already exists", orderID)
Expand All @@ -90,10 +92,12 @@ func (m *MemoryStore) AddAuthorization(authz *core.Authorization) (int, error) {
m.Lock()
defer m.Unlock()

authz.RLock()
authzID := authz.ID
if len(authzID) == 0 {
return 0, fmt.Errorf("authz must have a non-empty ID to add to MemoryStore")
}
authz.RUnlock()

if _, present := m.authorizationsByID[authzID]; present {
return 0, fmt.Errorf("authz %q already exists", authzID)
Expand All @@ -113,7 +117,9 @@ func (m *MemoryStore) AddChallenge(chal *core.Challenge) (int, error) {
m.Lock()
defer m.Unlock()

chal.RLock()
chalID := chal.ID
chal.RUnlock()
if len(chalID) == 0 {
return 0, fmt.Errorf("challenge must have a non-empty ID to add to MemoryStore")
}
Expand Down
Loading