// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package database import ( "context" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "io" "strings" "time" "github.com/hashicorp/vault/helper/random" "github.com/hashicorp/vault/sdk/database/dbplugin/v5" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/template" "github.com/mitchellh/mapstructure" ) // passwordGenerator generates password credentials. // A zero value passwordGenerator is usable. type passwordGenerator struct { // PasswordPolicy is the named password policy used to generate passwords. // If empty (default), a random string of 20 characters will be generated. PasswordPolicy string `mapstructure:"password_policy,omitempty"` } // newPasswordGenerator returns a new passwordGenerator using the given config. // Default values will be set on the returned passwordGenerator if not provided // in the config. func newPasswordGenerator(config map[string]interface{}) (passwordGenerator, error) { var pg passwordGenerator if err := mapstructure.WeakDecode(config, &pg); err != nil { return pg, err } return pg, nil } // Generate generates a password credential using the configured password policy. // Returns the generated password or an error. func (pg passwordGenerator) generate(ctx context.Context, b *databaseBackend, wrapper databaseVersionWrapper) (string, error) { if !wrapper.isV5() && !wrapper.isV4() { return "", fmt.Errorf("no underlying database specified") } // The database plugin generates the password if its interface is v4 if wrapper.isV4() { password, err := wrapper.v4.GenerateCredentials(ctx) if err != nil { return "", err } return password, nil } if pg.PasswordPolicy == "" { return random.DefaultStringGenerator.Generate(ctx, b.GetRandomReader()) } return b.System().GeneratePasswordFromPolicy(ctx, pg.PasswordPolicy) } // configMap returns the configuration of the passwordGenerator // as a map from string to string. func (pg passwordGenerator) configMap() (map[string]interface{}, error) { config := make(map[string]interface{}) if err := mapstructure.WeakDecode(pg, &config); err != nil { return nil, err } return config, nil } // rsaKeyGenerator generates RSA key pair credentials. // A zero value rsaKeyGenerator is usable. type rsaKeyGenerator struct { // Format is the output format of the generated private key. // Options include: 'pkcs8' (default) Format string `mapstructure:"format,omitempty"` // KeyBits is the bit size of the RSA key to generate. // Options include: 2048 (default), 3072, and 4096 KeyBits int `mapstructure:"key_bits,omitempty"` } // newRSAKeyGenerator returns a new rsaKeyGenerator using the given config. // Default values will be set on the returned rsaKeyGenerator if not provided // in the given config. func newRSAKeyGenerator(config map[string]interface{}) (rsaKeyGenerator, error) { var kg rsaKeyGenerator if err := mapstructure.WeakDecode(config, &kg); err != nil { return kg, err } switch strings.ToLower(kg.Format) { case "": kg.Format = "pkcs8" case "pkcs8": default: return kg, fmt.Errorf("invalid format: %v", kg.Format) } switch kg.KeyBits { case 0: kg.KeyBits = 2048 case 2048, 3072, 4096: default: return kg, fmt.Errorf("invalid key_bits: %v", kg.KeyBits) } return kg, nil } // Generate generates an RSA key pair. Returns a PEM-encoded, PKIX marshaled // public key and a PEM-encoded private key marshaled into the configuration // format (in that order) or an error. func (kg *rsaKeyGenerator) generate(r io.Reader) ([]byte, []byte, error) { reader := rand.Reader if r != nil { reader = r } var keyBits int switch kg.KeyBits { case 0: keyBits = 2048 case 2048, 3072, 4096: keyBits = kg.KeyBits default: return nil, nil, fmt.Errorf("invalid key_bits: %v", kg.KeyBits) } key, err := rsa.GenerateKey(reader, keyBits) if err != nil { return nil, nil, err } public, err := x509.MarshalPKIXPublicKey(key.Public()) if err != nil { return nil, nil, err } var private []byte switch strings.ToLower(kg.Format) { case "", "pkcs8": private, err = x509.MarshalPKCS8PrivateKey(key) if err != nil { return nil, nil, err } default: return nil, nil, fmt.Errorf("invalid format: %v", kg.Format) } publicBlock := &pem.Block{ Type: "PUBLIC KEY", Bytes: public, } privateBlock := &pem.Block{ Type: "PRIVATE KEY", Bytes: private, } return pem.EncodeToMemory(publicBlock), pem.EncodeToMemory(privateBlock), nil } // configMap returns the configuration of the rsaKeyGenerator // as a map from string to string. func (kg rsaKeyGenerator) configMap() (map[string]interface{}, error) { config := make(map[string]interface{}) if err := mapstructure.WeakDecode(kg, &config); err != nil { return nil, err } return config, nil } type ClientCertificateGenerator struct { // CommonNameTemplate is username template to be used for the client certificate common name. CommonNameTemplate string `mapstructure:"common_name_template,omitempty"` // CAPrivateKey is the PEM-encoded private key for the given ca_cert. CAPrivateKey string `mapstructure:"ca_private_key,omitempty"` // CACert is the PEM-encoded CA certificate. CACert string `mapstructure:"ca_cert,omitempty"` // KeyType specifies the desired key type. // Options include: 'rsa', 'ed25519', 'ec'. KeyType string `mapstructure:"key_type,omitempty"` // KeyBits is the number of bits to use for the generated keys. // Options include: with key_type=rsa, 2048 (default), 3072, 4096; // With key_type=ec, allowed values are: 224, 256 (default), 384, 521; // Ignored with key_type=ed25519. KeyBits int `mapstructure:"key_bits,omitempty"` // SignatureBits is the number of bits to use in the signature algorithm. // Options include: 256 (default), 384, 512. SignatureBits int `mapstructure:"signature_bits,omitempty"` parsedCABundle *certutil.ParsedCertBundle cnProducer template.StringTemplate } // newClientCertificateGenerator returns a new ClientCertificateGenerator // using the given config. Default values will be set on the returned // ClientCertificateGenerator if not provided in the config. func newClientCertificateGenerator(config map[string]interface{}) (ClientCertificateGenerator, error) { var cg ClientCertificateGenerator if err := mapstructure.WeakDecode(config, &cg); err != nil { return cg, err } switch cg.KeyType { case "rsa": switch cg.KeyBits { case 0: cg.KeyBits = 2048 case 2048, 3072, 4096: default: return cg, fmt.Errorf("invalid key_bits") } case "ec": switch cg.KeyBits { case 0: cg.KeyBits = 256 case 224, 256, 384, 521: default: return cg, fmt.Errorf("invalid key_bits") } case "ed25519": // key_bits ignored default: return cg, fmt.Errorf("invalid key_type") } switch cg.SignatureBits { case 0: cg.SignatureBits = 256 case 256, 384, 512: default: return cg, fmt.Errorf("invalid signature_bits") } if cg.CommonNameTemplate == "" { return cg, fmt.Errorf("missing required common_name_template") } // Validate the common name template t, err := template.NewTemplate(template.Template(cg.CommonNameTemplate)) if err != nil { return cg, fmt.Errorf("failed to create template: %w", err) } _, err = t.Generate(dbplugin.UsernameMetadata{}) if err != nil { return cg, fmt.Errorf("invalid common_name_template: %w", err) } cg.cnProducer = t if cg.CACert == "" { return cg, fmt.Errorf("missing required ca_cert") } if cg.CAPrivateKey == "" { return cg, fmt.Errorf("missing required ca_private_key") } parsedBundle, err := certutil.ParsePEMBundle(strings.Join([]string{cg.CACert, cg.CAPrivateKey}, "\n")) if err != nil { return cg, err } if parsedBundle.PrivateKey == nil { return cg, fmt.Errorf("private key not found in the PEM bundle") } if parsedBundle.PrivateKeyType == certutil.UnknownPrivateKey { return cg, fmt.Errorf("unknown private key found in the PEM bundle") } if parsedBundle.Certificate == nil { return cg, fmt.Errorf("certificate not found in the PEM bundle") } if !parsedBundle.Certificate.IsCA { return cg, fmt.Errorf("the given certificate is not marked for CA use") } if !parsedBundle.Certificate.BasicConstraintsValid { return cg, fmt.Errorf("the given certificate does not meet basic constraints for CA use") } certBundle, err := parsedBundle.ToCertBundle() if err != nil { return cg, fmt.Errorf("error converting raw values into cert bundle: %w", err) } parsedCABundle, err := certBundle.ToParsedCertBundle() if err != nil { return cg, fmt.Errorf("failed to parse cert bundle: %w", err) } cg.parsedCABundle = parsedCABundle return cg, nil } func (cg *ClientCertificateGenerator) generate(r io.Reader, expiration time.Time, userMeta dbplugin.UsernameMetadata) (*certutil.CertBundle, string, error) { commonName, err := cg.cnProducer.Generate(userMeta) if err != nil { return nil, "", err } // Set defaults keyBits := cg.KeyBits signatureBits := cg.SignatureBits switch cg.KeyType { case "rsa": if keyBits == 0 { keyBits = 2048 } if signatureBits == 0 { signatureBits = 256 } case "ec": if keyBits == 0 { keyBits = 256 } if signatureBits == 0 { if keyBits == 224 { signatureBits = 256 } else { signatureBits = keyBits } } case "ed25519": // key_bits ignored if signatureBits == 0 { signatureBits = 256 } } subject := pkix.Name{ CommonName: commonName, // Additional subject DN options intentionally omitted for now } creation := &certutil.CreationBundle{ Params: &certutil.CreationParameters{ Subject: subject, KeyType: cg.KeyType, KeyBits: cg.KeyBits, SignatureBits: cg.SignatureBits, NotAfter: expiration, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: certutil.ClientAuthExtKeyUsage, BasicConstraintsValidForNonCA: false, NotBeforeDuration: 30 * time.Second, URLs: &certutil.URLEntries{ IssuingCertificates: []string{}, CRLDistributionPoints: []string{}, OCSPServers: []string{}, }, }, SigningBundle: &certutil.CAInfoBundle{ ParsedCertBundle: *cg.parsedCABundle, URLs: &certutil.URLEntries{ IssuingCertificates: []string{}, CRLDistributionPoints: []string{}, OCSPServers: []string{}, }, }, } parsedClientBundle, err := certutil.CreateCertificateWithRandomSource(creation, r) if err != nil { return nil, "", fmt.Errorf("unable to generate client certificate: %w", err) } cb, err := parsedClientBundle.ToCertBundle() if err != nil { return nil, "", fmt.Errorf("error converting raw cert bundle to cert bundle: %w", err) } return cb, subject.String(), nil } // configMap returns the configuration of the ClientCertificateGenerator // as a map from string to string. func (cg ClientCertificateGenerator) configMap() (map[string]interface{}, error) { config := make(map[string]interface{}) if err := mapstructure.WeakDecode(cg, &config); err != nil { return nil, err } return config, nil }