You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/server/root_certificate.go

189 lines
5.9 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package server
import (
"context"
"fmt"
timestamp "github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/server/store"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
// The CertificateAuthority id will always be set to "roots".
// The const CaId contains this value
const CaId = "roots"
// CertificateAuthority is a versioned entity used to lock the database when rotation RootCertificates
type CertificateAuthority struct {
*store.CertificateAuthority
tableName string `gorm:"-"`
}
func newCertificateAuthority() *CertificateAuthority {
ca := &CertificateAuthority{
CertificateAuthority: &store.CertificateAuthority{
PrivateId: CaId,
},
}
return ca
}
// TableName returns the table name.
func (r *CertificateAuthority) TableName() string {
if r.tableName != "" {
return r.tableName
}
return "worker_auth_ca"
}
// SetTableName sets the table name.
func (r *CertificateAuthority) SetTableName(n string) {
r.tableName = n
}
// RootCertificate contains fields related to a RootCertificate resource
// This includes public/ private keys, the PEM encoded certificate, and the certificate validity period
type RootCertificate struct {
*store.RootCertificate
tableName string `gorm:"-"`
}
func (r *RootCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "server.(RootCertificate).encrypt"
if len(r.PrivateKey) == 0 {
return errors.New(ctx, errors.InvalidParameter, op, "no private key provided")
}
if err := structwrapping.WrapStruct(ctx, cipher, r.RootCertificate, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
}
keyId, err := cipher.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("error reading cipher key id"))
}
r.KeyId = keyId
return nil
}
func (r *RootCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "server.(RootCertificate).decrypt"
if err := structwrapping.UnwrapStruct(ctx, cipher, r.RootCertificate, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
// RootCertificateKeys contains the public and private keys for use in constructing a RootCertificate
type RootCertificateKeys struct {
publicKey []byte
privateKey []byte
}
func newRootCertificate(ctx context.Context, serialNumber uint64, certificate []byte, notValidBefore, notValidAfter *timestamp.Timestamp,
rootCertificateKeys RootCertificateKeys, keyId string, state CertificateState,
) (*RootCertificate, error) {
const op = "server.newRootCertificate"
if serialNumber == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no serialNumber")
}
if certificate == nil || len(certificate) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty certificate")
}
if notValidAfter == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no notValidAfter")
}
if notValidBefore == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no notValidBefore")
}
if rootCertificateKeys.publicKey == nil || len(rootCertificateKeys.publicKey) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty publicKey")
}
if rootCertificateKeys.privateKey == nil || len(rootCertificateKeys.privateKey) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty privateKey")
}
if keyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no keyId")
}
if !validState(state) {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is not a valid certificate state", state))
}
l := &RootCertificate{
RootCertificate: &store.RootCertificate{
SerialNumber: serialNumber,
Certificate: certificate,
NotValidAfter: notValidAfter,
NotValidBefore: notValidBefore,
PublicKey: rootCertificateKeys.publicKey,
CtPrivateKey: rootCertificateKeys.privateKey,
KeyId: keyId,
State: string(state),
IssuingCa: CaId,
},
}
return l, nil
}
func allocRootCertificate() *RootCertificate {
return &RootCertificate{
RootCertificate: &store.RootCertificate{},
}
}
func (r *RootCertificate) clone() *RootCertificate {
cp := proto.Clone(r.RootCertificate)
return &RootCertificate{
RootCertificate: cp.(*store.RootCertificate),
}
}
// Validate the RootCertificate. On success, return nil
func (r *RootCertificate) ValidateNewRootCertificate(ctx context.Context) error {
const op = "server.(RootCertificate).ValidateNewRootCertificate"
if r.SerialNumber == 0 {
return errors.New(ctx, errors.InvalidParameter, op, "missing SerialNumber")
}
if r.Certificate == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing Certificate")
}
if r.NotValidBefore.GetTimestamp().AsTime().IsZero() {
return errors.New(ctx, errors.InvalidParameter, op, "missing not valid before timestamp")
}
if r.NotValidAfter.GetTimestamp().AsTime().IsZero() {
return errors.New(ctx, errors.InvalidParameter, op, "missing not valid after timestamp")
}
if r.PublicKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing public key")
}
if r.CtPrivateKey == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing encrypted private key")
}
if r.KeyId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing key id")
}
if r.State == "" {
return errors.New(ctx, errors.InvalidParameter, op, "missing state")
}
return nil
}
// TableName returns the table name.
func (r *RootCertificate) TableName() string {
if r.tableName != "" {
return r.tableName
}
return "worker_auth_ca_certificate"
}
// SetTableName sets the table name.
func (r *RootCertificate) SetTableName(n string) {
r.tableName = n
}