2015-05-15 16:13:05 +00:00
package pki
import (
2018-01-19 06:44:44 +00:00
"context"
2017-11-06 17:05:07 +00:00
"crypto"
2016-02-19 23:50:51 +00:00
"crypto/ecdsa"
2021-10-05 15:28:49 +00:00
"crypto/ed25519"
2016-02-18 22:55:47 +00:00
"crypto/rsa"
2015-05-15 16:13:05 +00:00
"crypto/x509"
"crypto/x509/pkix"
2015-11-18 15:16:09 +00:00
"encoding/asn1"
2017-11-06 17:05:07 +00:00
"encoding/base64"
2015-09-30 01:48:31 +00:00
"encoding/pem"
2015-05-15 16:13:05 +00:00
"fmt"
2021-09-15 16:59:12 +00:00
"io"
2015-05-15 16:13:05 +00:00
"net"
2018-06-15 19:32:25 +00:00
"net/url"
2015-05-15 16:13:05 +00:00
"regexp"
2019-12-11 15:16:44 +00:00
"strconv"
2015-05-15 16:13:05 +00:00
"strings"
"time"
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/strutil"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/sdk/framework"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
2019-05-09 15:43:11 +00:00
"github.com/ryanuber/go-glob"
2019-12-11 15:16:44 +00:00
"golang.org/x/crypto/cryptobyte"
cbbasn1 "golang.org/x/crypto/cryptobyte/asn1"
2018-02-10 15:07:10 +00:00
"golang.org/x/net/idna"
2015-05-15 16:13:05 +00:00
)
2019-05-09 15:43:11 +00:00
type inputBundle struct {
role * roleEntry
req * logical . Request
apiData * framework . FieldData
2016-09-28 00:50:17 +00:00
}
2015-11-18 15:16:09 +00:00
var (
2018-02-10 15:07:10 +00:00
// A note on hostnameRegex: although we set the StrictDomainName option
// when doing the idna conversion, this appears to only affect output, not
// input, so it will allow e.g. host^123.example.com straight through. So
// we still need to use this to check the output.
2020-02-04 22:46:38 +00:00
hostnameRegex = regexp . MustCompile ( ` ^(\*\.)?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\.?$ ` )
2019-12-11 15:16:44 +00:00
oidExtensionBasicConstraints = [ ] int { 2 , 5 , 29 , 19 }
oidExtensionSubjectAltName = [ ] int { 2 , 5 , 29 , 17 }
2015-11-18 15:16:09 +00:00
)
func oidInExtensions ( oid asn1 . ObjectIdentifier , extensions [ ] pkix . Extension ) bool {
for _ , e := range extensions {
if e . Id . Equal ( oid ) {
return true
}
}
return false
}
2015-11-16 16:42:06 +00:00
2015-11-16 21:32:49 +00:00
func getFormat ( data * framework . FieldData ) string {
format := data . Get ( "format" ) . ( string )
switch format {
case "pem" :
case "der" :
2016-02-01 18:19:41 +00:00
case "pem_bundle" :
2015-11-16 21:32:49 +00:00
default :
format = ""
}
return format
}
2015-06-19 16:48:18 +00:00
// Fetches the CA info. Unlike other certificates, the CA info is stored
// in the backend as a CertBundle, because we are storing its private key
2019-05-09 15:43:11 +00:00
func fetchCAInfo ( ctx context . Context , req * logical . Request ) ( * certutil . CAInfoBundle , error ) {
2018-01-19 06:44:44 +00:00
bundleEntry , err := req . Storage . Get ( ctx , "config/ca_bundle" )
2015-05-15 16:13:05 +00:00
if err != nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "unable to fetch local CA certificate/key: %v" , err ) }
2015-05-15 16:13:05 +00:00
}
2015-06-19 16:48:18 +00:00
if bundleEntry == nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "backend must be configured with a CA certificate/key" }
2015-05-15 16:13:05 +00:00
}
2015-06-19 16:48:18 +00:00
var bundle certutil . CertBundle
if err := bundleEntry . DecodeJSON ( & bundle ) ; err != nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "unable to decode local CA certificate/key: %v" , err ) }
2015-05-15 16:13:05 +00:00
}
2015-06-17 16:43:36 +00:00
parsedBundle , err := bundle . ToParsedCertBundle ( )
2015-05-15 16:13:05 +00:00
if err != nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : err . Error ( ) }
2015-05-15 16:13:05 +00:00
}
2015-06-19 16:48:18 +00:00
if parsedBundle . Certificate == nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : "stored CA information not able to be parsed" }
2015-05-15 16:13:05 +00:00
}
2019-05-09 15:43:11 +00:00
caInfo := & certutil . CAInfoBundle { * parsedBundle , nil }
2015-10-09 17:45:17 +00:00
2018-01-19 06:44:44 +00:00
entries , err := getURLs ( ctx , req )
2015-10-09 17:45:17 +00:00
if err != nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "unable to fetch URL information: %v" , err ) }
2015-10-09 17:45:17 +00:00
}
2015-10-14 01:13:40 +00:00
if entries == nil {
2019-05-09 15:43:11 +00:00
entries = & certutil . URLEntries {
2015-10-14 18:48:51 +00:00
IssuingCertificates : [ ] string { } ,
CRLDistributionPoints : [ ] string { } ,
OCSPServers : [ ] string { } ,
2015-10-14 01:13:40 +00:00
}
2015-10-09 17:45:17 +00:00
}
2015-10-14 01:13:40 +00:00
caInfo . URLs = entries
2015-10-09 17:45:17 +00:00
return caInfo , nil
2015-05-15 16:13:05 +00:00
}
2015-06-19 16:48:18 +00:00
// Allows fetching certificates from the backend; it handles the slightly
// separate pathing for CA, CRL, and revoked certificates.
2018-01-19 06:44:44 +00:00
func fetchCertBySerial ( ctx context . Context , req * logical . Request , prefix , serial string ) ( * logical . StorageEntry , error ) {
2017-05-03 18:58:22 +00:00
var path , legacyPath string
2017-04-06 21:00:50 +00:00
var err error
var certEntry * logical . StorageEntry
2015-06-19 16:48:18 +00:00
2017-05-03 14:12:58 +00:00
hyphenSerial := normalizeSerial ( serial )
2017-05-02 18:11:57 +00:00
colonSerial := strings . Replace ( strings . ToLower ( serial ) , "-" , ":" , - 1 )
2015-06-19 16:48:18 +00:00
2015-05-15 16:13:05 +00:00
switch {
2016-03-07 15:57:38 +00:00
// Revoked goes first as otherwise ca/crl get hardcoded paths which fail if
// we actually want revocation info
case strings . HasPrefix ( prefix , "revoked/" ) :
2017-05-03 18:58:22 +00:00
legacyPath = "revoked/" + colonSerial
2017-05-02 18:11:57 +00:00
path = "revoked/" + hyphenSerial
2015-05-15 16:13:05 +00:00
case serial == "ca" :
path = "ca"
case serial == "crl" :
path = "crl"
default :
2017-05-03 18:58:22 +00:00
legacyPath = "certs/" + colonSerial
2017-05-02 18:11:57 +00:00
path = "certs/" + hyphenSerial
2015-05-15 16:13:05 +00:00
}
2018-01-19 06:44:44 +00:00
certEntry , err = req . Storage . Get ( ctx , path )
2016-02-22 15:36:26 +00:00
if err != nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "error fetching certificate %s: %s" , serial , err ) }
2016-02-22 15:36:26 +00:00
}
2017-04-06 21:00:50 +00:00
if certEntry != nil {
if certEntry . Value == nil || len ( certEntry . Value ) == 0 {
return nil , errutil . InternalError { Err : fmt . Sprintf ( "returned certificate bytes for serial %s were empty" , serial ) }
}
return certEntry , nil
}
2017-05-03 20:13:54 +00:00
// If legacyPath is unset, it's going to be a CA or CRL; return immediately
2017-05-03 18:58:22 +00:00
if legacyPath == "" {
2016-02-22 15:36:26 +00:00
return nil , nil
2015-05-15 16:13:05 +00:00
}
2019-01-09 02:08:21 +00:00
// Retrieve the old-style path. We disregard errors here because they
// always manifest on windows, and thus the initial check for a revoked
// cert fails would return an error when the cert isn't revoked, preventing
// the happy path from working.
certEntry , _ = req . Storage . Get ( ctx , legacyPath )
2016-02-22 15:36:26 +00:00
if certEntry == nil {
return nil , nil
2015-05-15 16:13:05 +00:00
}
2015-06-19 16:48:18 +00:00
if certEntry . Value == nil || len ( certEntry . Value ) == 0 {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "returned certificate bytes for serial %s were empty" , serial ) }
2015-05-15 16:13:05 +00:00
}
2017-04-27 21:09:59 +00:00
// Update old-style paths to new-style paths
2017-05-03 18:58:22 +00:00
certEntry . Key = path
2018-01-19 06:44:44 +00:00
if err = req . Storage . Put ( ctx , certEntry ) ; err != nil {
2017-04-06 21:00:50 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "error saving certificate with serial %s to new location" , serial ) }
}
2018-01-19 06:44:44 +00:00
if err = req . Storage . Delete ( ctx , legacyPath ) ; err != nil {
2017-04-06 21:00:50 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "error deleting certificate with serial %s from old location" , serial ) }
}
2015-06-19 16:48:18 +00:00
return certEntry , nil
2015-05-15 16:13:05 +00:00
}
2015-06-19 16:48:18 +00:00
// Given a set of requested names for a certificate, verifies that all of them
// match the various toggles set in the role for controlling issuance.
// If one does not pass, it is returned in the string argument.
2020-07-08 16:52:25 +00:00
func validateNames ( b * backend , data * inputBundle , names [ ] string ) string {
2015-10-02 18:27:30 +00:00
for _ , name := range names {
2015-05-15 16:13:05 +00:00
sanitizedName := name
2021-03-10 05:28:27 +00:00
emailDomain := sanitizedName
2015-10-02 18:27:30 +00:00
isEmail := false
2015-05-15 16:13:05 +00:00
isWildcard := false
2015-11-23 19:15:32 +00:00
// If it has an @, assume it is an email address and separate out the
// user from the hostname portion so that we can act on the hostname.
// Note that this matches behavior from the alt_names parameter. If it
// ends up being problematic for users, I guess that could be separated
// into dns_names and email_names in the future to be explicit, but I
// don't think this is likely.
2021-03-10 05:28:27 +00:00
if strings . Contains ( sanitizedName , "@" ) {
splitEmail := strings . Split ( sanitizedName , "@" )
2015-10-02 18:27:30 +00:00
if len ( splitEmail ) != 2 {
2017-03-16 15:41:00 +00:00
return name
2015-10-02 18:27:30 +00:00
}
sanitizedName = splitEmail [ 1 ]
2015-10-02 19:47:45 +00:00
emailDomain = splitEmail [ 1 ]
2015-10-02 18:27:30 +00:00
isEmail = true
}
2015-11-23 19:15:32 +00:00
// If we have an asterisk as the first part of the domain name, mark it
// as wildcard and set the sanitized name to the remainder of the
// domain
2015-10-02 18:27:30 +00:00
if strings . HasPrefix ( sanitizedName , "*." ) {
sanitizedName = sanitizedName [ 2 : ]
2015-05-15 16:13:05 +00:00
isWildcard = true
}
2015-08-20 21:33:37 +00:00
2015-11-23 19:15:32 +00:00
// Email addresses using wildcard domain names do not make sense
if isEmail && isWildcard {
2017-03-16 15:41:00 +00:00
return name
2015-11-23 19:15:32 +00:00
}
// AllowAnyName is checked after this because EnforceHostnames still
// applies when allowing any name. Also, we check the sanitized name to
// ensure that we are not either checking a full email address or a
// wildcard prefix.
2018-02-16 22:19:34 +00:00
if data . role . EnforceHostnames {
2018-02-10 15:07:10 +00:00
p := idna . New (
idna . StrictDomainName ( true ) ,
idna . VerifyDNSLength ( true ) ,
)
converted , err := p . ToASCII ( sanitizedName )
if err != nil {
return name
}
if ! hostnameRegex . MatchString ( converted ) {
2017-03-16 15:41:00 +00:00
return name
2015-08-20 21:33:37 +00:00
}
2015-10-14 15:26:56 +00:00
}
2015-11-23 19:15:32 +00:00
// Self-explanatory
2018-02-16 22:19:34 +00:00
if data . role . AllowAnyName {
2015-10-14 15:26:56 +00:00
continue
2015-05-15 16:13:05 +00:00
}
2015-08-20 21:33:37 +00:00
2015-11-23 19:15:32 +00:00
// The following blocks all work the same basic way:
// 1) If a role allows a certain class of base (localhost, token
2015-12-01 04:49:11 +00:00
// display name, role-configured domains), perform further tests
2015-11-23 19:15:32 +00:00
//
2021-03-10 05:28:27 +00:00
// 2) If there is a perfect match on either the sanitized name or it's an
2015-11-23 19:15:32 +00:00
// email address with a perfect match on the hostname portion, allow it
//
// 3) If subdomains are allowed, we check based on the sanitized name;
// note that if not a wildcard, will be equivalent to the email domain
// for email checks, and we already checked above for both a wildcard
// and email address being present in the same name
// 3a) First we check for a non-wildcard subdomain, as in <name>.<base>
// 3b) Then we check if it's a wildcard and the base domain is a match
//
// Variances are noted in-line
2018-02-16 22:19:34 +00:00
if data . role . AllowLocalhost {
2021-03-10 05:28:27 +00:00
if sanitizedName == "localhost" ||
sanitizedName == "localdomain" ||
2015-11-23 19:15:32 +00:00
( isEmail && emailDomain == "localhost" ) ||
( isEmail && emailDomain == "localdomain" ) {
2015-10-02 19:47:45 +00:00
continue
}
2018-02-16 22:19:34 +00:00
if data . role . AllowSubdomains {
2015-11-23 19:15:32 +00:00
// It is possible, if unlikely, to have a subdomain of "localhost"
if strings . HasSuffix ( sanitizedName , ".localhost" ) ||
2015-12-01 04:49:11 +00:00
( isWildcard && sanitizedName == "localhost" ) {
2015-11-23 19:15:32 +00:00
continue
2015-10-02 19:47:45 +00:00
}
2015-11-23 19:15:32 +00:00
// A subdomain of "localdomain" is also not entirely uncommon
if strings . HasSuffix ( sanitizedName , ".localdomain" ) ||
2015-12-01 04:49:11 +00:00
( isWildcard && sanitizedName == "localdomain" ) {
2015-10-02 19:47:45 +00:00
continue
}
}
}
2018-02-16 22:19:34 +00:00
if data . role . AllowTokenDisplayName {
if name == data . req . DisplayName {
2015-05-15 16:13:05 +00:00
continue
}
2018-02-16 22:19:34 +00:00
if data . role . AllowSubdomains {
2015-11-23 19:15:32 +00:00
if isEmail {
// If it's an email address, we need to parse the token
// display name in order to do a proper comparison of the
// subdomain
2018-02-16 22:19:34 +00:00
if strings . Contains ( data . req . DisplayName , "@" ) {
splitDisplay := strings . Split ( data . req . DisplayName , "@" )
2015-11-23 19:15:32 +00:00
if len ( splitDisplay ) == 2 {
// Compare the sanitized name against the hostname
2018-03-20 18:54:10 +00:00
// portion of the email address in the broken
2015-11-23 19:15:32 +00:00
// display name
if strings . HasSuffix ( sanitizedName , "." + splitDisplay [ 1 ] ) {
continue
}
}
2015-10-02 18:27:30 +00:00
}
}
2018-02-16 22:19:34 +00:00
if strings . HasSuffix ( sanitizedName , "." + data . req . DisplayName ) ||
( isWildcard && sanitizedName == data . req . DisplayName ) {
2015-05-15 16:13:05 +00:00
continue
}
}
}
2018-02-16 22:19:34 +00:00
if len ( data . role . AllowedDomains ) > 0 {
2015-12-01 04:49:11 +00:00
valid := false
2018-02-16 22:19:34 +00:00
for _ , currDomain := range data . role . AllowedDomains {
2015-12-01 04:49:11 +00:00
// If there is, say, a trailing comma, ignore it
if currDomain == "" {
2015-05-15 16:13:05 +00:00
continue
}
2015-12-01 04:49:11 +00:00
2020-07-08 16:52:25 +00:00
if data . role . AllowedDomainsTemplate {
2020-08-17 18:37:00 +00:00
isTemplate , _ := framework . ValidateIdentityTemplate ( currDomain )
if isTemplate && data . req . EntityID != "" {
2020-07-08 16:52:25 +00:00
tmpCurrDomain , err := framework . PopulateIdentityTemplate ( currDomain , data . req . EntityID , b . System ( ) )
if err != nil {
continue
}
currDomain = tmpCurrDomain
}
}
2015-12-01 04:49:11 +00:00
// First, allow an exact match of the base domain if that role flag
// is enabled
2018-02-16 22:19:34 +00:00
if data . role . AllowBareDomains &&
2021-03-10 05:28:27 +00:00
( strings . EqualFold ( sanitizedName , currDomain ) ||
2021-10-04 18:02:47 +00:00
( isEmail && strings . EqualFold ( emailDomain , currDomain ) ) ||
2021-11-11 00:09:06 +00:00
// Handle the use case of AllowedDomain being an email address
( isEmail && strings . EqualFold ( name , currDomain ) ) ) {
2015-12-01 04:49:11 +00:00
valid = true
break
}
2018-02-16 22:19:34 +00:00
if data . role . AllowSubdomains {
2015-12-01 04:49:11 +00:00
if strings . HasSuffix ( sanitizedName , "." + currDomain ) ||
2021-03-10 05:28:27 +00:00
( isWildcard && strings . EqualFold ( sanitizedName , currDomain ) ) {
2015-12-01 04:49:11 +00:00
valid = true
break
}
}
2017-05-01 14:40:18 +00:00
2018-02-16 22:19:34 +00:00
if data . role . AllowGlobDomains &&
2017-05-01 14:40:18 +00:00
strings . Contains ( currDomain , "*" ) &&
2021-03-10 05:28:27 +00:00
glob . Glob ( currDomain , sanitizedName ) {
2017-05-01 14:40:18 +00:00
valid = true
break
}
2015-12-01 04:49:11 +00:00
}
2020-07-08 16:52:25 +00:00
2015-12-01 04:49:11 +00:00
if valid {
continue
2015-05-15 16:13:05 +00:00
}
}
2017-03-16 15:41:00 +00:00
return name
2015-05-15 16:13:05 +00:00
}
2017-03-16 15:41:00 +00:00
return ""
2015-05-15 16:13:05 +00:00
}
2018-02-16 22:19:34 +00:00
// validateOtherSANs checks if the values requested are allowed. If an OID
// isn't allowed, it will be returned as the first string. If a value isn't
// allowed, it will be returned as the second string. Empty strings + error
// means everything is okay.
2019-05-09 15:43:11 +00:00
func validateOtherSANs ( data * inputBundle , requested map [ string ] [ ] string ) ( string , string , error ) {
2019-12-11 15:16:44 +00:00
if len ( data . role . AllowedOtherSANs ) == 1 && data . role . AllowedOtherSANs [ 0 ] == "*" {
// Anything is allowed
return "" , "" , nil
2018-10-08 13:51:43 +00:00
}
2019-12-11 15:16:44 +00:00
2018-02-16 22:19:34 +00:00
allowed , err := parseOtherSANs ( data . role . AllowedOtherSANs )
if err != nil {
2021-04-22 15:20:59 +00:00
return "" , "" , fmt . Errorf ( "error parsing role's allowed SANs: %w" , err )
2018-02-16 22:19:34 +00:00
}
for oid , names := range requested {
for _ , name := range names {
allowedNames , ok := allowed [ oid ]
if ! ok {
return oid , "" , nil
}
valid := false
for _ , allowedName := range allowedNames {
if glob . Glob ( allowedName , name ) {
valid = true
break
}
}
if ! valid {
return oid , name , nil
}
}
}
return "" , "" , nil
}
func parseOtherSANs ( others [ ] string ) ( map [ string ] [ ] string , error ) {
result := map [ string ] [ ] string { }
for _ , other := range others {
splitOther := strings . SplitN ( other , ";" , 2 )
if len ( splitOther ) != 2 {
return nil , fmt . Errorf ( "expected a semicolon in other SAN %q" , other )
}
splitType := strings . SplitN ( splitOther [ 1 ] , ":" , 2 )
if len ( splitType ) != 2 {
return nil , fmt . Errorf ( "expected a colon in other SAN %q" , other )
}
2018-10-08 13:51:43 +00:00
switch {
case strings . EqualFold ( splitType [ 0 ] , "utf8" ) :
case strings . EqualFold ( splitType [ 0 ] , "utf-8" ) :
default :
2018-02-16 22:19:34 +00:00
return nil , fmt . Errorf ( "only utf8 other SANs are supported; found non-supported type in other SAN %q" , other )
}
result [ splitOther [ 0 ] ] = append ( result [ splitOther [ 0 ] ] , splitType [ 1 ] )
}
return result , nil
}
2019-05-09 15:43:11 +00:00
func validateSerialNumber ( data * inputBundle , serialNumber string ) string {
2018-06-05 03:18:39 +00:00
valid := false
if len ( data . role . AllowedSerialNumbers ) > 0 {
for _ , currSerialNumber := range data . role . AllowedSerialNumbers {
if currSerialNumber == "" {
continue
}
if ( strings . Contains ( currSerialNumber , "*" ) &&
glob . Glob ( currSerialNumber , serialNumber ) ) ||
currSerialNumber == serialNumber {
valid = true
break
}
}
}
if ! valid {
return serialNumber
} else {
return ""
}
}
2018-01-19 06:44:44 +00:00
func generateCert ( ctx context . Context ,
b * backend ,
2019-05-09 15:43:11 +00:00
input * inputBundle ,
caSign * certutil . CAInfoBundle ,
2021-09-15 16:59:12 +00:00
isCA bool ,
randomSource io . Reader ) ( * certutil . ParsedCertBundle , error ) {
2015-08-29 13:03:02 +00:00
2019-05-09 15:43:11 +00:00
if input . role == nil {
2018-02-16 22:19:34 +00:00
return nil , errutil . InternalError { Err : "no role found in data bundle" }
}
2019-05-09 15:43:11 +00:00
if input . role . KeyType == "rsa" && input . role . KeyBits < 2048 {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "RSA keys < 2048 bits are unsafe and not supported" }
2016-02-18 22:55:47 +00:00
}
2019-05-09 15:43:11 +00:00
data , err := generateCreationBundle ( b , input , caSign , nil )
2015-08-29 13:03:02 +00:00
if err != nil {
return nil , err
}
2019-05-09 15:43:11 +00:00
if data . Params == nil {
2018-03-20 18:54:10 +00:00
return nil , errutil . InternalError { Err : "nil parameters received from parameter bundle generation" }
2018-02-16 22:19:34 +00:00
}
2015-08-29 13:03:02 +00:00
2015-10-09 17:45:17 +00:00
if isCA {
2019-05-09 15:43:11 +00:00
data . Params . IsCA = isCA
data . Params . PermittedDNSDomains = input . apiData . Get ( "permitted_dns_domains" ) . ( [ ] string )
2017-08-15 20:10:36 +00:00
2019-05-09 15:43:11 +00:00
if data . SigningBundle == nil {
2015-11-16 16:42:06 +00:00
// Generating a self-signed root certificate
2019-05-09 15:43:11 +00:00
entries , err := getURLs ( ctx , input . req )
2015-11-16 16:42:06 +00:00
if err != nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . InternalError { Err : fmt . Sprintf ( "unable to fetch URL information: %v" , err ) }
2015-11-16 16:42:06 +00:00
}
if entries == nil {
2019-05-09 15:43:11 +00:00
entries = & certutil . URLEntries {
2015-11-16 16:42:06 +00:00
IssuingCertificates : [ ] string { } ,
CRLDistributionPoints : [ ] string { } ,
OCSPServers : [ ] string { } ,
}
}
2019-05-09 15:43:11 +00:00
data . Params . URLs = entries
2015-11-16 16:42:06 +00:00
2019-05-09 15:43:11 +00:00
if input . role . MaxPathLength == nil {
data . Params . MaxPathLength = - 1
2015-11-16 16:42:06 +00:00
} else {
2019-05-09 15:43:11 +00:00
data . Params . MaxPathLength = * input . role . MaxPathLength
2015-11-16 16:42:06 +00:00
}
}
2015-10-09 17:45:17 +00:00
}
2021-09-15 16:59:12 +00:00
parsedBundle , err := certutil . CreateCertificateWithRandomSource ( data , randomSource )
2015-08-29 13:03:02 +00:00
if err != nil {
return nil , err
}
return parsedBundle , nil
}
2015-10-02 15:55:30 +00:00
// N.B.: This is only meant to be used for generating intermediate CAs.
// It skips some sanity checks.
2021-09-15 16:59:12 +00:00
func generateIntermediateCSR ( b * backend , input * inputBundle , randomSource io . Reader ) ( * certutil . ParsedCSRBundle , error ) {
2019-05-09 15:43:11 +00:00
creation , err := generateCreationBundle ( b , input , nil , nil )
2015-11-12 16:24:32 +00:00
if err != nil {
return nil , err
2015-08-29 13:03:02 +00:00
}
2019-05-09 15:43:11 +00:00
if creation . Params == nil {
2018-03-20 18:54:10 +00:00
return nil , errutil . InternalError { Err : "nil parameters received from parameter bundle generation" }
2018-02-16 22:19:34 +00:00
}
2015-08-29 13:03:02 +00:00
2019-05-09 15:43:11 +00:00
addBasicConstraints := input . apiData != nil && input . apiData . Get ( "add_basic_constraints" ) . ( bool )
2021-09-15 16:59:12 +00:00
parsedBundle , err := certutil . CreateCSRWithRandomSource ( creation , addBasicConstraints , randomSource )
2015-08-29 13:03:02 +00:00
if err != nil {
return nil , err
}
return parsedBundle , nil
}
func signCert ( b * backend ,
2019-05-09 15:43:11 +00:00
data * inputBundle ,
caSign * certutil . CAInfoBundle ,
2015-10-09 17:45:17 +00:00
isCA bool ,
2018-02-16 22:19:34 +00:00
useCSRValues bool ) ( * certutil . ParsedCertBundle , error ) {
if data . role == nil {
return nil , errutil . InternalError { Err : "no role found in data bundle" }
}
2015-08-29 13:03:02 +00:00
2018-02-16 22:19:34 +00:00
csrString := data . apiData . Get ( "csr" ) . ( string )
2015-09-30 01:48:31 +00:00
if csrString == "" {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf ( "\"csr\" is empty" ) }
2015-09-30 01:48:31 +00:00
}
pemBytes := [ ] byte ( csrString )
pemBlock , pemBytes := pem . Decode ( pemBytes )
if pemBlock == nil {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "csr contains no data" }
2015-09-30 01:48:31 +00:00
}
csr , err := x509 . ParseCertificateRequest ( pemBlock . Bytes )
if err != nil {
2017-04-28 12:30:24 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf ( "certificate request could not be parsed: %v" , err ) }
2015-09-30 01:48:31 +00:00
}
2018-02-16 22:19:34 +00:00
switch data . role . KeyType {
2016-02-19 23:50:51 +00:00
case "rsa" :
// Verify that the key matches the role type
if csr . PublicKeyAlgorithm != x509 . RSA {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2016-02-19 23:50:51 +00:00
"role requires keys of type %s" ,
2018-02-16 22:19:34 +00:00
data . role . KeyType ) }
2016-02-19 23:50:51 +00:00
}
pubKey , ok := csr . PublicKey . ( * rsa . PublicKey )
if ! ok {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "could not parse CSR's public key" }
2016-02-19 23:50:51 +00:00
}
// Verify that the key is at least 2048 bits
if pubKey . N . BitLen ( ) < 2048 {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "RSA keys < 2048 bits are unsafe and not supported" }
2016-02-19 23:50:51 +00:00
}
// Verify that the bit size is at least the size specified in the role
2018-02-16 22:19:34 +00:00
if pubKey . N . BitLen ( ) < data . role . KeyBits {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2016-02-19 23:50:51 +00:00
"role requires a minimum of a %d-bit key, but CSR's key is %d bits" ,
2018-02-16 22:19:34 +00:00
data . role . KeyBits ,
2016-02-19 23:50:51 +00:00
pubKey . N . BitLen ( ) ) }
}
case "ec" :
// Verify that the key matches the role type
if csr . PublicKeyAlgorithm != x509 . ECDSA {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2016-02-19 23:50:51 +00:00
"role requires keys of type %s" ,
2018-02-16 22:19:34 +00:00
data . role . KeyType ) }
2016-02-19 23:50:51 +00:00
}
pubKey , ok := csr . PublicKey . ( * ecdsa . PublicKey )
if ! ok {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "could not parse CSR's public key" }
2016-02-19 23:50:51 +00:00
}
// Verify that the bit size is at least the size specified in the role
2018-02-16 22:19:34 +00:00
if pubKey . Params ( ) . BitSize < data . role . KeyBits {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2016-02-19 23:50:51 +00:00
"role requires a minimum of a %d-bit key, but CSR's key is %d bits" ,
2018-02-16 22:19:34 +00:00
data . role . KeyBits ,
2016-02-19 23:50:51 +00:00
pubKey . Params ( ) . BitSize ) }
}
2021-10-05 15:28:49 +00:00
case "ed25519" :
// Verify that the key matches the role type
if csr . PublicKeyAlgorithm != x509 . PublicKeyAlgorithm ( x509 . Ed25519 ) {
return nil , errutil . UserError { Err : fmt . Sprintf (
"role requires keys of type %s" ,
data . role . KeyType ) }
}
_ , ok := csr . PublicKey . ( ed25519 . PublicKey )
if ! ok {
return nil , errutil . UserError { Err : "could not parse CSR's public key" }
}
2016-02-19 23:50:51 +00:00
case "any" :
// We only care about running RSA < 2048 bit checks, so if not RSA
// break out
if csr . PublicKeyAlgorithm != x509 . RSA {
break
}
// Run RSA < 2048 bit checks
2016-02-18 22:55:47 +00:00
pubKey , ok := csr . PublicKey . ( * rsa . PublicKey )
if ! ok {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "could not parse CSR's public key" }
2016-02-18 22:55:47 +00:00
}
if pubKey . N . BitLen ( ) < 2048 {
2016-07-28 19:19:27 +00:00
return nil , errutil . UserError { Err : "RSA keys < 2048 bits are unsafe and not supported" }
2016-02-18 22:55:47 +00:00
}
2016-02-19 23:50:51 +00:00
2016-02-18 22:55:47 +00:00
}
2019-05-09 15:43:11 +00:00
creation , err := generateCreationBundle ( b , data , caSign , csr )
2015-08-29 13:03:02 +00:00
if err != nil {
return nil , err
}
2019-05-09 15:43:11 +00:00
if creation . Params == nil {
2018-03-20 18:54:10 +00:00
return nil , errutil . InternalError { Err : "nil parameters received from parameter bundle generation" }
2018-02-16 22:19:34 +00:00
}
2015-08-29 13:03:02 +00:00
2019-05-09 15:43:11 +00:00
creation . Params . IsCA = isCA
creation . Params . UseCSRValues = useCSRValues
2015-10-09 17:45:17 +00:00
2017-08-15 20:10:36 +00:00
if isCA {
2019-05-09 15:43:11 +00:00
creation . Params . PermittedDNSDomains = data . apiData . Get ( "permitted_dns_domains" ) . ( [ ] string )
2017-08-15 20:10:36 +00:00
}
2019-05-09 15:43:11 +00:00
parsedBundle , err := certutil . SignCertificate ( creation )
2015-08-29 13:03:02 +00:00
if err != nil {
return nil , err
}
return parsedBundle , nil
}
2019-12-11 15:16:44 +00:00
// otherNameRaw describes a name related to a certificate which is not in one
// of the standard name formats. RFC 5280, 4.2.1.6:
// OtherName ::= SEQUENCE {
// type-id OBJECT IDENTIFIER,
// value [0] EXPLICIT ANY DEFINED BY type-id }
type otherNameRaw struct {
TypeID asn1 . ObjectIdentifier
Value asn1 . RawValue
}
type otherNameUtf8 struct {
oid string
value string
}
// ExtractUTF8String returns the UTF8 string contained in the Value, or an error
// if none is present.
func ( oraw * otherNameRaw ) extractUTF8String ( ) ( * otherNameUtf8 , error ) {
svalue := cryptobyte . String ( oraw . Value . Bytes )
var outTag cbbasn1 . Tag
var val cryptobyte . String
read := svalue . ReadAnyASN1 ( & val , & outTag )
if read && outTag == asn1 . TagUTF8String {
return & otherNameUtf8 { oid : oraw . TypeID . String ( ) , value : string ( val ) } , nil
}
return nil , fmt . Errorf ( "no UTF-8 string found in OtherName" )
}
func ( o otherNameUtf8 ) String ( ) string {
return fmt . Sprintf ( "%s;%s:%s" , o . oid , "UTF-8" , o . value )
}
func getOtherSANsFromX509Extensions ( exts [ ] pkix . Extension ) ( [ ] otherNameUtf8 , error ) {
var ret [ ] otherNameUtf8
for _ , ext := range exts {
if ! ext . Id . Equal ( oidExtensionSubjectAltName ) {
continue
}
err := forEachSAN ( ext . Value , func ( tag int , data [ ] byte ) error {
if tag != 0 {
return nil
}
var other otherNameRaw
_ , err := asn1 . UnmarshalWithParams ( data , & other , "tag:0" )
if err != nil {
2021-04-22 15:20:59 +00:00
return fmt . Errorf ( "could not parse requested other SAN: %w" , err )
2019-12-11 15:16:44 +00:00
}
val , err := other . extractUTF8String ( )
if err != nil {
return err
}
ret = append ( ret , * val )
return nil
} )
if err != nil {
return nil , err
}
}
return ret , nil
}
func forEachSAN ( extension [ ] byte , callback func ( tag int , data [ ] byte ) error ) error {
// RFC 5280, 4.2.1.6
// SubjectAltName ::= GeneralNames
//
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
//
// GeneralName ::= CHOICE {
// otherName [0] OtherName,
// rfc822Name [1] IA5String,
// dNSName [2] IA5String,
// x400Address [3] ORAddress,
// directoryName [4] Name,
// ediPartyName [5] EDIPartyName,
// uniformResourceIdentifier [6] IA5String,
// iPAddress [7] OCTET STRING,
// registeredID [8] OBJECT IDENTIFIER }
var seq asn1 . RawValue
rest , err := asn1 . Unmarshal ( extension , & seq )
if err != nil {
return err
} else if len ( rest ) != 0 {
return fmt . Errorf ( "x509: trailing data after X.509 extension" )
}
if ! seq . IsCompound || seq . Tag != 16 || seq . Class != 0 {
return asn1 . StructuralError { Msg : "bad SAN sequence" }
}
rest = seq . Bytes
for len ( rest ) > 0 {
var v asn1 . RawValue
rest , err = asn1 . Unmarshal ( rest , & v )
if err != nil {
return err
}
if err := callback ( v . Tag , v . FullBytes ) ; err != nil {
return err
}
}
return nil
}
2015-11-19 21:51:27 +00:00
// generateCreationBundle is a shared function that reads parameters supplied
2019-05-09 15:43:11 +00:00
// from the various endpoints and generates a CreationParameters with the
2015-11-19 21:51:27 +00:00
// parameters that can be used to issue or sign
2019-05-09 15:43:11 +00:00
func generateCreationBundle ( b * backend , data * inputBundle , caSign * certutil . CAInfoBundle , csr * x509 . CertificateRequest ) ( * certutil . CreationBundle , error ) {
2017-03-16 15:41:00 +00:00
// Read in names -- CN, DNS and email addresses
2015-11-16 15:10:03 +00:00
var cn string
2018-06-05 03:18:39 +00:00
var ridSerialNumber string
2017-03-16 15:41:00 +00:00
dnsNames := [ ] string { }
emailAddresses := [ ] string { }
2015-11-19 21:51:27 +00:00
{
2019-05-09 15:43:11 +00:00
if csr != nil && data . role . UseCSRCommonName {
cn = csr . Subject . CommonName
2015-11-16 15:10:03 +00:00
}
if cn == "" {
2018-02-16 22:19:34 +00:00
cn = data . apiData . Get ( "common_name" ) . ( string )
if cn == "" && data . role . RequireCN {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : ` the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true, unless "require_cn" is set to false ` }
2015-11-19 21:51:27 +00:00
}
2015-10-09 17:45:17 +00:00
}
2017-02-16 06:04:29 +00:00
2018-06-05 03:18:39 +00:00
ridSerialNumber = data . apiData . Get ( "serial_number" ) . ( string )
// only take serial number from CSR if one was not supplied via API
2019-05-09 15:43:11 +00:00
if ridSerialNumber == "" && csr != nil {
ridSerialNumber = csr . Subject . SerialNumber
2018-06-05 03:18:39 +00:00
}
2019-05-09 15:43:11 +00:00
if csr != nil && data . role . UseCSRSANs {
dnsNames = csr . DNSNames
emailAddresses = csr . EmailAddresses
2017-03-15 18:38:18 +00:00
}
2018-02-16 22:19:34 +00:00
if cn != "" && ! data . apiData . Get ( "exclude_cn_from_sans" ) . ( bool ) {
2016-03-17 20:28:40 +00:00
if strings . Contains ( cn , "@" ) {
// Note: emails are not disallowed if the role's email protection
// flag is false, because they may well be included for
// informational purposes; it is up to the verifying party to
// ensure that email addresses in a subject alternate name can be
// used for the purpose for which they are presented
emailAddresses = append ( emailAddresses , cn )
} else {
2018-02-10 15:07:10 +00:00
// Only add to dnsNames if it's actually a DNS name but convert
// idn first
p := idna . New (
idna . StrictDomainName ( true ) ,
idna . VerifyDNSLength ( true ) ,
)
converted , err := p . ToASCII ( cn )
if err != nil {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : err . Error ( ) }
2018-02-10 15:07:10 +00:00
}
if hostnameRegex . MatchString ( converted ) {
dnsNames = append ( dnsNames , converted )
2018-02-09 18:42:19 +00:00
}
2016-03-17 20:28:40 +00:00
}
2015-11-19 21:51:27 +00:00
}
2017-03-15 18:38:18 +00:00
2019-05-09 15:43:11 +00:00
if csr == nil || ! data . role . UseCSRSANs {
2018-02-16 22:19:34 +00:00
cnAltRaw , ok := data . apiData . GetOk ( "alt_names" )
2017-03-15 18:38:18 +00:00
if ok {
2021-03-10 05:28:27 +00:00
cnAlt := strutil . ParseDedupAndSortStrings ( cnAltRaw . ( string ) , "," )
2017-03-16 15:41:00 +00:00
for _ , v := range cnAlt {
if strings . Contains ( v , "@" ) {
emailAddresses = append ( emailAddresses , v )
} else {
2018-02-10 15:07:10 +00:00
// Only add to dnsNames if it's actually a DNS name but
// convert idn first
p := idna . New (
idna . StrictDomainName ( true ) ,
idna . VerifyDNSLength ( true ) ,
)
converted , err := p . ToASCII ( v )
if err != nil {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : err . Error ( ) }
2018-02-10 15:07:10 +00:00
}
if hostnameRegex . MatchString ( converted ) {
dnsNames = append ( dnsNames , converted )
2018-02-09 18:42:19 +00:00
}
2015-11-19 21:51:27 +00:00
}
2015-10-02 18:27:30 +00:00
}
2015-09-29 23:13:54 +00:00
}
2015-08-29 13:03:02 +00:00
}
2015-11-19 21:51:27 +00:00
2017-03-16 15:41:00 +00:00
// Check the CN. This ensures that the CN is checked even if it's
// excluded from SANs.
2018-02-09 18:42:19 +00:00
if cn != "" {
2020-07-08 16:52:25 +00:00
badName := validateNames ( b , data , [ ] string { cn } )
2018-02-09 18:42:19 +00:00
if len ( badName ) != 0 {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2018-02-09 18:42:19 +00:00
"common name %s not allowed by this role" , badName ) }
}
2017-03-16 15:41:00 +00:00
}
2018-06-05 03:18:39 +00:00
if ridSerialNumber != "" {
badName := validateSerialNumber ( data , ridSerialNumber )
if len ( badName ) != 0 {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2018-06-05 03:18:39 +00:00
"serial_number %s not allowed by this role" , badName ) }
}
}
2015-11-19 21:51:27 +00:00
// Check for bad email and/or DNS names
2020-07-08 16:52:25 +00:00
badName := validateNames ( b , data , dnsNames )
2015-11-19 21:51:27 +00:00
if len ( badName ) != 0 {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2017-03-16 15:41:00 +00:00
"subject alternate name %s not allowed by this role" , badName ) }
2015-11-19 21:51:27 +00:00
}
2020-07-08 16:52:25 +00:00
badName = validateNames ( b , data , emailAddresses )
2015-11-19 21:51:27 +00:00
if len ( badName ) != 0 {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2017-03-16 15:41:00 +00:00
"email address %s not allowed by this role" , badName ) }
2015-11-19 21:51:27 +00:00
}
2015-08-29 13:03:02 +00:00
}
2019-12-11 15:16:44 +00:00
// otherSANsInput has the same format as the other_sans HTTP param in the
// Vault PKI API: it is a list of strings of the form <oid>;<type>:<value>
// where <type> must be UTF8/UTF-8.
var otherSANsInput [ ] string
// otherSANs is the output of parseOtherSANs(otherSANsInput): its keys are
// the <oid> value, its values are of the form [<type>, <value>]
2018-02-16 22:19:34 +00:00
var otherSANs map [ string ] [ ] string
if sans := data . apiData . Get ( "other_sans" ) . ( [ ] string ) ; len ( sans ) > 0 {
2019-12-11 15:16:44 +00:00
otherSANsInput = sans
}
if data . role . UseCSRSANs && csr != nil && len ( csr . Extensions ) > 0 {
others , err := getOtherSANsFromX509Extensions ( csr . Extensions )
if err != nil {
2021-04-22 15:20:59 +00:00
return nil , errutil . UserError { Err : fmt . Errorf ( "could not parse requested other SAN: %w" , err ) . Error ( ) }
2019-12-11 15:16:44 +00:00
}
for _ , other := range others {
otherSANsInput = append ( otherSANsInput , other . String ( ) )
}
}
if len ( otherSANsInput ) > 0 {
requested , err := parseOtherSANs ( otherSANsInput )
2018-02-16 22:19:34 +00:00
if err != nil {
2021-04-22 15:20:59 +00:00
return nil , errutil . UserError { Err : fmt . Errorf ( "could not parse requested other SAN: %w" , err ) . Error ( ) }
2018-02-16 22:19:34 +00:00
}
badOID , badName , err := validateOtherSANs ( data , requested )
switch {
case err != nil :
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : err . Error ( ) }
2018-02-16 22:19:34 +00:00
case len ( badName ) > 0 :
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2018-02-16 22:19:34 +00:00
"other SAN %s not allowed for OID %s by this role" , badName , badOID ) }
case len ( badOID ) > 0 :
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2018-02-16 22:19:34 +00:00
"other SAN OID %s not allowed by this role" , badOID ) }
default :
otherSANs = requested
}
}
2015-11-19 21:51:27 +00:00
// Get and verify any IP SANs
2015-10-02 18:27:30 +00:00
ipAddresses := [ ] net . IP { }
2015-11-19 21:51:27 +00:00
{
2019-05-09 15:43:11 +00:00
if csr != nil && data . role . UseCSRSANs {
if len ( csr . IPAddresses ) > 0 {
2018-02-16 22:19:34 +00:00
if ! data . role . AllowIPSANs {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2017-04-13 17:40:31 +00:00
"IP Subject Alternative Names are not allowed in this role, but was provided some via CSR" ) }
}
2019-05-09 15:43:11 +00:00
ipAddresses = csr . IPAddresses
2017-03-15 18:38:18 +00:00
}
} else {
2018-06-15 19:32:25 +00:00
ipAlt := data . apiData . Get ( "ip_sans" ) . ( [ ] string )
if len ( ipAlt ) > 0 {
if ! data . role . AllowIPSANs {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2018-06-15 19:32:25 +00:00
"IP Subject Alternative Names are not allowed in this role, but was provided %s" , ipAlt ) }
}
for _ , v := range ipAlt {
parsedIP := net . ParseIP ( v )
if parsedIP == nil {
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
2018-06-15 19:32:25 +00:00
"the value '%s' is not a valid IP address" , v ) }
}
ipAddresses = append ( ipAddresses , parsedIP )
}
}
}
}
URIs := [ ] * url . URL { }
{
2019-05-09 15:43:11 +00:00
if csr != nil && data . role . UseCSRSANs {
if len ( csr . URIs ) > 0 {
2018-06-15 19:32:25 +00:00
if len ( data . role . AllowedURISANs ) == 0 {
2021-04-08 16:43:39 +00:00
return nil , errutil . UserError {
Err : fmt . Sprintf (
"URI Subject Alternative Names are not allowed in this role, but were provided via CSR" ) ,
2017-03-15 18:38:18 +00:00
}
2018-06-15 19:32:25 +00:00
}
// validate uri sans
2019-05-09 15:43:11 +00:00
for _ , uri := range csr . URIs {
2018-06-15 19:32:25 +00:00
valid := false
for _ , allowed := range data . role . AllowedURISANs {
validURI := glob . Glob ( allowed , uri . String ( ) )
if validURI {
valid = true
break
2017-03-15 18:38:18 +00:00
}
2015-11-19 21:51:27 +00:00
}
2018-06-15 19:32:25 +00:00
if ! valid {
2021-04-08 16:43:39 +00:00
return nil , errutil . UserError {
Err : fmt . Sprintf (
"URI Subject Alternative Names were provided via CSR which are not valid for this role" ) ,
2018-06-15 19:32:25 +00:00
}
}
URIs = append ( URIs , uri )
}
}
} else {
uriAlt := data . apiData . Get ( "uri_sans" ) . ( [ ] string )
if len ( uriAlt ) > 0 {
if len ( data . role . AllowedURISANs ) == 0 {
2021-04-08 16:43:39 +00:00
return nil , errutil . UserError {
Err : fmt . Sprintf (
"URI Subject Alternative Names are not allowed in this role, but were provided via the API" ) ,
2018-06-15 19:32:25 +00:00
}
}
for _ , uri := range uriAlt {
valid := false
for _ , allowed := range data . role . AllowedURISANs {
validURI := glob . Glob ( allowed , uri )
if validURI {
valid = true
break
}
}
if ! valid {
2021-04-08 16:43:39 +00:00
return nil , errutil . UserError {
Err : fmt . Sprintf (
"URI Subject Alternative Names were provided via the API which are not valid for this role" ) ,
2018-06-15 19:32:25 +00:00
}
}
parsedURI , err := url . Parse ( uri )
if parsedURI == nil || err != nil {
2021-04-08 16:43:39 +00:00
return nil , errutil . UserError {
Err : fmt . Sprintf (
"the provided URI Subject Alternative Name '%s' is not a valid URI" , uri ) ,
2018-06-15 19:32:25 +00:00
}
}
URIs = append ( URIs , parsedURI )
2015-09-29 23:13:54 +00:00
}
2015-08-29 13:03:02 +00:00
}
}
}
2019-05-02 21:31:29 +00:00
// Most of these could also be RemoveDuplicateStable, or even
// leave duplicates in, but OU is the one most likely to be duplicated.
2018-02-16 22:19:34 +00:00
subject := pkix . Name {
2018-02-18 02:01:36 +00:00
CommonName : cn ,
2018-06-05 03:18:39 +00:00
SerialNumber : ridSerialNumber ,
2021-04-02 15:33:24 +00:00
Country : strutil . RemoveDuplicatesStable ( data . role . Country , false ) ,
Organization : strutil . RemoveDuplicatesStable ( data . role . Organization , false ) ,
2019-05-02 21:31:29 +00:00
OrganizationalUnit : strutil . RemoveDuplicatesStable ( data . role . OU , false ) ,
2021-04-02 15:33:24 +00:00
Locality : strutil . RemoveDuplicatesStable ( data . role . Locality , false ) ,
Province : strutil . RemoveDuplicatesStable ( data . role . Province , false ) ,
StreetAddress : strutil . RemoveDuplicatesStable ( data . role . StreetAddress , false ) ,
PostalCode : strutil . RemoveDuplicatesStable ( data . role . PostalCode , false ) ,
2018-02-16 22:19:34 +00:00
}
2017-03-16 15:41:00 +00:00
2017-09-15 02:49:04 +00:00
// Get the TTL and verify it against the max allowed
2015-08-29 13:03:02 +00:00
var ttl time . Duration
var maxTTL time . Duration
2017-08-15 20:10:36 +00:00
var notAfter time . Time
2021-11-11 00:09:06 +00:00
var err error
2015-11-19 21:51:27 +00:00
{
2018-02-16 22:19:34 +00:00
ttl = time . Duration ( data . apiData . Get ( "ttl" ) . ( int ) ) * time . Second
2021-11-11 00:09:06 +00:00
notAfterAlt := data . role . NotAfter
if notAfterAlt == "" {
notAfterAltRaw , ok := data . apiData . GetOk ( "not_after" )
if ok {
notAfterAlt = notAfterAltRaw . ( string )
}
}
if ttl > 0 && notAfterAlt != "" {
return nil , errutil . UserError {
Err : fmt . Sprintf (
"Either ttl or not_after should be provided. Both should not be provided in the same request." ) ,
}
}
2015-08-29 13:03:02 +00:00
2018-05-09 14:29:54 +00:00
if ttl == 0 && data . role . TTL > 0 {
ttl = data . role . TTL
2018-03-20 01:01:41 +00:00
}
2018-05-09 14:29:54 +00:00
if data . role . MaxTTL > 0 {
maxTTL = data . role . MaxTTL
2015-11-19 21:51:27 +00:00
}
2015-10-03 01:02:16 +00:00
2017-08-31 19:46:13 +00:00
if ttl == 0 {
ttl = b . System ( ) . DefaultLeaseTTL ( )
}
if maxTTL == 0 {
maxTTL = b . System ( ) . MaxLeaseTTL ( )
}
2015-11-19 21:51:27 +00:00
if ttl > maxTTL {
2017-08-31 19:46:13 +00:00
ttl = maxTTL
2015-11-19 21:51:27 +00:00
}
2015-10-02 18:27:30 +00:00
2021-11-11 00:09:06 +00:00
if notAfterAlt != "" {
notAfter , err = time . Parse ( time . RFC3339 , notAfterAlt )
if err != nil {
return nil , errutil . UserError { Err : err . Error ( ) }
}
} else {
notAfter = time . Now ( ) . Add ( ttl )
}
2015-11-19 21:51:27 +00:00
// If it's not self-signed, verify that the issued certificate won't be
// valid past the lifetime of the CA certificate
2019-05-09 15:43:11 +00:00
if caSign != nil &&
notAfter . After ( caSign . Certificate . NotAfter ) && ! data . role . AllowExpirationPastCA {
2015-08-29 13:03:02 +00:00
2019-05-09 15:43:11 +00:00
return nil , errutil . UserError { Err : fmt . Sprintf (
"cannot satisfy request, as TTL would result in notAfter %s that is beyond the expiration of the CA certificate at %s" , notAfter . Format ( time . RFC3339Nano ) , caSign . Certificate . NotAfter . Format ( time . RFC3339Nano ) ) }
}
}
creation := & certutil . CreationBundle {
Params : & certutil . CreationParameters {
Subject : subject ,
2019-10-28 16:31:56 +00:00
DNSNames : strutil . RemoveDuplicates ( dnsNames , false ) ,
EmailAddresses : strutil . RemoveDuplicates ( emailAddresses , false ) ,
2019-05-09 15:43:11 +00:00
IPAddresses : ipAddresses ,
URIs : URIs ,
OtherSANs : otherSANs ,
KeyType : data . role . KeyType ,
KeyBits : data . role . KeyBits ,
2021-09-10 21:39:05 +00:00
SignatureBits : data . role . SignatureBits ,
2019-05-09 15:43:11 +00:00
NotAfter : notAfter ,
KeyUsage : x509 . KeyUsage ( parseKeyUsages ( data . role . KeyUsage ) ) ,
ExtKeyUsage : parseExtKeyUsages ( data . role ) ,
ExtKeyUsageOIDs : data . role . ExtKeyUsageOIDs ,
PolicyIdentifiers : data . role . PolicyIdentifiers ,
BasicConstraintsValidForNonCA : data . role . BasicConstraintsValidForNonCA ,
NotBeforeDuration : data . role . NotBeforeDuration ,
} ,
SigningBundle : caSign ,
CSR : csr ,
2015-08-29 13:03:02 +00:00
}
2015-11-19 21:51:27 +00:00
// Don't deal with URLs or max path length if it's self-signed, as these
// normally come from the signing bundle
2019-05-09 15:43:11 +00:00
if caSign == nil {
return creation , nil
2015-11-16 16:42:06 +00:00
}
2015-10-14 19:53:57 +00:00
2015-11-19 21:51:27 +00:00
// This will have been read in from the getURLs function
2019-05-09 15:43:11 +00:00
creation . Params . URLs = caSign . URLs
2015-11-19 21:51:27 +00:00
// If the max path length in the role is not nil, it was specified at
// generation time with the max_path_length parameter; otherwise derive it
// from the signing certificate
2018-02-16 22:19:34 +00:00
if data . role . MaxPathLength != nil {
2019-05-09 15:43:11 +00:00
creation . Params . MaxPathLength = * data . role . MaxPathLength
2015-11-16 16:42:06 +00:00
} else {
switch {
2019-05-09 15:43:11 +00:00
case caSign . Certificate . MaxPathLen < 0 :
creation . Params . MaxPathLength = - 1
case caSign . Certificate . MaxPathLen == 0 &&
caSign . Certificate . MaxPathLenZero :
2015-11-16 16:42:06 +00:00
// The signing function will ensure that we do not issue a CA cert
2019-05-09 15:43:11 +00:00
creation . Params . MaxPathLength = 0
2015-11-16 16:42:06 +00:00
default :
// If this takes it to zero, we handle this case later if
// necessary
2019-05-09 15:43:11 +00:00
creation . Params . MaxPathLength = caSign . Certificate . MaxPathLen - 1
2015-10-14 19:53:57 +00:00
}
2015-10-14 01:13:40 +00:00
}
2019-05-09 15:43:11 +00:00
return creation , nil
2015-05-15 16:13:05 +00:00
}
2017-08-15 20:10:36 +00:00
2017-11-06 17:05:07 +00:00
func convertRespToPKCS8 ( resp * logical . Response ) error {
privRaw , ok := resp . Data [ "private_key" ]
if ! ok {
return nil
}
priv , ok := privRaw . ( string )
if ! ok {
return fmt . Errorf ( "error converting response to pkcs8: could not parse original value as string" )
}
privKeyTypeRaw , ok := resp . Data [ "private_key_type" ]
if ! ok {
return fmt . Errorf ( "error converting response to pkcs8: %q not found in response" , "private_key_type" )
}
privKeyType , ok := privKeyTypeRaw . ( certutil . PrivateKeyType )
if ! ok {
return fmt . Errorf ( "error converting response to pkcs8: could not parse original type value as string" )
}
var keyData [ ] byte
var pemUsed bool
var err error
var signer crypto . Signer
block , _ := pem . Decode ( [ ] byte ( priv ) )
if block == nil {
keyData , err = base64 . StdEncoding . DecodeString ( priv )
if err != nil {
2021-04-22 15:20:59 +00:00
return fmt . Errorf ( "error converting response to pkcs8: error decoding original value: %w" , err )
2017-11-06 17:05:07 +00:00
}
} else {
keyData = block . Bytes
pemUsed = true
}
switch privKeyType {
case certutil . RSAPrivateKey :
signer , err = x509 . ParsePKCS1PrivateKey ( keyData )
case certutil . ECPrivateKey :
signer , err = x509 . ParseECPrivateKey ( keyData )
2021-11-24 19:24:06 +00:00
case certutil . Ed25519PrivateKey :
k , err := x509 . ParsePKCS8PrivateKey ( keyData )
if err != nil {
return fmt . Errorf ( "error converting response to pkcs8: error parsing previous key: %w" , err )
}
signer = k . ( crypto . Signer )
2017-11-06 17:05:07 +00:00
default :
return fmt . Errorf ( "unknown private key type %q" , privKeyType )
}
if err != nil {
2021-04-22 15:20:59 +00:00
return fmt . Errorf ( "error converting response to pkcs8: error parsing previous key: %w" , err )
2017-11-06 17:05:07 +00:00
}
2018-03-29 19:32:16 +00:00
keyData , err = x509 . MarshalPKCS8PrivateKey ( signer )
2017-11-06 17:05:07 +00:00
if err != nil {
2021-04-22 15:20:59 +00:00
return fmt . Errorf ( "error converting response to pkcs8: error marshaling pkcs8 key: %w" , err )
2017-11-06 17:05:07 +00:00
}
if pemUsed {
block . Type = "PRIVATE KEY"
block . Bytes = keyData
2018-03-18 20:00:51 +00:00
resp . Data [ "private_key" ] = strings . TrimSpace ( string ( pem . EncodeToMemory ( block ) ) )
2017-11-06 17:05:07 +00:00
} else {
resp . Data [ "private_key" ] = base64 . StdEncoding . EncodeToString ( keyData )
}
return nil
}
2019-12-11 15:16:44 +00:00
func handleOtherCSRSANs ( in * x509 . CertificateRequest , sans map [ string ] [ ] string ) error {
certTemplate := & x509 . Certificate {
DNSNames : in . DNSNames ,
IPAddresses : in . IPAddresses ,
EmailAddresses : in . EmailAddresses ,
URIs : in . URIs ,
}
if err := handleOtherSANs ( certTemplate , sans ) ; err != nil {
return err
}
if len ( certTemplate . ExtraExtensions ) > 0 {
for _ , v := range certTemplate . ExtraExtensions {
in . ExtraExtensions = append ( in . ExtraExtensions , v )
}
}
return nil
}
func handleOtherSANs ( in * x509 . Certificate , sans map [ string ] [ ] string ) error {
// If other SANs is empty we return which causes normal Go stdlib parsing
// of the other SAN types
if len ( sans ) == 0 {
return nil
}
var rawValues [ ] asn1 . RawValue
// We need to generate an IMPLICIT sequence for compatibility with OpenSSL
// -- it's an open question what the default for RFC 5280 actually is, see
// https://github.com/openssl/openssl/issues/5091 -- so we have to use
// cryptobyte because using the asn1 package's marshaling always produces
// an EXPLICIT sequence. Note that asn1 is way too magical according to
// agl, and cryptobyte is modeled after the CBB/CBS bits that agl put into
// boringssl.
for oid , vals := range sans {
for _ , val := range vals {
var b cryptobyte . Builder
oidStr , err := stringToOid ( oid )
if err != nil {
return err
}
b . AddASN1ObjectIdentifier ( oidStr )
b . AddASN1 ( cbbasn1 . Tag ( 0 ) . ContextSpecific ( ) . Constructed ( ) , func ( b * cryptobyte . Builder ) {
b . AddASN1 ( cbbasn1 . UTF8String , func ( b * cryptobyte . Builder ) {
b . AddBytes ( [ ] byte ( val ) )
} )
} )
m , err := b . Bytes ( )
if err != nil {
return err
}
rawValues = append ( rawValues , asn1 . RawValue { Tag : 0 , Class : 2 , IsCompound : true , Bytes : m } )
}
}
// If other SANs is empty we return which causes normal Go stdlib parsing
// of the other SAN types
if len ( rawValues ) == 0 {
return nil
}
// Append any existing SANs, sans marshalling
rawValues = append ( rawValues , marshalSANs ( in . DNSNames , in . EmailAddresses , in . IPAddresses , in . URIs ) ... )
// Marshal and add to ExtraExtensions
ext := pkix . Extension {
// This is the defined OID for subjectAltName
Id : asn1 . ObjectIdentifier ( oidExtensionSubjectAltName ) ,
}
var err error
ext . Value , err = asn1 . Marshal ( rawValues )
if err != nil {
return err
}
in . ExtraExtensions = append ( in . ExtraExtensions , ext )
return nil
}
// Note: Taken from the Go source code since it's not public, and used in the
// modified function below (which also uses these consts upstream)
const (
nameTypeOther = 0
nameTypeEmail = 1
nameTypeDNS = 2
nameTypeURI = 6
nameTypeIP = 7
)
// Note: Taken from the Go source code since it's not public, plus changed to not marshal
// marshalSANs marshals a list of addresses into a the contents of an X.509
// SubjectAlternativeName extension.
func marshalSANs ( dnsNames , emailAddresses [ ] string , ipAddresses [ ] net . IP , uris [ ] * url . URL ) [ ] asn1 . RawValue {
var rawValues [ ] asn1 . RawValue
for _ , name := range dnsNames {
rawValues = append ( rawValues , asn1 . RawValue { Tag : nameTypeDNS , Class : 2 , Bytes : [ ] byte ( name ) } )
}
for _ , email := range emailAddresses {
rawValues = append ( rawValues , asn1 . RawValue { Tag : nameTypeEmail , Class : 2 , Bytes : [ ] byte ( email ) } )
}
for _ , rawIP := range ipAddresses {
// If possible, we always want to encode IPv4 addresses in 4 bytes.
ip := rawIP . To4 ( )
if ip == nil {
ip = rawIP
}
rawValues = append ( rawValues , asn1 . RawValue { Tag : nameTypeIP , Class : 2 , Bytes : ip } )
}
for _ , uri := range uris {
rawValues = append ( rawValues , asn1 . RawValue { Tag : nameTypeURI , Class : 2 , Bytes : [ ] byte ( uri . String ( ) ) } )
}
return rawValues
}
func stringToOid ( in string ) ( asn1 . ObjectIdentifier , error ) {
split := strings . Split ( in , "." )
ret := make ( asn1 . ObjectIdentifier , 0 , len ( split ) )
for _ , v := range split {
i , err := strconv . Atoi ( v )
if err != nil {
return nil , err
}
ret = append ( ret , i )
}
return asn1 . ObjectIdentifier ( ret ) , nil
}