diff --git a/ca/ca.go b/ca/ca.go index 3339b6ba..ae7fd4a7 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -30,7 +30,7 @@ const ( type CAImpl struct { log *log.Logger - db *db.MemoryStore + db db.Store ocspResponderURL string chains []*chain @@ -347,7 +347,7 @@ func (ca *CAImpl) newCertificate(domains []string, ips []net.IP, key crypto.Publ return newCert, nil } -func New(log *log.Logger, db *db.MemoryStore, ocspResponderURL string, alternateRoots int, chainLength int, certificateValidityPeriod uint64) *CAImpl { +func New(log *log.Logger, db db.Store, ocspResponderURL string, alternateRoots int, chainLength int, certificateValidityPeriod uint64) *CAImpl { ca := &CAImpl{ log: log, db: db, diff --git a/cmd/pebble/main.go b/cmd/pebble/main.go index 259fda81..959e9e83 100644 --- a/cmd/pebble/main.go +++ b/cmd/pebble/main.go @@ -30,6 +30,7 @@ type config struct { DomainBlocklist []string CertificateValidityPeriod uint64 + RetryAfter struct { Authz int Order int @@ -75,21 +76,22 @@ func main() { chainLength = int(val) } - db := db.NewMemoryStore() - ca := ca.New(logger, db, c.Pebble.OCSPResponderURL, alternateRoots, chainLength, c.Pebble.CertificateValidityPeriod) + var dbStore db.Store + dbStore = db.NewMemoryStore() + ca := ca.New(logger, dbStore, c.Pebble.OCSPResponderURL, alternateRoots, chainLength, c.Pebble.CertificateValidityPeriod) va := va.New(logger, c.Pebble.HTTPPort, c.Pebble.TLSPort, *strictMode, *resolverAddress) for keyID, key := range c.Pebble.ExternalAccountMACKeys { - err := db.AddExternalAccountKeyByID(keyID, key) + err := dbStore.AddExternalAccountKeyByID(keyID, key) cmd.FailOnError(err, "Failed to add key to external account bindings") } for _, domainName := range c.Pebble.DomainBlocklist { - err := db.AddBlockedDomain(domainName) + err := dbStore.AddBlockedDomain(domainName) cmd.FailOnError(err, "Failed to add domain to block list") } - wfeImpl := wfe.New(logger, db, va, ca, *strictMode, c.Pebble.ExternalAccountBindingRequired, c.Pebble.RetryAfter.Authz, c.Pebble.RetryAfter.Order) + wfeImpl := wfe.New(logger, dbStore, va, ca, *strictMode, c.Pebble.ExternalAccountBindingRequired, c.Pebble.RetryAfter.Authz, c.Pebble.RetryAfter.Order) muxHandler := wfeImpl.Handler() if c.Pebble.ManagementListenAddress != "" { diff --git a/db/memorystore.go b/db/memorystore.go index c93c64eb..c5a55767 100644 --- a/db/memorystore.go +++ b/db/memorystore.go @@ -2,10 +2,7 @@ package db import ( "crypto" - "crypto/sha256" - "crypto/x509" "encoding/base64" - "encoding/hex" "errors" "fmt" "math/big" @@ -22,15 +19,7 @@ import ( "github.com/letsencrypt/pebble/v2/core" ) -// ExistingAccountError is an error type indicating when an operation fails -// because the MatchingAccount has a key conflict. -type ExistingAccountError struct { - MatchingAccount *core.Account -} - -func (e ExistingAccountError) Error() string { - return fmt.Sprintf("New public key is already in use by account %s", e.MatchingAccount.ID) -} +var _ Store = (*MemoryStore)(nil) // Pebble keeps all of its various objects (accounts, orders, etc) // in-memory, not persisted anywhere. MemoryStore implements this in-memory @@ -84,7 +73,7 @@ func (m *MemoryStore) GetAccountByID(id string) *core.Account { } func (m *MemoryStore) GetAccountByKey(key crypto.PublicKey) (*core.Account, error) { - keyID, err := keyToID(key) + keyID, err := KeyToID(key) if err != nil { return nil, err } @@ -103,7 +92,7 @@ func (m *MemoryStore) UpdateAccountByID(id string, acct *core.Account) error { if m.accountsByID[id] == nil { return fmt.Errorf("account with ID %q does not exist", id) } - keyID, err := keyToID(acct.Key) + keyID, err := KeyToID(acct.Key) if err != nil { return err } @@ -120,7 +109,7 @@ func (m *MemoryStore) AddAccount(acct *core.Account) (int, error) { return 0, fmt.Errorf("account must not have a nil Key") } - keyID, err := keyToID(acct.Key) + keyID, err := KeyToID(acct.Key) if err != nil { return 0, err } @@ -147,12 +136,12 @@ func (m *MemoryStore) ChangeAccountKey(acct *core.Account, newKey *jose.JSONWebK m.Lock() defer m.Unlock() - oldKeyID, err := keyToID(acct.Key) + oldKeyID, err := KeyToID(acct.Key) if err != nil { return err } - newKeyID, err := keyToID(newKey) + newKeyID, err := KeyToID(newKey) if err != nil { return err } @@ -363,30 +352,6 @@ func (m *MemoryStore) RevokeCertificate(cert *core.RevokedCertificate) { delete(m.certificatesByID, cert.Certificate.ID) } -/* - * keyToID produces a string with the hex representation of the SHA256 digest - * over a provided public key. We use this to associate public keys to - * acme.Account objects, and to ensure every account has a unique public key. - */ -func keyToID(key crypto.PublicKey) (string, error) { - switch t := key.(type) { - case *jose.JSONWebKey: - if t == nil { - return "", fmt.Errorf("Cannot compute ID of nil key") - } - return keyToID(t.Key) - case jose.JSONWebKey: - return keyToID(t.Key) - default: - keyDER, err := x509.MarshalPKIXPublicKey(key) - if err != nil { - return "", err - } - spkiDigest := sha256.Sum256(keyDER) - return hex.EncodeToString(spkiDigest[:]), nil - } -} - // GetCertificateBySerial loops over all certificates to find the one that matches the provided // serial number. This method is linear and it's not optimized to give you a quick response. func (m *MemoryStore) GetCertificateBySerial(serialNumber *big.Int) *core.Certificate { @@ -443,7 +408,7 @@ func (m *MemoryStore) AddExternalAccountKeyByID(keyID, key string) error { // GetExternalAccountKeyByID will return the raw, base64 URL unencoded key // value by its key ID pair. -func (m *MemoryStore) GetExtenalAccountKeyByID(keyID string) ([]byte, bool) { +func (m *MemoryStore) GetExternalAccountKeyByID(keyID string) ([]byte, bool) { m.RLock() defer m.RUnlock() key, ok := m.externalAccountKeysByID[keyID] diff --git a/db/store.go b/db/store.go new file mode 100644 index 00000000..6f1447c7 --- /dev/null +++ b/db/store.go @@ -0,0 +1,77 @@ +package db + +import ( + "crypto" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "fmt" + "github.com/letsencrypt/pebble/v2/acme" + "github.com/letsencrypt/pebble/v2/core" + "gopkg.in/square/go-jose.v2" + "math/big" +) + +// ExistingAccountError is an error type indicating when an operation fails +// because the MatchingAccount has a key conflict. +type ExistingAccountError struct { + MatchingAccount *core.Account +} + +func (e ExistingAccountError) Error() string { + return fmt.Sprintf("New public key is already in use by account %s", e.MatchingAccount.ID) +} + +/* + * KeyToID produces a string with the hex representation of the SHA256 digest + * over a provided public key. We use this to associate public keys to + * acme.Account objects, and to ensure every account has a unique public key. + */ +func KeyToID(key crypto.PublicKey) (string, error) { + switch t := key.(type) { + case *jose.JSONWebKey: + if t == nil { + return "", fmt.Errorf("Cannot compute ID of nil key") + } + return KeyToID(t.Key) + case jose.JSONWebKey: + return KeyToID(t.Key) + default: + keyDER, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return "", err + } + spkiDigest := sha256.Sum256(keyDER) + return hex.EncodeToString(spkiDigest[:]), nil + } +} + +// Pebble keeps all of its various objects (accounts, orders, etc) +// in-memory, not persisted anywhere. MemoryStore implements this in-memory +// "database" +type Store interface { + GetAccountByID(id string) *core.Account + GetAccountByKey(key crypto.PublicKey) (*core.Account, error) + UpdateAccountByID(id string, acct *core.Account) error + AddAccount(acct *core.Account) (int, error) + ChangeAccountKey(acct *core.Account, newKey *jose.JSONWebKey) error + AddOrder(order *core.Order) (int, error) + GetOrderByID(id string) *core.Order + GetOrdersByAccountID(accountID string) []*core.Order + AddAuthorization(authz *core.Authorization) (int, error) + GetAuthorizationByID(id string) *core.Authorization + FindValidAuthorization(accountID string, identifier acme.Identifier) *core.Authorization + AddChallenge(chal *core.Challenge) (int, error) + GetChallengeByID(id string) *core.Challenge + AddCertificate(cert *core.Certificate) (int, error) + GetCertificateByID(id string) *core.Certificate + GetCertificateByDER(der []byte) *core.Certificate + GetRevokedCertificateByDER(der []byte) *core.RevokedCertificate + RevokeCertificate(cert *core.RevokedCertificate) + GetCertificateBySerial(serialNumber *big.Int) *core.Certificate + GetRevokedCertificateBySerial(serialNumber *big.Int) *core.RevokedCertificate + AddExternalAccountKeyByID(keyID, key string) error + GetExternalAccountKeyByID(keyID string) ([]byte, bool) + AddBlockedDomain(name string) error + IsDomainBlocked(name string) bool +} diff --git a/wfe/wfe.go b/wfe/wfe.go index 314f07b2..0dd03ab8 100644 --- a/wfe/wfe.go +++ b/wfe/wfe.go @@ -155,7 +155,7 @@ func (th *topHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type WebFrontEndImpl struct { log *log.Logger - db *db.MemoryStore + db db.Store nonce *nonceMap nonceErrPercent int authzReusePercent int @@ -172,7 +172,7 @@ const ToSURL = "data:text/plain,Do%20what%20thou%20wilt" func New( log *log.Logger, - db *db.MemoryStore, + db db.Store, va *va.VAImpl, ca *ca.CAImpl, strict, requireEAB bool, retryAfterAuthz int, retryAfterOrder int) WebFrontEndImpl { @@ -2787,7 +2787,7 @@ func (wfe *WebFrontEndImpl) verifyEAB( //3. Retrieve the MAC key corresponding to the key identifier in the // "kid" field - key, ok := wfe.db.GetExtenalAccountKeyByID(keyID) + key, ok := wfe.db.GetExternalAccountKeyByID(keyID) if !ok { return nil, acme.UnauthorizedProblem( "the field 'kid' references a key that is not known to the ACME server")