2021-02-28 18:09:06 +00:00
|
|
|
package nosql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/pem"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2021-03-01 06:49:20 +00:00
|
|
|
"github.com/smallstep/certificates/acme"
|
2021-02-28 18:09:06 +00:00
|
|
|
"github.com/smallstep/nosql"
|
|
|
|
)
|
|
|
|
|
|
|
|
type dbCert struct {
|
|
|
|
ID string `json:"id"`
|
2021-03-19 06:08:13 +00:00
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
2021-02-28 18:09:06 +00:00
|
|
|
AccountID string `json:"accountID"`
|
|
|
|
OrderID string `json:"orderID"`
|
|
|
|
Leaf []byte `json:"leaf"`
|
|
|
|
Intermediates []byte `json:"intermediates"`
|
|
|
|
}
|
|
|
|
|
2021-07-09 15:51:31 +00:00
|
|
|
type dbSerial struct {
|
|
|
|
Serial string `json:"serial"`
|
|
|
|
CertificateID string `json:"certificateID"`
|
|
|
|
}
|
|
|
|
|
2021-02-28 18:09:06 +00:00
|
|
|
// CreateCertificate creates and stores an ACME certificate type.
|
2021-03-01 06:49:20 +00:00
|
|
|
func (db *DB) CreateCertificate(ctx context.Context, cert *acme.Certificate) error {
|
|
|
|
var err error
|
|
|
|
cert.ID, err = randID()
|
2021-02-28 18:09:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
leaf := pem.EncodeToMemory(&pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
2021-03-01 06:49:20 +00:00
|
|
|
Bytes: cert.Leaf.Raw,
|
2021-02-28 18:09:06 +00:00
|
|
|
})
|
|
|
|
var intermediates []byte
|
2021-03-01 06:49:20 +00:00
|
|
|
for _, cert := range cert.Intermediates {
|
2021-02-28 18:09:06 +00:00
|
|
|
intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: cert.Raw,
|
|
|
|
})...)
|
|
|
|
}
|
|
|
|
|
2021-03-01 06:49:20 +00:00
|
|
|
dbch := &dbCert{
|
2021-02-28 18:09:06 +00:00
|
|
|
ID: cert.ID,
|
|
|
|
AccountID: cert.AccountID,
|
|
|
|
OrderID: cert.OrderID,
|
|
|
|
Leaf: leaf,
|
|
|
|
Intermediates: intermediates,
|
2021-03-19 06:08:13 +00:00
|
|
|
CreatedAt: time.Now().UTC(),
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
2021-07-09 15:51:31 +00:00
|
|
|
err = db.save(ctx, cert.ID, dbch, nil, "certificate", certTable)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
serial := cert.Leaf.SerialNumber.String()
|
|
|
|
dbSerial := &dbSerial{
|
|
|
|
Serial: serial,
|
|
|
|
CertificateID: cert.ID,
|
|
|
|
}
|
|
|
|
return db.save(ctx, serial, dbSerial, nil, "serial", certBySerialTable)
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetCertificate retrieves and unmarshals an ACME certificate type from the
|
|
|
|
// datastore.
|
2021-03-01 06:49:20 +00:00
|
|
|
func (db *DB) GetCertificate(ctx context.Context, id string) (*acme.Certificate, error) {
|
2021-02-28 18:09:06 +00:00
|
|
|
b, err := db.db.Get(certTable, []byte(id))
|
|
|
|
if nosql.IsErrNotFound(err) {
|
2021-03-19 06:08:13 +00:00
|
|
|
return nil, acme.NewError(acme.ErrorMalformedType, "certificate %s not found", id)
|
2021-02-28 18:09:06 +00:00
|
|
|
} else if err != nil {
|
2021-03-18 21:50:55 +00:00
|
|
|
return nil, errors.Wrapf(err, "error loading certificate %s", id)
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
2021-03-01 06:49:20 +00:00
|
|
|
dbC := new(dbCert)
|
|
|
|
if err := json.Unmarshal(b, dbC); err != nil {
|
2021-03-18 21:50:55 +00:00
|
|
|
return nil, errors.Wrapf(err, "error unmarshaling certificate %s", id)
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 21:50:55 +00:00
|
|
|
certs, err := parseBundle(append(dbC.Leaf, dbC.Intermediates...))
|
2021-02-28 18:09:06 +00:00
|
|
|
if err != nil {
|
2021-03-18 21:50:55 +00:00
|
|
|
return nil, errors.Wrapf(err, "error parsing certificate chain for ACME certificate with ID %s", id)
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
|
2021-03-01 06:49:20 +00:00
|
|
|
return &acme.Certificate{
|
|
|
|
ID: dbC.ID,
|
|
|
|
AccountID: dbC.AccountID,
|
|
|
|
OrderID: dbC.OrderID,
|
2021-03-18 21:50:55 +00:00
|
|
|
Leaf: certs[0],
|
|
|
|
Intermediates: certs[1:],
|
2021-03-01 06:49:20 +00:00
|
|
|
}, nil
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 15:51:31 +00:00
|
|
|
// GetCertificateBySerial retrieves and unmarshals an ACME certificate type from the
|
|
|
|
// datastore based on a certificate serial number.
|
|
|
|
func (db *DB) GetCertificateBySerial(ctx context.Context, serial string) (*acme.Certificate, error) {
|
|
|
|
b, err := db.db.Get(certBySerialTable, []byte(serial))
|
|
|
|
if nosql.IsErrNotFound(err) {
|
|
|
|
return nil, acme.NewError(acme.ErrorMalformedType, "certificate with serial %s not found", serial)
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error loading certificate ID for serial %s", serial)
|
|
|
|
}
|
|
|
|
|
|
|
|
dbSerial := new(dbSerial)
|
|
|
|
if err := json.Unmarshal(b, dbSerial); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error unmarshaling certificate with serial %s", serial)
|
|
|
|
}
|
|
|
|
|
|
|
|
return db.GetCertificate(ctx, dbSerial.CertificateID)
|
|
|
|
}
|
|
|
|
|
2021-02-28 18:09:06 +00:00
|
|
|
func parseBundle(b []byte) ([]*x509.Certificate, error) {
|
2021-03-01 06:49:20 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
block *pem.Block
|
|
|
|
bundle []*x509.Certificate
|
|
|
|
)
|
2021-02-28 18:09:06 +00:00
|
|
|
for len(b) > 0 {
|
|
|
|
block, b = pem.Decode(b)
|
|
|
|
if block == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if block.Type != "CERTIFICATE" {
|
2021-03-01 06:49:20 +00:00
|
|
|
return nil, errors.New("error decoding PEM: data contains block that is not a certificate")
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
var crt *x509.Certificate
|
|
|
|
crt, err = x509.ParseCertificate(block.Bytes)
|
|
|
|
if err != nil {
|
2021-03-01 06:49:20 +00:00
|
|
|
return nil, errors.Wrapf(err, "error parsing x509 certificate")
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
bundle = append(bundle, crt)
|
|
|
|
}
|
|
|
|
if len(b) > 0 {
|
2021-03-01 06:49:20 +00:00
|
|
|
return nil, errors.New("error decoding PEM: unexpected data")
|
2021-02-28 18:09:06 +00:00
|
|
|
}
|
|
|
|
return bundle, nil
|
|
|
|
}
|