1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
package nosql
import (
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/nosql"
)
type dbCert struct {
ID string `json:"id"`
CreatedAt time.Time `json:"createdAt"`
AccountID string `json:"accountID"`
OrderID string `json:"orderID"`
Leaf []byte `json:"leaf"`
Intermediates []byte `json:"intermediates"`
}
type dbSerial struct {
Serial string `json:"serial"`
CertificateID string `json:"certificateID"`
}
// CreateCertificate creates and stores an ACME certificate type.
func (db *DB) CreateCertificate(ctx context.Context, cert *acme.Certificate) error {
var err error
cert.ID, err = randID()
if err != nil {
return err
}
leaf := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Leaf.Raw,
})
var intermediates []byte
for _, cert := range cert.Intermediates {
intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})...)
}
dbch := &dbCert{
ID: cert.ID,
AccountID: cert.AccountID,
OrderID: cert.OrderID,
Leaf: leaf,
Intermediates: intermediates,
CreatedAt: time.Now().UTC(),
}
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)
}
// GetCertificate retrieves and unmarshals an ACME certificate type from the
// datastore.
func (db *DB) GetCertificate(_ context.Context, id string) (*acme.Certificate, error) {
b, err := db.db.Get(certTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "certificate %s not found", id)
} else if err != nil {
return nil, errors.Wrapf(err, "error loading certificate %s", id)
}
dbC := new(dbCert)
if err := json.Unmarshal(b, dbC); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling certificate %s", id)
}
certs, err := parseBundle(append(dbC.Leaf, dbC.Intermediates...))
if err != nil {
return nil, errors.Wrapf(err, "error parsing certificate chain for ACME certificate with ID %s", id)
}
return &acme.Certificate{
ID: dbC.ID,
AccountID: dbC.AccountID,
OrderID: dbC.OrderID,
Leaf: certs[0],
Intermediates: certs[1:],
}, nil
}
// 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)
}
func parseBundle(b []byte) ([]*x509.Certificate, error) {
var (
err error
block *pem.Block
bundle []*x509.Certificate
)
for len(b) > 0 {
block, b = pem.Decode(b)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
return nil, errors.New("error decoding PEM: data contains block that is not a certificate")
}
var crt *x509.Certificate
crt, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errors.Wrapf(err, "error parsing x509 certificate")
}
bundle = append(bundle, crt)
}
if len(b) > 0 {
return nil, errors.New("error decoding PEM: unexpected data")
}
return bundle, nil
}
|