Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SDK CIEPS changes #21974

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions sdk/helper/certutil/cieps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package certutil

import (
"crypto/x509"
"encoding/pem"
"fmt"
)

// Source of the issuance request: sign implies that the key material was
// generated by the user and submitted via a CSR request but only ACL level
// validation was applied; issue implies that Vault created the key material
// on behalf of the user with ACL level validation occurring; ACME implies
// that the user submitted a CSR and that additional ACME validation has
// occurred before sending the request to the external service for
// construction.
type CIEPSIssuanceMode string

const (
SignCIEPSMode = "sign"
IssueCIEPSMode = "issue"
ACMECIEPSMode = "acme"
ICACIEPSMOde = "ica"
)

// Configuration of the issuer and mount at the time of this request;
// states the issuer's templated AIA information (falling back to the
// mount-global config if no per-issuer AIA info is set, the issuer's
// leaf_not_after_behavior (permit/truncate/err) for TTLs exceeding the
// issuer's validity period, and the mount's default and max TTL.
type CIEPSIssuanceConfig struct {
AIAValues *URLEntries `json:"aia_values"`
LeafNotAfterBehavior NotAfterBehavior `json:"leaf_not_after_behavior"`
MountDefaultTTL string `json:"mount_default_ttl"`
MountMaxTTL string `json:"mount_max_ttl"`
}

// Structured parameters sent by Vault or explicitly validated by Vault
// prior to sending.
type CIEPSVaultParams struct {
PolicyName string `json:"policy_name,omitempty"`
Mount string `json:"mount"`
Namespace string `json:"ns"`

// These indicate the type of the cluster node talking to the CIEPS
// service. When IsPerfStandby=true, setting StoreCert=true in the
// response will result in Vault forwarding the client's request
// up to the Performance Secondary's active node and re-trying the
// operation (including re-submitting the request to the CIEPS
// service).
//
// Any response returned by the CIEPS service in this case will be
// ignored and not signed by the CA's keys.
//
// IsPRSecondary is set to false when a local mount is used on a
// PR Secondary; in this scenario, PR Secondary nodes behave like
// PR Primary nodes. From a CIEPS service perspective, no behavior
// difference is expected between PR Primary and PR Secondary nodes;
// both will issue and store certificates on their active nodes.
// This information is included for audit tracking purposes.
IsPerfStandby bool `json:"vault_is_performance_standby"`
IsPRSecondary bool `json:"vault_is_performance_secondary"`
IsDRSecondary bool `json:"vault_is_disaster_secondary"`

IssuanceMode CIEPSIssuanceMode `json:"issuance_mode"`

GeneratedKey bool `json:"vault_generated_private_key"`

IssuerName string `json:"requested_issuer_name"`
IssuerID string `json:"requested_issuer_id"`
IssuerCert string `json:"requested_issuer_cert"`

Config CIEPSIssuanceConfig `json:"requested_issuance_config"`
}

// Outer request object sent by Vault to the external CIEPS service.
//
// The top-level fields denote properties about the CIEPS request,
// with various request fields containing untrusted and trusted input
// respectively.
type CIEPSRequest struct {
Version int `json:"request_version"`
UUID string `json:"request_uuid"`
Sync bool `json:"synchronous"`

UserRequestKV map[string]interface{} `json:"user_request_key_values"`
IdentityRequestKV map[string]interface{} `json:"identity_request_key_values,omitempty"`
ACMERequestKV map[string]interface{} `json:"acme_request_key_values,omitempty"`
VaultRequestKV CIEPSVaultParams `json:"vault_request_values"`

// Vault guarantees that UserRequestKV will contain a csr parameter
// for all request types; this field is useful for engine implementations
// to have in parsed format. We assume that this is sent in PEM format,
// aligning with other Vault requests.
ParsedCSR *x509.CertificateRequest `json:"-"`
}

func (req *CIEPSRequest) ParseUserCSR() error {
csrValueRaw, present := req.UserRequestKV["csr"]
if !present {
return fmt.Errorf("missing expected 'csr' attribute on the request")
}

csrValue, ok := csrValueRaw.(string)
if !ok {
return fmt.Errorf("unexpected type of 'csr' attribute: %T", csrValueRaw)
}

if csrValue == "" {
return fmt.Errorf("unexpectedly empty 'csr' attribute on the request")
}

block, rest := pem.Decode([]byte(csrValue))
if len(rest) > 0 {
return fmt.Errorf("failed to decode 'csr': %v bytes of trailing data after PEM block", len(rest))
}
if block == nil {
return fmt.Errorf("failed to decode 'csr' PEM block")
}

csr, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse certificate request: %w", err)
}

req.ParsedCSR = csr
return nil
}

// Expected response object from the external CIEPS service.
//
// When parsing, Vault will disallow unknown fields, failing the
// parse if unknown fields are sent.
type CIEPSResponse struct {
UUID string `json:"request_uuid"`
Error string `json:"error"`
Warnings []string `json:"warnings"`
Certificate string `json:"certificate"`
ParsedCertificate *x509.Certificate `json:"-"`
IssuerRef string `json:"issuer_ref,omitempty"`
StoreCert bool `json:"store_certificate"`
}

func (c *CIEPSResponse) MarshalCertificate() error {
if c.ParsedCertificate == nil || len(c.ParsedCertificate.Raw) == 0 {
return fmt.Errorf("no certificate present")
}

pem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: c.ParsedCertificate.Raw,
})
if len(pem) == 0 {
return fmt.Errorf("failed to generate PEM: no body")
}
c.Certificate = string(pem)

return nil
}
19 changes: 16 additions & 3 deletions sdk/helper/certutil/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
Expand Down Expand Up @@ -304,6 +305,18 @@ func ParsePEMBundle(pemBundle string) (*ParsedCertBundle, error) {
return parsedBundle, nil
}

func (p *ParsedCertBundle) ToTLSCertificate() tls.Certificate {
var cert tls.Certificate
cert.Certificate = append(cert.Certificate, p.CertificateBytes)
cert.Leaf = p.Certificate
cert.PrivateKey = p.PrivateKey
for _, ca := range p.CAChain {
cert.Certificate = append(cert.Certificate, ca.Bytes)
}

return cert
}

// GeneratePrivateKey generates a private key with the specified type and key bits.
func GeneratePrivateKey(keyType string, keyBits int, container ParsedPrivateKeyContainer) error {
return generatePrivateKey(keyType, keyBits, container, nil)
Expand Down Expand Up @@ -1292,7 +1305,7 @@ func NewCertPool(reader io.Reader) (*x509.CertPool, error) {
if err != nil {
return nil, err
}
certs, err := parseCertsPEM(pemBlock)
certs, err := ParseCertsPEM(pemBlock)
if err != nil {
return nil, fmt.Errorf("error reading certs: %s", err)
}
Expand All @@ -1303,9 +1316,9 @@ func NewCertPool(reader io.Reader) (*x509.CertPool, error) {
return pool, nil
}

// parseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array
// ParseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array
// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates
func parseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) {
func ParseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) {
ok := false
certs := []*x509.Certificate{}
for len(pemCerts) > 0 {
Expand Down