Files
threshold/internal/target/target_certificate.go
T

312 lines
11 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package target
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
mathrand "math/rand"
"net"
"time"
talias "github.com/hashicorp/boundary/internal/alias/target"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/target/store"
"github.com/hashicorp/boundary/internal/util"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
func generatePrivAndPubKeys(ctx context.Context) (privKeyBytes []byte, pubKeyBytes []byte, err error) {
const op = "target.generatePrivAndPubKeys"
// Generate a private key using the P521 curve
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "failed to generate ECDSA key")
}
privKeyBytes, err = x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error marshalling private key"))
}
pubKeyBytes, err = x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error marshalling public key"))
}
return privKeyBytes, pubKeyBytes, nil
}
// generateTargetCert generates a self-signed certificate for the target for localhost with the localhost addresses for ipv4 and ipv6.
// Supports the option withAlias to pass an alias for use in the cert DNS names field
func generateTargetCert(ctx context.Context, privKey *ecdsa.PrivateKey, exp time.Time, opt ...Option) ([]byte, error) {
const op = "target.generateTargetCert"
switch {
case util.IsNil(privKey):
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key")
case exp.IsZero():
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing expiry")
case exp.Before(time.Now()):
return nil, errors.New(ctx, errors.InvalidParameter, op, "expiration time must be in the future")
}
opts := GetOpts(opt...)
template := &x509.Certificate{
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
Subject: pkix.Name{
CommonName: "localhost",
},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-1 * time.Minute),
NotAfter: exp,
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
}
if opts.withAlias != nil {
template.DNSNames = append(template.DNSNames, opts.withAlias.Value)
}
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.GenCert))
}
return certBytes, nil
}
// TargetProxyCertificate represents a proxy certificate for a target
type TargetProxyCertificate struct {
*store.TargetProxyCertificate
tableName string `gorm:"-"`
}
// newTargetProxyCertificate creates a new in memory TargetProxyCertificate
// Supports the options withTargetId to set the target ID
// If this is not provided, the TargetId will need to be set before storing the certificate
func NewTargetProxyCertificate(ctx context.Context, opt ...Option) (*TargetProxyCertificate, error) {
const op = "target.NewTargetProxyCertificate"
opts := GetOpts(opt...)
privKey, pubKey, err := generatePrivAndPubKeys(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target proxy certificate key"))
}
notValidAfter := time.Now().AddDate(1, 0, 0) // 1 year from now
parsedKey, err := x509.ParseECPrivateKey(privKey)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing target proxy certificate key"))
}
certBytes, err := generateTargetCert(ctx, parsedKey, notValidAfter)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target certificate"))
}
return &TargetProxyCertificate{
TargetProxyCertificate: &store.TargetProxyCertificate{
PrivateKey: privKey,
PublicKey: pubKey,
Certificate: certBytes,
NotValidAfter: timestamp.New(notValidAfter),
TargetId: opts.withTargetId,
},
}, nil
}
// encrypt the target cert key before writing it to the db
func (t *TargetProxyCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "target.(TargetProxyCertificate).encrypt"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.WrapStruct(ctx, cipher, t.TargetProxyCertificate, 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("failed to read cipher key id"))
}
t.KeyId = keyId
return nil
}
// decrypt the target cert key after reading it from the db
func (t *TargetProxyCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "target.(TargetProxyCertificate).decrypt"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.UnwrapStruct(ctx, cipher, t.TargetProxyCertificate, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func allocTargetProxyCertificate() *TargetProxyCertificate {
return &TargetProxyCertificate{
TargetProxyCertificate: &store.TargetProxyCertificate{},
}
}
// Clone creates a clone of the TargetProxyCertificate
func (t *TargetProxyCertificate) Clone() *TargetProxyCertificate {
cp := proto.Clone(t.TargetProxyCertificate)
return &TargetProxyCertificate{
TargetProxyCertificate: cp.(*store.TargetProxyCertificate),
}
}
// VetForWrite implements db.VetForWrite() interface and validates a target certificate
func (t *TargetProxyCertificate) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "target.(TargetProxyCertificate).VetForWrite"
switch {
case t.PrivateKeyEncrypted == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing private key")
case t.PublicKey == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing public key")
case t.KeyId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing key id")
case t.TargetId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing target id")
case len(t.Certificate) == 0:
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
case t.NotValidAfter == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing not valid after")
}
return nil
}
// TableName returns the table name.
func (t *TargetProxyCertificate) TableName() string {
return "target_proxy_certificate"
}
// TargetAliasProxyCertificate represents a certificate for a target accessed with an alias
type TargetAliasProxyCertificate struct {
*store.TargetAliasProxyCertificate
tableName string `gorm:"-"`
}
// NewTargetAliasProxyCertificate creates a new in memory TargetAliasProxyCertificate
func NewTargetAliasProxyCertificate(ctx context.Context, targetId string, alias *talias.Alias) (*TargetAliasProxyCertificate, error) {
const op = "target.NewTargetAliasProxyCertificate"
switch {
case targetId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target id")
case alias == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target alias")
}
privKey, pubKey, err := generatePrivAndPubKeys(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target proxy certificate key"))
}
notValidAfter := time.Now().AddDate(1, 0, 0) // 1 year from now
parsedKey, err := x509.ParseECPrivateKey(privKey)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing target proxy certificate key"))
}
certBytes, err := generateTargetCert(ctx, parsedKey, notValidAfter, WithAlias(alias))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target certificate"))
}
return &TargetAliasProxyCertificate{
TargetAliasProxyCertificate: &store.TargetAliasProxyCertificate{
TargetId: targetId,
PrivateKey: privKey,
PublicKey: pubKey,
AliasId: alias.PublicId,
Certificate: certBytes,
NotValidAfter: timestamp.New(notValidAfter),
},
}, nil
}
// encrypt the target cert key before writing it to the db
func (t *TargetAliasProxyCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "target.(TargetAliasProxyCertificate).encrypt"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.WrapStruct(ctx, cipher, t.TargetAliasProxyCertificate, 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("failed to read cipher key id"))
}
t.KeyId = keyId
return nil
}
// decrypt the target cert key after reading it from the db
func (t *TargetAliasProxyCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "target.(TargetAliasProxyCertificate).decrypt"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.UnwrapStruct(ctx, cipher, t.TargetAliasProxyCertificate, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func allocTargetAliasProxyCertificate() *TargetAliasProxyCertificate {
return &TargetAliasProxyCertificate{
TargetAliasProxyCertificate: &store.TargetAliasProxyCertificate{},
}
}
// Clone creates a clone of the TargetAliasProxyCertificate
func (t *TargetAliasProxyCertificate) Clone() *TargetAliasProxyCertificate {
cp := proto.Clone(t.TargetAliasProxyCertificate)
return &TargetAliasProxyCertificate{
TargetAliasProxyCertificate: cp.(*store.TargetAliasProxyCertificate),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the target alias certificate
func (t *TargetAliasProxyCertificate) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "target.(TargetAliasProxyCertificate).VetForWrite"
switch {
case t.PrivateKeyEncrypted == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing private key")
case t.PublicKey == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing public key")
case t.KeyId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing key id")
case t.TargetId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing target id")
case t.AliasId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing alias id")
case len(t.Certificate) == 0:
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
case t.NotValidAfter == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing not valid after")
}
return nil
}
// TableName returns the table name.
func (t *TargetAliasProxyCertificate) TableName() string {
return "target_alias_proxy_certificate"
}