forked from TrueCloudLab/certificates
[acme db interface] wip
This commit is contained in:
parent
461bad3fef
commit
121cc34cca
10 changed files with 190 additions and 151 deletions
|
@ -38,29 +38,5 @@ func (a *Account) GetKey() *jose.JSONWebKey {
|
||||||
|
|
||||||
// IsValid returns true if the Account is valid.
|
// IsValid returns true if the Account is valid.
|
||||||
func (a *Account) IsValid() bool {
|
func (a *Account) IsValid() bool {
|
||||||
return a.Status == StatusValid
|
return Status(a.Status) == StatusValid
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountOptions are the options needed to create a new ACME account.
|
|
||||||
type AccountOptions struct {
|
|
||||||
Key *jose.JSONWebKey
|
|
||||||
Contact []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountUpdateOptions are the options needed to update an existing ACME account.
|
|
||||||
type AccountUpdateOptions struct {
|
|
||||||
Contact []string
|
|
||||||
Status types.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
// toACME converts the internal Account type into the public acmeAccount
|
|
||||||
// type for presentation in the ACME protocol.
|
|
||||||
//func (a *account) toACME(ctx context.Context, db nosql.DB, dir *directory) (*Account, error) {
|
|
||||||
// return &Account{
|
|
||||||
// Status: a.Status,
|
|
||||||
// Contact: a.Contact,
|
|
||||||
// Orders: dir.getLink(ctx, OrdersByAccountLink, true, a.ID),
|
|
||||||
// Key: a.Key,
|
|
||||||
// ID: a.ID,
|
|
||||||
// }, nil
|
|
||||||
//}
|
|
||||||
|
|
|
@ -28,16 +28,16 @@ type Interface interface {
|
||||||
DeactivateAccount(ctx context.Context, accID string) (*Account, error)
|
DeactivateAccount(ctx context.Context, accID string) (*Account, error)
|
||||||
GetAccount(ctx context.Context, accID string) (*Account, error)
|
GetAccount(ctx context.Context, accID string) (*Account, error)
|
||||||
GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error)
|
GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error)
|
||||||
NewAccount(ctx context.Context, ao AccountOptions) (*Account, error)
|
NewAccount(ctx context.Context, acc *Account) (*Account, error)
|
||||||
UpdateAccount(context.Context, string, []string) (*Account, error)
|
UpdateAccount(ctx context.Context, acc *Account) (*Account, error)
|
||||||
|
|
||||||
GetAuthz(ctx context.Context, accID string, authzID string) (*Authz, error)
|
GetAuthz(ctx context.Context, accID string, authzID string) (*Authorization, error)
|
||||||
ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error)
|
ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error)
|
||||||
|
|
||||||
FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error)
|
FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error)
|
||||||
GetOrder(ctx context.Context, accID string, orderID string) (*Order, error)
|
GetOrder(ctx context.Context, accID string, orderID string) (*Order, error)
|
||||||
GetOrdersByAccount(ctx context.Context, accID string) ([]string, error)
|
GetOrdersByAccount(ctx context.Context, accID string) ([]string, error)
|
||||||
NewOrder(ctx context.Context, oo OrderOptions) (*Order, error)
|
NewOrder(ctx context.Context, o *Order) (*Order, error)
|
||||||
|
|
||||||
GetCertificate(string, string) ([]byte, error)
|
GetCertificate(string, string) ([]byte, error)
|
||||||
|
|
||||||
|
@ -140,22 +140,19 @@ func (a *Authority) UseNonce(ctx context.Context, nonce string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount creates, stores, and returns a new ACME account.
|
// NewAccount creates, stores, and returns a new ACME account.
|
||||||
func (a *Authority) NewAccount(ctx context.Context, ao AccountOptions) (*Account, error) {
|
func (a *Authority) NewAccount(ctx context.Context, acc *Account) (*Account, error) {
|
||||||
a := NewAccount(ao)
|
if err := a.db.CreateAccount(ctx, acc); err != nil {
|
||||||
if err := a.db.CreateAccount(ctx, a); err != nil {
|
|
||||||
return ServerInternalErr(err)
|
return ServerInternalErr(err)
|
||||||
}
|
}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAccount updates an ACME account.
|
// UpdateAccount updates an ACME account.
|
||||||
func (a *Authority) UpdateAccount(ctx context.Context, auo AccountUpdateOptions) (*Account, error) {
|
func (a *Authority) UpdateAccount(ctx context.Context, acc *Account) (*Account, error) {
|
||||||
acc, err := a.db.GetAccount(ctx, auo.ID)
|
/*
|
||||||
if err != nil {
|
acc.Contact = auo.Contact
|
||||||
return ServerInternalErr(err)
|
acc.Status = auo.Status
|
||||||
}
|
*/
|
||||||
acc.Contact = auo.Contact
|
|
||||||
acc.Status = auo.Status
|
|
||||||
if err = a.db.UpdateAccount(ctx, acc); err != nil {
|
if err = a.db.UpdateAccount(ctx, acc); err != nil {
|
||||||
return ServerInternalErr(err)
|
return ServerInternalErr(err)
|
||||||
}
|
}
|
||||||
|
@ -228,20 +225,19 @@ func (a *Authority) GetOrdersByAccount(ctx context.Context, id string) ([]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOrder generates, stores, and returns a new ACME order.
|
// NewOrder generates, stores, and returns a new ACME order.
|
||||||
func (a *Authority) NewOrder(ctx context.Context, ops OrderOptions) (*Order, error) {
|
func (a *Authority) NewOrder(ctx context.Context, o *Order) (*Order, error) {
|
||||||
prov, err := ProvisionerFromContext(ctx)
|
prov, err := ProvisionerFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return db.CreateOrder(ctx, &Order{
|
o.DefaultDuration = prov.DefaultTLSCertDuration()
|
||||||
AccountID: ops.AccountID,
|
o.Backdate = a.backdate.Duration
|
||||||
ProvisionerID: prov.GetID(),
|
o.ProvisionerID = prov.GetID()
|
||||||
Backdate: a.backdate.Duration,
|
|
||||||
DefaultDuration: prov.DefaultTLSCertDuration(),
|
if err = db.CreateOrder(ctx, o); err != nil {
|
||||||
Identifiers: ops.Identifiers,
|
return nil, ServerInternalErr(err)
|
||||||
NotBefore: ops.NotBefore,
|
}
|
||||||
NotAfter: ops.NotAfter,
|
return o, nil
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinalizeOrder attempts to finalize an order and generate a new certificate.
|
// FinalizeOrder attempts to finalize an order and generate a new certificate.
|
||||||
|
@ -271,7 +267,7 @@ func (a *Authority) FinalizeOrder(ctx context.Context, accID, orderID string, cs
|
||||||
|
|
||||||
// GetAuthz retrieves and attempts to update the status on an ACME authz
|
// GetAuthz retrieves and attempts to update the status on an ACME authz
|
||||||
// before returning.
|
// before returning.
|
||||||
func (a *Authority) GetAuthz(ctx context.Context, accID, authzID string) (*Authz, error) {
|
func (a *Authority) GetAuthz(ctx context.Context, accID, authzID string) (*Authorization, error) {
|
||||||
az, err := a.db.GetAuthorization(ctx, authzID)
|
az, err := a.db.GetAuthorization(ctx, authzID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -316,13 +312,14 @@ func (a *Authority) ValidateChallenge(ctx context.Context, accID, chID string, j
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificate retrieves the Certificate by ID.
|
// GetCertificate retrieves the Certificate by ID.
|
||||||
func (a *Authority) GetCertificate(accID, certID string) ([]byte, error) {
|
func (a *Authority) GetCertificate(ctx context.Context, accID, certID string) ([]byte, error) {
|
||||||
cert, err := getCert(a.db, certID)
|
cert, err := a.db.GetCertificate(a.db, certID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if accID != cert.AccountID {
|
if cert.AccountID != accID {
|
||||||
return nil, UnauthorizedErr(errors.New("account does not own certificate"))
|
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, cert.AccountID)
|
||||||
|
return nil, UnauthorizedErr(errors.New("account does not own challenge"))
|
||||||
}
|
}
|
||||||
return cert.toACME(a.db, a.dir)
|
return cert.toACME(a.db, a.dir)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package types
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
|
@ -2,88 +2,28 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/smallstep/nosql"
|
"github.com/smallstep/nosql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type certificate struct {
|
// Certificate options with which to create and store a cert object.
|
||||||
ID string `json:"id"`
|
type Certificate struct {
|
||||||
Created time.Time `json:"created"`
|
ID string
|
||||||
AccountID string `json:"accountID"`
|
|
||||||
OrderID string `json:"orderID"`
|
|
||||||
Leaf []byte `json:"leaf"`
|
|
||||||
Intermediates []byte `json:"intermediates"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertOptions options with which to create and store a cert object.
|
|
||||||
type CertOptions struct {
|
|
||||||
AccountID string
|
AccountID string
|
||||||
OrderID string
|
OrderID string
|
||||||
Leaf *x509.Certificate
|
Leaf *x509.Certificate
|
||||||
Intermediates []*x509.Certificate
|
Intermediates []*x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCert(db nosql.DB, ops CertOptions) (*certificate, error) {
|
// ToACME encodes the entire X509 chain into a PEM list.
|
||||||
id, err := randID()
|
func (cert *Certificate) ToACME(db nosql.DB, dir *directory) ([]byte, error) {
|
||||||
if err != nil {
|
var ret []byte
|
||||||
return nil, err
|
for _, c := range append([]*x509.Certificate{cert.Leaf}, cert.Intermediates...) {
|
||||||
}
|
ret = append(ret, pem.EncodeToMemory(&pem.Block{
|
||||||
|
|
||||||
leaf := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: ops.Leaf.Raw,
|
|
||||||
})
|
|
||||||
var intermediates []byte
|
|
||||||
for _, cert := range ops.Intermediates {
|
|
||||||
intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
Type: "CERTIFICATE",
|
||||||
Bytes: cert.Raw,
|
Bytes: c.Raw,
|
||||||
})...)
|
})...)
|
||||||
}
|
}
|
||||||
|
return ret, nil
|
||||||
cert := &certificate{
|
|
||||||
ID: id,
|
|
||||||
AccountID: ops.AccountID,
|
|
||||||
OrderID: ops.OrderID,
|
|
||||||
Leaf: leaf,
|
|
||||||
Intermediates: intermediates,
|
|
||||||
Created: time.Now().UTC(),
|
|
||||||
}
|
|
||||||
certB, err := json.Marshal(cert)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ServerInternalErr(errors.Wrap(err, "error marshaling certificate"))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, swapped, err := db.CmpAndSwap(certTable, []byte(id), nil, certB)
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return nil, ServerInternalErr(errors.Wrap(err, "error storing certificate"))
|
|
||||||
case !swapped:
|
|
||||||
return nil, ServerInternalErr(errors.New("error storing certificate; " +
|
|
||||||
"value has changed since last read"))
|
|
||||||
default:
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *certificate) toACME(db nosql.DB, dir *directory) ([]byte, error) {
|
|
||||||
return append(c.Leaf, c.Intermediates...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCert(db nosql.DB, id string) (*certificate, error) {
|
|
||||||
b, err := db.Get(certTable, []byte(id))
|
|
||||||
if nosql.IsErrNotFound(err) {
|
|
||||||
return nil, MalformedErr(errors.Wrapf(err, "certificate %s not found", id))
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, ServerInternalErr(errors.Wrap(err, "error loading certificate"))
|
|
||||||
}
|
|
||||||
var cert certificate
|
|
||||||
if err := json.Unmarshal(b, &cert); err != nil {
|
|
||||||
return nil, ServerInternalErr(errors.Wrap(err, "error unmarshaling certificate"))
|
|
||||||
}
|
|
||||||
return &cert, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package types
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
34
acme/db.go
34
acme/db.go
|
@ -4,28 +4,28 @@ import "context"
|
||||||
|
|
||||||
// DB is the DB interface expected by the step-ca ACME API.
|
// DB is the DB interface expected by the step-ca ACME API.
|
||||||
type DB interface {
|
type DB interface {
|
||||||
CreateAccount(ctx context.Context, acc *types.Account) (*types.Account, error)
|
CreateAccount(ctx context.Context, acc *Account) (*Account, error)
|
||||||
GetAccount(ctx context.Context, id string) (*types.Account, error)
|
GetAccount(ctx context.Context, id string) (*Account, error)
|
||||||
GetAccountByKeyID(ctx context.Context, kid string) (*types.Account, error)
|
GetAccountByKeyID(ctx context.Context, kid string) (*Account, error)
|
||||||
UpdateAccount(ctx context.Context, acc *types.Account) error
|
UpdateAccount(ctx context.Context, acc *Account) error
|
||||||
|
|
||||||
CreateNonce(ctx context.Context) (types.Nonce, error)
|
CreateNonce(ctx context.Context) (Nonce, error)
|
||||||
DeleteNonce(ctx context.Context, nonce types.Nonce) error
|
DeleteNonce(ctx context.Context, nonce Nonce) error
|
||||||
|
|
||||||
CreateAuthorization(ctx context.Context, authz *types.Authorization) error
|
CreateAuthorization(ctx context.Context, az *Authorization) error
|
||||||
GetAuthorization(ctx context.Context, id string) (*types.Authorization, error)
|
GetAuthorization(ctx context.Context, id string) (*Authorization, error)
|
||||||
UpdateAuthorization(ctx context.Context, authz *types.Authorization) error
|
UpdateAuthorization(ctx context.Context, az *Authorization) error
|
||||||
|
|
||||||
CreateCertificate(ctx context.Context, cert *types.Certificate) error
|
CreateCertificate(ctx context.Context, cert *Certificate) error
|
||||||
GetCertificate(ctx context.Context, id string) (*types.Certificate, error)
|
GetCertificate(ctx context.Context, id string) (*Certificate, error)
|
||||||
|
|
||||||
CreateChallenge(ctx context.Context, ch *types.Challenge) error
|
CreateChallenge(ctx context.Context, ch *Challenge) error
|
||||||
GetChallenge(ctx context.Context, id, authzID string) (*types.Challenge, error)
|
GetChallenge(ctx context.Context, id, authzID string) (*Challenge, error)
|
||||||
UpdateChallenge(ctx context.Context, ch *types.Challenge) error
|
UpdateChallenge(ctx context.Context, ch *Challenge) error
|
||||||
|
|
||||||
CreateOrder(ctx context.Context, o *types.Order) error
|
CreateOrder(ctx context.Context, o *Order) error
|
||||||
DeleteOrder(ctx context.Context, id string) error
|
DeleteOrder(ctx context.Context, id string) error
|
||||||
GetOrder(ctx context.Context, id string) (*types.Order, error)
|
GetOrder(ctx context.Context, id string) (*Order, error)
|
||||||
GetOrdersByAccountID(ctx context.Context, accountID string) error
|
GetOrdersByAccountID(ctx context.Context, accountID string) error
|
||||||
UpdateOrder(ctx context.Context, o *types.Order) error
|
UpdateOrder(ctx context.Context, o *Order) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ func (dba *dbAccount) clone() *dbAccount {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAccount imlements the AcmeDB.CreateAccount interface.
|
// CreateAccount imlements the AcmeDB.CreateAccount interface.
|
||||||
func (db *DB) CreateAccount(ctx context.Context, acc *types.Account) error {
|
func (db *DB) CreateAccount(ctx context.Context, acc *Account) error {
|
||||||
acc.ID, err = randID()
|
acc.ID, err = randID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -63,9 +63,13 @@ func (db *DB) CreateAccount(ctx context.Context, acc *types.Account) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount retrieves an ACME account by ID.
|
// GetAccount retrieves an ACME account by ID.
|
||||||
func (db *DB) GetAccount(ctx context.Context, id string) (*types.Account, error) {
|
func (db *DB) GetAccount(ctx context.Context, id string) (*Account, error) {
|
||||||
|
acc, err := db.getDBAccount(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &types.Account{
|
return &Account{
|
||||||
Status: dbacc.Status,
|
Status: dbacc.Status,
|
||||||
Contact: dbacc.Contact,
|
Contact: dbacc.Contact,
|
||||||
Orders: dir.getLink(ctx, OrdersByAccountLink, true, a.ID),
|
Orders: dir.getLink(ctx, OrdersByAccountLink, true, a.ID),
|
||||||
|
@ -75,7 +79,7 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*types.Account, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountByKeyID retrieves an ACME account by KeyID (thumbprint of the Account Key -- JWK).
|
// GetAccountByKeyID retrieves an ACME account by KeyID (thumbprint of the Account Key -- JWK).
|
||||||
func (db *DB) GetAccountByKeyID(ctx context.Context, kid string) (*types.Account, error) {
|
func (db *DB) GetAccountByKeyID(ctx context.Context, kid string) (*Account, error) {
|
||||||
id, err := db.getAccountIDByKeyID(kid)
|
id, err := db.getAccountIDByKeyID(kid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -84,7 +88,7 @@ func (db *DB) GetAccountByKeyID(ctx context.Context, kid string) (*types.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAccount imlements the AcmeDB.UpdateAccount interface.
|
// UpdateAccount imlements the AcmeDB.UpdateAccount interface.
|
||||||
func (db *DB) UpdateAccount(ctx context.Context, acc *types.Account) error {
|
func (db *DB) UpdateAccount(ctx context.Context, acc *Account) error {
|
||||||
if len(acc.ID) == 0 {
|
if len(acc.ID) == 0 {
|
||||||
return ServerInternalErr(errors.New("id cannot be empty"))
|
return ServerInternalErr(errors.New("id cannot be empty"))
|
||||||
}
|
}
|
||||||
|
@ -99,7 +103,7 @@ func (db *DB) UpdateAccount(ctx context.Context, acc *types.Account) error {
|
||||||
nu.Status = acc.Status
|
nu.Status = acc.Status
|
||||||
|
|
||||||
// If the status has changed to 'deactivated', then set deactivatedAt timestamp.
|
// If the status has changed to 'deactivated', then set deactivatedAt timestamp.
|
||||||
if acc.Status == types.StatusDeactivated && old.Status != types.Status.Deactivated {
|
if acc.Status == StatusDeactivated && old.Status != Status.Deactivated {
|
||||||
nu.Deactivated = clock.Now()
|
nu.Deactivated = clock.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package nosql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/nosql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbCert struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
AccountID string `json:"accountID"`
|
||||||
|
OrderID string `json:"orderID"`
|
||||||
|
Leaf []byte `json:"leaf"`
|
||||||
|
Intermediates []byte `json:"intermediates"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCertificate creates and stores an ACME certificate type.
|
||||||
|
func (db *DB) CreateCertificate(ctx context.Context, cert *Certificate) error {
|
||||||
|
cert.id, err = randID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: ops.Leaf.Raw,
|
||||||
|
})
|
||||||
|
var intermediates []byte
|
||||||
|
for _, cert := range ops.Intermediates {
|
||||||
|
intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: cert.Raw,
|
||||||
|
})...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := &dbCert{
|
||||||
|
ID: cert.ID,
|
||||||
|
AccountID: cert.AccountID,
|
||||||
|
OrderID: cert.OrderID,
|
||||||
|
Leaf: leaf,
|
||||||
|
Intermediates: intermediates,
|
||||||
|
Created: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
return db.save(ctx, cert.ID, cert, nil, "certificate", certTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificate retrieves and unmarshals an ACME certificate type from the
|
||||||
|
// datastore.
|
||||||
|
func (db *DB) GetCertificate(ctx context.Context, id string) (*Certificate, error) {
|
||||||
|
b, err := db.db.Get(certTable, []byte(id))
|
||||||
|
if nosql.IsErrNotFound(err) {
|
||||||
|
return nil, MalformedErr(errors.Wrapf(err, "certificate %s not found", id))
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, ServerInternalErr(errors.Wrap(err, "error loading certificate"))
|
||||||
|
}
|
||||||
|
var dbCert certificate
|
||||||
|
if err := json.Unmarshal(b, &dbCert); err != nil {
|
||||||
|
return nil, ServerInternalErr(errors.Wrap(err, "error unmarshaling certificate"))
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf, err := parseCert(dbCert.Leaf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ServerInternalErr(errors.Wrapf("error parsing leaf of ACME Certificate with ID '%s'", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
intermediates, err := parseBundle(dbCert.Intermediates)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ServerInternalErr(errors.Wrapf("error parsing intermediate bundle of ACME Certificate with ID '%s'", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Certificate{
|
||||||
|
ID: dbCert.ID,
|
||||||
|
AccountID: dbCert.AccountID,
|
||||||
|
OrderID: dbCert.OrderID,
|
||||||
|
Leaf: leaf,
|
||||||
|
Intermediates: intermediate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCert(b []byte) (*x509.Certificate, error) {
|
||||||
|
block, rest := pem.Decode(dbCert.Leaf)
|
||||||
|
if block == nil || len(rest) > 0 {
|
||||||
|
return nil, errors.New("error decoding PEM block: contains unexpected data")
|
||||||
|
}
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, errors.New("error decoding PEM: block is not a certificate bundle")
|
||||||
|
}
|
||||||
|
var crt *x509.Certificate
|
||||||
|
crt, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBundle(b []byte) ([]*x509.Certificate, error) {
|
||||||
|
var block *pem.Block
|
||||||
|
var bundle []*x509.Certificate
|
||||||
|
for len(b) > 0 {
|
||||||
|
block, b = pem.Decode(b)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, errors.Errorf("error decoding PEM: file '%s' is not a certificate bundle", filename)
|
||||||
|
}
|
||||||
|
var crt *x509.Certificate
|
||||||
|
crt, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error parsing %s", filename)
|
||||||
|
}
|
||||||
|
bundle = append(bundle, crt)
|
||||||
|
}
|
||||||
|
if len(b) > 0 {
|
||||||
|
return nil, errors.Errorf("error decoding PEM: file '%s' contains unexpected data", filename)
|
||||||
|
}
|
||||||
|
return bundle, nil
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -188,7 +189,7 @@ func (o *order) Finalize(ctx, db DB, csr *x509.CertificateRequest, auth SignAuth
|
||||||
return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID))
|
return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := newCert(db, CertOptions{
|
cert, err := db.CreateCertificate(ctx, &Certificate{
|
||||||
AccountID: o.AccountID,
|
AccountID: o.AccountID,
|
||||||
OrderID: o.ID,
|
OrderID: o.ID,
|
||||||
Leaf: certChain[0],
|
Leaf: certChain[0],
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package types
|
package acme
|
||||||
|
|
||||||
// Status represents an ACME status.
|
// Status represents an ACME status.
|
||||||
type Status string
|
type Status string
|
||||||
|
|
Loading…
Reference in a new issue