// Copyright IBM Corp. 2020, 2026 // SPDX-License-Identifier: BUSL-1.1 package target import ( "context" "time" "repo.anhonesthost.net/CyberCoveLLC/threshold/globals" talias "repo.anhonesthost.net/CyberCoveLLC/threshold/internal/alias/target" "repo.anhonesthost.net/CyberCoveLLC/threshold/internal/db" "repo.anhonesthost.net/CyberCoveLLC/threshold/internal/db/timestamp" "repo.anhonesthost.net/CyberCoveLLC/threshold/internal/errors" "repo.anhonesthost.net/CyberCoveLLC/threshold/internal/util" wrapping "github.com/hashicorp/go-kms-wrapping/v2" ) type proxyServerCertificate interface { Decrypt(ctx context.Context, wrapper wrapping.Wrapper) error Encrypt(ctx context.Context, wrapper wrapping.Wrapper) error GetNotValidAfter() *timestamp.Timestamp SetPrivateKey(privKeyBytes []byte) SetPublicKey(pubKeyBytes []byte) SetCertificate(certBytes []byte) SetNotValidAfter(timestamp *timestamp.Timestamp) ToServerCertificate(ctx context.Context) (*ServerCertificate, error) } // Check if the certificate is close to expiration and regenerate/ update it if necessary. func maybeRegenerateCert(ctx context.Context, t proxyServerCertificate, w db.Writer, wrapper wrapping.Wrapper, sessionMaxSeconds uint32, opt ...Option) (*ServerCertificate, error) { const op = "target.maybeRegenerateCert" switch { case wrapper == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "wrapper is nil") case t == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "target is nil") case util.IsNil(w): return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil") case sessionMaxSeconds == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "session max seconds is 0") } // Check if the certificate is close to expiration. We consider it close to expiration if it is less than 1 day + sessionMaxSeconds away from now. if t.GetNotValidAfter().AsTime().Before(time.Now().Add((time.Hour * 24) + (time.Second * time.Duration(sessionMaxSeconds)))) { notValidAfter := time.Now().AddDate(1, 0, 0) // 1 year from now privKey, pubKey, cert, err := generateKeysAndCert(ctx, notValidAfter, opt...) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating keys and certificate for proxy server certificate")) } t.SetPrivateKey(privKey) t.SetPublicKey(pubKey) t.SetCertificate(cert) t.SetNotValidAfter(timestamp.New(notValidAfter)) if err := t.Encrypt(ctx, wrapper); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error encrypting proxy certificate")) } var rowsUpdated int rowsUpdated, err = w.Update(ctx, t, []string{"PublicKey", "PrivateKey", "Certificate", "NotValidAfter"}, nil) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error updating proxy server certificate")) } if rowsUpdated != 1 { return nil, errors.New(ctx, errors.MultipleRecords, op, "more than 1 proxy server certificate would have been updated") } } if err := t.Decrypt(ctx, wrapper); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to decrypt proxy server certificate private key")) } serverCert, err := t.ToServerCertificate(ctx) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error converting proxy server certificate to PEM")) } return serverCert, nil } func fetchTargetProxyServerCertificate(ctx context.Context, r db.Reader, w db.Writer, targetId, scopeId string, wrapper wrapping.Wrapper, sessionMaxSeconds uint32) (*ServerCertificate, error) { const op = "target.fetchTargetProxyServerCert" switch { case wrapper == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "wrapper is nil") case util.IsNil(r): return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil") case util.IsNil(w): return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil") case targetId == "": return nil, errors.New(ctx, errors.InvalidParameter, op, "target id is empty") case scopeId == "": return nil, errors.New(ctx, errors.InvalidParameter, op, "scope id is empty") } targetCert := allocTargetProxyCertificate() if err := r.SearchWhere(ctx, &targetCert, "target_id = ?", []any{targetId}); err != nil { return nil, errors.Wrap(ctx, err, op) } // If a cert is not found, this target type does not support proxy server certificates/ it was not generated during target creation. if targetCert.Certificate == nil { return nil, errors.New(ctx, errors.RecordNotFound, op, "target proxy server certificate not found", errors.WithoutEvent()) } return maybeRegenerateCert(ctx, targetCert, w, wrapper, sessionMaxSeconds) } func fetchTargetAliasProxyServerCertificate(ctx context.Context, r db.Reader, w db.Writer, targetId, scopeId string, alias *talias.Alias, wrapper wrapping.Wrapper, sessionMaxSeconds uint32, opt ...Option) (*ServerCertificate, error) { const op = "target.fetchTargetProxyServerCert" switch { case wrapper == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "wrapper is nil") case util.IsNil(r): return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil") case util.IsNil(w): return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil") case alias == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "alias is nil") case alias.Value == "": return nil, errors.New(ctx, errors.InvalidParameter, op, "alias value is empty") case targetId == "": return nil, errors.New(ctx, errors.InvalidParameter, op, "target id is empty") case scopeId == "": return nil, errors.New(ctx, errors.InvalidParameter, op, "scope id is empty") } // We first check if a localhost cert exists for this target; if it does not, one was not generated during // target creation because it is not applicable and we don't need an alias proxy cert. targetCert := allocTargetProxyCertificate() if err := r.SearchWhere(ctx, &targetCert, "target_id = ?", []any{targetId}); err != nil { return nil, errors.Wrap(ctx, err, op) } if targetCert.Certificate == nil { return nil, errors.New(ctx, errors.RecordNotFound, op, "target proxy server certificate not found", errors.WithoutEvent()) } aliasCert := allocTargetAliasProxyCertificate() if err := r.SearchWhere(ctx, &aliasCert, "target_id = ? and alias_id = ?", []any{targetId, alias.PublicId}); err != nil { return nil, errors.Wrap(ctx, err, op) } // Create the cert, if not found- alias certs are not created as part of target creation. var err error if aliasCert.Certificate == nil { aliasCert, err = NewTargetAliasProxyCertificate(ctx, targetId, alias, opt...) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating new target alias proxy certificate")) } if err = aliasCert.Encrypt(ctx, wrapper); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error encrypting target alias proxy certificate")) } id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix) if err != nil { return nil, errors.Wrap(ctx, err, op) } aliasCert.PublicId = id if err = w.Create(ctx, aliasCert); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target alias proxy certificate")) } } return maybeRegenerateCert(ctx, aliasCert, w, wrapper, sessionMaxSeconds, WithAlias(alias)) }