2022-05-10 10:03:08 -04:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2022-05-27 13:17:29 -04:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-02-02 17:15:17 -08:00
|
|
|
|
2022-06-20 18:20:17 -07:00
|
|
|
package server
|
2022-05-10 10:03:08 -04:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
timestamp "github.com/hashicorp/boundary/internal/db/timestamp"
|
2022-11-16 17:17:28 -06:00
|
|
|
"github.com/hashicorp/boundary/internal/errors"
|
2022-06-20 18:20:17 -07:00
|
|
|
"github.com/hashicorp/boundary/internal/server/store"
|
2022-11-16 17:17:28 -06:00
|
|
|
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
|
|
|
|
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
|
2022-05-10 10:03:08 -04:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
)
|
|
|
|
|
|
2022-05-27 13:17:29 -04:00
|
|
|
// The CertificateAuthority id will always be set to "roots".
|
2022-09-08 13:19:21 -04:00
|
|
|
// The const CaId contains this value
|
|
|
|
|
const CaId = "roots"
|
2022-05-27 13:17:29 -04:00
|
|
|
|
|
|
|
|
// 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{
|
2022-09-08 13:19:21 -04:00
|
|
|
PrivateId: CaId,
|
2022-05-27 13:17:29 -04:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-10 10:03:08 -04:00
|
|
|
// 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:"-"`
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 17:17:28 -06:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-27 13:17:29 -04:00
|
|
|
// RootCertificateKeys contains the public and private keys for use in constructing a RootCertificate
|
2022-05-10 10:03:08 -04:00
|
|
|
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) {
|
2022-06-20 18:20:17 -07:00
|
|
|
const op = "server.newRootCertificate"
|
2022-05-10 10:03:08 -04:00
|
|
|
|
|
|
|
|
if &serialNumber == nil {
|
|
|
|
|
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,
|
2022-11-16 17:17:28 -06:00
|
|
|
CtPrivateKey: rootCertificateKeys.privateKey,
|
2022-05-10 10:03:08 -04:00
|
|
|
KeyId: keyId,
|
|
|
|
|
State: string(state),
|
2022-09-08 13:19:21 -04:00
|
|
|
IssuingCa: CaId,
|
2022-05-10 10:03:08 -04:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return l, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-27 13:17:29 -04:00
|
|
|
func allocRootCertificate() *RootCertificate {
|
2022-05-10 10:03:08 -04:00
|
|
|
return &RootCertificate{
|
|
|
|
|
RootCertificate: &store.RootCertificate{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *RootCertificate) clone() *RootCertificate {
|
|
|
|
|
cp := proto.Clone(r.RootCertificate)
|
|
|
|
|
return &RootCertificate{
|
|
|
|
|
RootCertificate: cp.(*store.RootCertificate),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-27 13:17:29 -04:00
|
|
|
// Validate the RootCertificate. On success, return nil
|
2022-05-10 10:03:08 -04:00
|
|
|
func (r *RootCertificate) ValidateNewRootCertificate(ctx context.Context) error {
|
2022-06-20 18:20:17 -07:00
|
|
|
const op = "server.(RootCertificate).ValidateNewRootCertificate"
|
2022-05-10 10:03:08 -04:00
|
|
|
if &r.SerialNumber == nil {
|
|
|
|
|
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")
|
|
|
|
|
}
|
2022-11-16 17:17:28 -06:00
|
|
|
if r.CtPrivateKey == nil {
|
|
|
|
|
return errors.New(ctx, errors.InvalidParameter, op, "missing encrypted private key")
|
2022-05-10 10:03:08 -04:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|