forked from TrueCloudLab/certificates
c242602231
* Only shutdown the database once. * Be careful when reloading the CA. Depending on whether the DB has already been shutdown, and error may be unrecoverable.
138 lines
3.5 KiB
Go
138 lines
3.5 KiB
Go
package db
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/nosql"
|
|
)
|
|
|
|
var (
|
|
revokedCertsTable = []byte("revoked_x509_certs")
|
|
certsTable = []byte("x509_certs")
|
|
)
|
|
|
|
// ErrAlreadyExists can be returned if the DB attempts to set a key that has
|
|
// been previously set.
|
|
var ErrAlreadyExists = errors.New("already exists")
|
|
|
|
// Config represents the JSON attributes used for configuring a step-ca DB.
|
|
type Config struct {
|
|
Type string `json:"type"`
|
|
DataSource string `json:"dataSource"`
|
|
ValueDir string `json:"valueDir"`
|
|
Database string `json:"database"`
|
|
}
|
|
|
|
// AuthDB is an interface over an Authority DB client that implements a nosql.DB interface.
|
|
type AuthDB interface {
|
|
IsRevoked(sn string) (bool, error)
|
|
Revoke(rci *RevokedCertificateInfo) error
|
|
StoreCertificate(crt *x509.Certificate) error
|
|
Shutdown() error
|
|
}
|
|
|
|
// DB is a wrapper over the nosql.DB interface.
|
|
type DB struct {
|
|
nosql.DB
|
|
isUp bool
|
|
}
|
|
|
|
// New returns a new database client that implements the AuthDB interface.
|
|
func New(c *Config) (AuthDB, error) {
|
|
if c == nil {
|
|
return new(NoopDB), nil
|
|
}
|
|
|
|
db, err := nosql.New(c.Type, c.DataSource, nosql.WithDatabase(c.Database),
|
|
nosql.WithValueDir(c.ValueDir))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Error opening database of Type %s with source %s", c.Type, c.DataSource)
|
|
}
|
|
|
|
tables := [][]byte{revokedCertsTable, certsTable}
|
|
for _, b := range tables {
|
|
if err := db.CreateTable(b); err != nil {
|
|
return nil, errors.Wrapf(err, "error creating table %s",
|
|
string(b))
|
|
}
|
|
}
|
|
|
|
return &DB{db, true}, nil
|
|
}
|
|
|
|
// RevokedCertificateInfo contains information regarding the certificate
|
|
// revocation action.
|
|
type RevokedCertificateInfo struct {
|
|
Serial string
|
|
ProvisionerID string
|
|
ReasonCode int
|
|
Reason string
|
|
RevokedAt time.Time
|
|
TokenID string
|
|
MTLS bool
|
|
}
|
|
|
|
// IsRevoked returns whether or not a certificate with the given identifier
|
|
// has been revoked.
|
|
// In the case of an X509 Certificate the `id` should be the Serial Number of
|
|
// the Certificate.
|
|
func (db *DB) IsRevoked(sn string) (bool, error) {
|
|
// If the DB is nil then act as pass through.
|
|
if db == nil {
|
|
return false, nil
|
|
}
|
|
|
|
// If the error is `Not Found` then the certificate has not been revoked.
|
|
// Any other error should be propagated to the caller.
|
|
if _, err := db.Get(revokedCertsTable, []byte(sn)); err != nil {
|
|
if nosql.IsErrNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "error checking revocation bucket")
|
|
}
|
|
|
|
// This certificate has been revoked.
|
|
return true, nil
|
|
}
|
|
|
|
// Revoke adds a certificate to the revocation table.
|
|
func (db *DB) Revoke(rci *RevokedCertificateInfo) error {
|
|
isRvkd, err := db.IsRevoked(rci.Serial)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isRvkd {
|
|
return ErrAlreadyExists
|
|
}
|
|
rcib, err := json.Marshal(rci)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error marshaling revoked certificate info")
|
|
}
|
|
|
|
if err = db.Set(revokedCertsTable, []byte(rci.Serial), rcib); err != nil {
|
|
return errors.Wrap(err, "database Set error")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StoreCertificate stores a certificate PEM.
|
|
func (db *DB) StoreCertificate(crt *x509.Certificate) error {
|
|
if err := db.Set(certsTable, []byte(crt.SerialNumber.String()), crt.Raw); err != nil {
|
|
return errors.Wrap(err, "database Set error")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Shutdown sends a shutdown message to the database.
|
|
func (db *DB) Shutdown() error {
|
|
if db.isUp {
|
|
if err := db.Close(); err != nil {
|
|
return errors.Wrap(err, "database shutdown error")
|
|
}
|
|
db.isUp = false
|
|
}
|
|
return nil
|
|
}
|