2015-05-15 16:13:05 +00:00
|
|
|
package pki
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
2015-06-12 01:57:05 +00:00
|
|
|
"crypto/rand"
|
2015-05-15 16:13:05 +00:00
|
|
|
"crypto/rsa"
|
2015-08-29 13:03:02 +00:00
|
|
|
"crypto/sha1"
|
2015-05-15 16:13:05 +00:00
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
2015-09-30 01:48:31 +00:00
|
|
|
"encoding/pem"
|
2015-05-15 16:13:05 +00:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"net"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2015-10-02 15:55:30 +00:00
|
|
|
"github.com/hashicorp/otto/helper/uuid"
|
2015-06-17 16:43:36 +00:00
|
|
|
"github.com/hashicorp/vault/helper/certutil"
|
2015-05-15 16:13:05 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
2015-08-29 13:03:02 +00:00
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
2015-05-15 16:13:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type certUsage int
|
|
|
|
|
|
|
|
const (
|
|
|
|
serverUsage certUsage = 1 << iota
|
|
|
|
clientUsage
|
|
|
|
codeSigningUsage
|
2015-08-29 13:03:02 +00:00
|
|
|
emailProtectionUsage
|
|
|
|
caUsage
|
2015-05-15 16:13:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type certCreationBundle struct {
|
2015-08-29 13:03:02 +00:00
|
|
|
CAType string
|
2015-06-17 16:43:36 +00:00
|
|
|
CommonNames []string
|
2015-08-29 13:03:02 +00:00
|
|
|
PKIAddress string
|
2015-06-17 16:43:36 +00:00
|
|
|
IPSANs []net.IP
|
|
|
|
KeyType string
|
|
|
|
KeyBits int
|
2015-08-29 13:03:02 +00:00
|
|
|
SigningBundle *certutil.ParsedCertBundle
|
2015-08-27 19:24:37 +00:00
|
|
|
TTL time.Duration
|
2015-06-17 16:43:36 +00:00
|
|
|
Usage certUsage
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
func fetchCAInfo(req *logical.Request) (*certutil.ParsedCertBundle, error) {
|
|
|
|
bundleEntry, err := req.Storage.Get("config/ca_bundle")
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-06-19 16:48:18 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("Unable to fetch local CA certificate/key: %s", err)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
2015-06-19 16:48:18 +00:00
|
|
|
if bundleEntry == nil {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf("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 {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("Unable to decode local CA certificate/key: %s", 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 {
|
2015-06-19 16:48:18 +00:00
|
|
|
return nil, certutil.InternalError{Err: err.Error()}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
if parsedBundle.Certificate == nil {
|
|
|
|
return nil, certutil.InternalError{Err: "Stored CA information not able to be parsed"}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
return parsedBundle, 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.
|
|
|
|
func fetchCertBySerial(req *logical.Request, prefix, serial string) (*logical.StorageEntry, error) {
|
2015-05-15 16:13:05 +00:00
|
|
|
var path string
|
2015-06-19 16:48:18 +00:00
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
switch {
|
|
|
|
case serial == "ca":
|
|
|
|
path = "ca"
|
|
|
|
case serial == "crl":
|
|
|
|
path = "crl"
|
|
|
|
case strings.HasPrefix(prefix, "revoked/"):
|
|
|
|
path = "revoked/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
|
|
|
|
default:
|
|
|
|
path = "certs/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
|
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
certEntry, err := req.Storage.Get(path)
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil || certEntry == nil {
|
2015-06-19 16:48:18 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("Certificate with serial number %s not found", serial)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
if certEntry.Value == nil || len(certEntry.Value) == 0 {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("Returned certificate bytes for serial %s were empty", serial)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
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.
|
2015-05-15 16:13:05 +00:00
|
|
|
func validateCommonNames(req *logical.Request, commonNames []string, role *roleEntry) (string, error) {
|
|
|
|
hostnameRegex, err := regexp.Compile(`^(([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])$`)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Error compiling hostname regex: %s", err)
|
|
|
|
}
|
|
|
|
subdomainRegex, err := regexp.Compile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))*$`)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Error compiling subdomain regex: %s", err)
|
|
|
|
}
|
|
|
|
for _, name := range commonNames {
|
|
|
|
if role.AllowLocalhost && name == "localhost" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
sanitizedName := name
|
|
|
|
isWildcard := false
|
|
|
|
if strings.HasPrefix(name, "*.") {
|
|
|
|
sanitizedName = name[2:]
|
|
|
|
isWildcard = true
|
|
|
|
}
|
2015-08-20 21:33:37 +00:00
|
|
|
|
|
|
|
if role.EnforceHostnames {
|
|
|
|
if !hostnameRegex.MatchString(sanitizedName) {
|
|
|
|
return name, nil
|
|
|
|
}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
2015-08-20 21:33:37 +00:00
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
if role.AllowAnyName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if role.AllowTokenDisplayName {
|
|
|
|
if name == req.DisplayName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if role.AllowSubdomains {
|
|
|
|
if strings.HasSuffix(name, "."+req.DisplayName) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(role.AllowedBaseDomain) != 0 {
|
2015-10-02 16:22:02 +00:00
|
|
|
if name == role.AllowedBaseDomain && role.AllowBaseDomain {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
if strings.HasSuffix(name, "."+role.AllowedBaseDomain) {
|
|
|
|
if role.AllowSubdomains {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if subdomainRegex.MatchString(strings.TrimSuffix(name, "."+role.AllowedBaseDomain)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if isWildcard && role.AllowedBaseDomain == sanitizedName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
func generateCert(b *backend,
|
|
|
|
role *roleEntry,
|
|
|
|
signingBundle *certutil.ParsedCertBundle,
|
2015-09-29 23:13:54 +00:00
|
|
|
isCA bool,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certutil.ParsedCertBundle, error) {
|
|
|
|
|
2015-09-29 23:13:54 +00:00
|
|
|
creationBundle, err := generateCreationBundle(b, role, signingBundle, isCA, req, data)
|
2015-08-29 13:03:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedBundle, err := createCertificate(creationBundle)
|
|
|
|
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.
|
2015-08-29 13:03:02 +00:00
|
|
|
func generateCSR(b *backend,
|
|
|
|
role *roleEntry,
|
|
|
|
signingBundle *certutil.ParsedCertBundle,
|
2015-09-29 23:13:54 +00:00
|
|
|
isCA bool,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certutil.ParsedCSRBundle, error) {
|
|
|
|
|
2015-10-02 15:55:30 +00:00
|
|
|
creationBundle := &certCreationBundle{
|
|
|
|
CommonNames: []string{
|
|
|
|
fmt.Sprintf("intcsr-%s-%s", strings.TrimSuffix(req.MountPoint, "/"), uuid.GenerateUUID()),
|
|
|
|
},
|
|
|
|
KeyType: "rsa",
|
|
|
|
KeyBits: role.KeyBits,
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parsedBundle, err := createCSR(creationBundle)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return parsedBundle, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func signCert(b *backend,
|
|
|
|
role *roleEntry,
|
|
|
|
signingBundle *certutil.ParsedCertBundle,
|
2015-09-29 23:13:54 +00:00
|
|
|
isCA bool,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certutil.ParsedCertBundle, error) {
|
|
|
|
|
2015-09-30 01:48:31 +00:00
|
|
|
csrString := req.Data["csr"].(string)
|
|
|
|
if csrString == "" {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"\"csr\" is empty")}
|
|
|
|
}
|
|
|
|
|
|
|
|
pemBytes := []byte(csrString)
|
|
|
|
pemBlock, pemBytes := pem.Decode(pemBytes)
|
|
|
|
if pemBlock == nil {
|
|
|
|
return nil, certutil.UserError{Err: "csr contains no data"}
|
|
|
|
}
|
|
|
|
csr, err := x509.ParseCertificateRequest(pemBlock.Bytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.UserError{Err: "certificate request could not be parsed"}
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:13:54 +00:00
|
|
|
creationBundle, err := generateCreationBundle(b, role, signingBundle, isCA, req, data)
|
2015-08-29 13:03:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedBundle, err := signCertificate(creationBundle, csr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return parsedBundle, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateCreationBundle(b *backend,
|
|
|
|
role *roleEntry,
|
|
|
|
signingBundle *certutil.ParsedCertBundle,
|
2015-09-29 23:13:54 +00:00
|
|
|
isCA bool,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certCreationBundle, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Get the common name(s)
|
2015-10-02 15:55:30 +00:00
|
|
|
cn := data.Get("common_name").(string)
|
|
|
|
if len(cn) == 0 {
|
|
|
|
return nil, certutil.UserError{Err: "The common_name field is required"}
|
2015-09-29 23:13:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
commonNames := []string{cn}
|
|
|
|
cnAltInt, ok := data.GetOk("alt_names")
|
|
|
|
if ok {
|
|
|
|
cnAlt := cnAltInt.(string)
|
|
|
|
if len(cnAlt) != 0 {
|
|
|
|
for _, v := range strings.Split(cnAlt, ",") {
|
|
|
|
commonNames = append(commonNames, v)
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get any IP SANs
|
|
|
|
ipSANs := []net.IP{}
|
2015-09-29 23:13:54 +00:00
|
|
|
ipAltInt, ok := data.GetOk("ip_sans")
|
|
|
|
if ok {
|
|
|
|
ipAlt := ipAltInt.(string)
|
|
|
|
if len(ipAlt) != 0 {
|
|
|
|
if !role.AllowIPSANs {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
2015-09-29 23:13:54 +00:00
|
|
|
"IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)}
|
|
|
|
}
|
|
|
|
for _, v := range strings.Split(ipAlt, ",") {
|
|
|
|
parsedIP := net.ParseIP(v)
|
|
|
|
if parsedIP == nil {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"the value '%s' is not a valid IP address", v)}
|
|
|
|
}
|
|
|
|
ipSANs = append(ipSANs, parsedIP)
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:13:54 +00:00
|
|
|
var ttlField string
|
|
|
|
ttlFieldInt, ok := data.GetOk("ttl")
|
|
|
|
if !ok {
|
2015-08-29 13:03:02 +00:00
|
|
|
ttlField = role.TTL
|
2015-09-29 23:13:54 +00:00
|
|
|
} else {
|
|
|
|
ttlField = ttlFieldInt.(string)
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var ttl time.Duration
|
|
|
|
if len(ttlField) == 0 {
|
|
|
|
ttl = b.System().DefaultLeaseTTL()
|
|
|
|
} else {
|
|
|
|
ttl, err = time.ParseDuration(ttlField)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"invalid requested ttl: %s", err)}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var maxTTL time.Duration
|
|
|
|
if len(role.MaxTTL) == 0 {
|
|
|
|
maxTTL = b.System().MaxLeaseTTL()
|
|
|
|
} else {
|
|
|
|
maxTTL, err = time.ParseDuration(role.MaxTTL)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"invalid ttl: %s", err)}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ttl > maxTTL {
|
|
|
|
// Don't error if they were using system defaults, only error if
|
|
|
|
// they specifically chose a bad TTL
|
|
|
|
if len(ttlField) == 0 {
|
|
|
|
ttl = maxTTL
|
|
|
|
} else {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"ttl is larger than maximum allowed by this role")}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
badName, err := validateCommonNames(req, commonNames, role)
|
|
|
|
if len(badName) != 0 {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"name %s not allowed by this role", badName)}
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf(
|
|
|
|
"error validating name %s: %s", badName, err)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if signingBundle != nil &&
|
|
|
|
time.Now().Add(ttl).After(signingBundle.Certificate.NotAfter) {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"cannot satisfy request, as TTL is beyond the expiration of the CA certificate")}
|
|
|
|
}
|
|
|
|
|
|
|
|
var usage certUsage
|
|
|
|
if role.ServerFlag {
|
|
|
|
usage = usage | serverUsage
|
|
|
|
}
|
|
|
|
if role.ClientFlag {
|
|
|
|
usage = usage | clientUsage
|
|
|
|
}
|
|
|
|
if role.CodeSigningFlag {
|
|
|
|
usage = usage | codeSigningUsage
|
|
|
|
}
|
2015-10-02 15:55:30 +00:00
|
|
|
if role.EmailProtectionFlag {
|
|
|
|
usage = usage | emailProtectionUsage
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
|
|
|
|
creationBundle := &certCreationBundle{
|
|
|
|
CommonNames: commonNames,
|
|
|
|
IPSANs: ipSANs,
|
|
|
|
KeyType: role.KeyType,
|
|
|
|
KeyBits: role.KeyBits,
|
|
|
|
SigningBundle: signingBundle,
|
|
|
|
TTL: ttl,
|
|
|
|
Usage: usage,
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:13:54 +00:00
|
|
|
if isCA {
|
|
|
|
if _, ok := req.Data["ca_type"]; ok {
|
|
|
|
creationBundle.CAType = req.Data["ca_type"].(string)
|
|
|
|
}
|
|
|
|
if _, ok := req.Data["pki_address"]; ok {
|
|
|
|
creationBundle.PKIAddress = req.Data["pki_address"].(string)
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return creationBundle, nil
|
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
// Performs the heavy lifting of creating a certificate. Returns
|
|
|
|
// a fully-filled-in ParsedCertBundle.
|
|
|
|
func createCertificate(creationInfo *certCreationBundle) (*certutil.ParsedCertBundle, error) {
|
2015-05-15 16:13:05 +00:00
|
|
|
var clientPrivKey crypto.Signer
|
|
|
|
var err error
|
2015-06-19 16:48:18 +00:00
|
|
|
result := &certutil.ParsedCertBundle{}
|
2015-06-12 01:57:05 +00:00
|
|
|
|
2015-06-17 16:43:36 +00:00
|
|
|
var serialNumber *big.Int
|
|
|
|
serialNumber, err = rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
2015-06-12 01:57:05 +00:00
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error getting random serial number")}
|
2015-06-12 01:57:05 +00:00
|
|
|
}
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
switch creationInfo.KeyType {
|
|
|
|
case "rsa":
|
2015-06-19 16:48:18 +00:00
|
|
|
result.PrivateKeyType = certutil.RSAPrivateKey
|
2015-06-12 01:57:05 +00:00
|
|
|
clientPrivKey, err = rsa.GenerateKey(rand.Reader, creationInfo.KeyBits)
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error generating RSA private key")}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
2015-06-19 16:48:18 +00:00
|
|
|
result.PrivateKey = clientPrivKey
|
|
|
|
result.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(clientPrivKey.(*rsa.PrivateKey))
|
2015-06-05 18:03:57 +00:00
|
|
|
case "ec":
|
2015-06-19 16:48:18 +00:00
|
|
|
result.PrivateKeyType = certutil.ECPrivateKey
|
2015-05-15 16:13:05 +00:00
|
|
|
var curve elliptic.Curve
|
|
|
|
switch creationInfo.KeyBits {
|
|
|
|
case 224:
|
|
|
|
curve = elliptic.P224()
|
|
|
|
case 256:
|
|
|
|
curve = elliptic.P256()
|
|
|
|
case 384:
|
|
|
|
curve = elliptic.P384()
|
|
|
|
case 521:
|
|
|
|
curve = elliptic.P521()
|
|
|
|
default:
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf("unsupported bit length for EC key: %d", creationInfo.KeyBits)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
2015-06-12 01:57:05 +00:00
|
|
|
clientPrivKey, err = ecdsa.GenerateKey(curve, rand.Reader)
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error generating EC private key")}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
2015-06-19 16:48:18 +00:00
|
|
|
result.PrivateKey = clientPrivKey
|
|
|
|
result.PrivateKeyBytes, err = x509.MarshalECPrivateKey(clientPrivKey.(*ecdsa.PrivateKey))
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error marshalling EC private key")}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
default:
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf("unknown key type: %s", creationInfo.KeyType)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
subjKeyID, err := certutil.GetSubjKeyID(result.PrivateKey)
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error getting subject key ID: %s", err)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
subject := pkix.Name{
|
2015-08-29 13:03:02 +00:00
|
|
|
SerialNumber: serialNumber.String(),
|
|
|
|
CommonName: creationInfo.CommonNames[0],
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
certTemplate := &x509.Certificate{
|
|
|
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
2015-06-17 16:43:36 +00:00
|
|
|
SerialNumber: serialNumber,
|
2015-05-15 16:13:05 +00:00
|
|
|
Subject: subject,
|
|
|
|
NotBefore: time.Now(),
|
2015-08-27 19:24:37 +00:00
|
|
|
NotAfter: time.Now().Add(creationInfo.TTL),
|
2015-05-15 16:13:05 +00:00
|
|
|
KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement),
|
|
|
|
BasicConstraintsValid: true,
|
2015-08-29 13:03:02 +00:00
|
|
|
IsCA: false,
|
|
|
|
SubjectKeyId: subjKeyID,
|
|
|
|
DNSNames: creationInfo.CommonNames,
|
|
|
|
IPAddresses: creationInfo.IPSANs,
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if creationInfo.Usage&serverUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
|
|
|
|
}
|
|
|
|
if creationInfo.Usage&clientUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
|
|
|
|
}
|
|
|
|
if creationInfo.Usage&codeSigningUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning)
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
if creationInfo.Usage&emailProtectionUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection)
|
|
|
|
}
|
|
|
|
|
|
|
|
var certBytes []byte
|
|
|
|
if creationInfo.SigningBundle != nil {
|
|
|
|
caCert := creationInfo.SigningBundle.Certificate
|
|
|
|
subject.Country = caCert.Subject.Country
|
|
|
|
subject.Organization = caCert.Subject.Organization
|
|
|
|
subject.OrganizationalUnit = caCert.Subject.OrganizationalUnit
|
|
|
|
subject.Locality = caCert.Subject.Locality
|
|
|
|
subject.Province = caCert.Subject.Province
|
|
|
|
subject.StreetAddress = caCert.Subject.StreetAddress
|
|
|
|
subject.PostalCode = caCert.Subject.PostalCode
|
|
|
|
certTemplate.CRLDistributionPoints = caCert.CRLDistributionPoints
|
|
|
|
certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, clientPrivKey.Public(), creationInfo.SigningBundle.PrivateKey)
|
|
|
|
} else {
|
|
|
|
certTemplate.CRLDistributionPoints = []string{
|
|
|
|
creationInfo.PKIAddress + "/crl",
|
|
|
|
}
|
|
|
|
certTemplate.IssuingCertificateURL = []string{
|
|
|
|
creationInfo.PKIAddress + "/ca",
|
|
|
|
}
|
|
|
|
certTemplate.IsCA = true
|
|
|
|
certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign)
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning)
|
|
|
|
certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, clientPrivKey.Public(), clientPrivKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)}
|
|
|
|
}
|
|
|
|
|
|
|
|
result.CertificateBytes = certBytes
|
|
|
|
result.Certificate, err = x509.ParseCertificate(certBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if creationInfo.SigningBundle != nil {
|
|
|
|
result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes
|
|
|
|
result.IssuingCA = creationInfo.SigningBundle.Certificate
|
|
|
|
} else {
|
|
|
|
result.IssuingCABytes = result.CertificateBytes
|
|
|
|
result.IssuingCA = result.Certificate
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a CSR. This is currently only meant for use when
|
|
|
|
// generating an intermediate certificate.
|
|
|
|
func createCSR(creationInfo *certCreationBundle) (*certutil.ParsedCSRBundle, error) {
|
|
|
|
var clientPrivKey crypto.Signer
|
|
|
|
var err error
|
|
|
|
result := &certutil.ParsedCSRBundle{}
|
|
|
|
|
|
|
|
switch creationInfo.KeyType {
|
|
|
|
case "rsa":
|
|
|
|
result.PrivateKeyType = certutil.RSAPrivateKey
|
|
|
|
clientPrivKey, err = rsa.GenerateKey(rand.Reader, creationInfo.KeyBits)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error generating RSA private key")}
|
|
|
|
}
|
|
|
|
result.PrivateKey = clientPrivKey
|
|
|
|
result.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(clientPrivKey.(*rsa.PrivateKey))
|
|
|
|
default:
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf("unsupported key type for CA generation: %s", creationInfo.KeyType)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Like many root CAs, other information is ignored
|
|
|
|
subject := pkix.Name{
|
|
|
|
CommonName: creationInfo.CommonNames[0],
|
|
|
|
}
|
|
|
|
|
|
|
|
csrTemplate := &x509.CertificateRequest{
|
|
|
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
|
|
|
Subject: subject,
|
|
|
|
}
|
|
|
|
|
|
|
|
csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, result.PrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)}
|
|
|
|
}
|
|
|
|
|
|
|
|
result.CSRBytes = csr
|
|
|
|
result.CSR, err = x509.ParseCertificateRequest(csr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performs the heavy lifting of generating a certificate from a CSR.
|
|
|
|
// Returns a ParsedCertBundle sans private keys.
|
|
|
|
func signCertificate(creationInfo *certCreationBundle,
|
|
|
|
csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, error) {
|
|
|
|
switch {
|
|
|
|
case creationInfo == nil:
|
|
|
|
return nil, certutil.UserError{Err: "nil creation info given to signCertificate"}
|
|
|
|
case creationInfo.SigningBundle == nil:
|
|
|
|
return nil, certutil.UserError{Err: "nil signing bundle given to signCertificate"}
|
|
|
|
case csr == nil:
|
|
|
|
return nil, certutil.UserError{Err: "nil csr given to signCertificate"}
|
|
|
|
case creationInfo.CAType != "" && creationInfo.PKIAddress == "":
|
|
|
|
return nil, certutil.UserError{Err: "ca cert to sign but no PKI address given to signCertificate"}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := csr.CheckSignature()
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.UserError{Err: "request signature invalid"}
|
|
|
|
}
|
|
|
|
|
|
|
|
result := &certutil.ParsedCertBundle{}
|
|
|
|
|
|
|
|
var serialNumber *big.Int
|
|
|
|
serialNumber, err = rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil))
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error getting random serial number")}
|
|
|
|
}
|
|
|
|
|
|
|
|
subject := pkix.Name{
|
|
|
|
SerialNumber: serialNumber.String(),
|
|
|
|
CommonName: creationInfo.CommonNames[0],
|
|
|
|
}
|
|
|
|
|
|
|
|
marshaledKey, err := x509.MarshalPKIXPublicKey(csr.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("error marshalling public key: %s", err)}
|
|
|
|
}
|
|
|
|
subjKeyID := sha1.Sum(marshaledKey)
|
|
|
|
|
|
|
|
certTemplate := &x509.Certificate{
|
|
|
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
|
|
|
SerialNumber: serialNumber,
|
|
|
|
Subject: subject,
|
|
|
|
NotBefore: time.Now(),
|
|
|
|
NotAfter: time.Now().Add(creationInfo.TTL),
|
|
|
|
KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement),
|
|
|
|
BasicConstraintsValid: true,
|
|
|
|
IsCA: false,
|
|
|
|
SubjectKeyId: subjKeyID[:],
|
|
|
|
DNSNames: creationInfo.CommonNames,
|
|
|
|
IPAddresses: creationInfo.IPSANs,
|
|
|
|
}
|
|
|
|
|
|
|
|
if creationInfo.Usage&serverUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
|
|
|
|
}
|
|
|
|
if creationInfo.Usage&clientUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
|
|
|
|
}
|
|
|
|
if creationInfo.Usage&codeSigningUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning)
|
|
|
|
}
|
|
|
|
if creationInfo.Usage&emailProtectionUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection)
|
|
|
|
}
|
|
|
|
|
|
|
|
var certBytes []byte
|
|
|
|
caCert := creationInfo.SigningBundle.Certificate
|
|
|
|
subject.Country = caCert.Subject.Country
|
|
|
|
subject.Organization = caCert.Subject.Organization
|
|
|
|
subject.OrganizationalUnit = caCert.Subject.OrganizationalUnit
|
|
|
|
subject.Locality = caCert.Subject.Locality
|
|
|
|
subject.Province = caCert.Subject.Province
|
|
|
|
subject.StreetAddress = caCert.Subject.StreetAddress
|
|
|
|
subject.PostalCode = caCert.Subject.PostalCode
|
|
|
|
|
|
|
|
certTemplate.IssuingCertificateURL = caCert.IssuingCertificateURL
|
|
|
|
|
|
|
|
if creationInfo.CAType != "" && creationInfo.PKIAddress != "" {
|
|
|
|
certTemplate.CRLDistributionPoints = []string{
|
|
|
|
creationInfo.PKIAddress + "/crl",
|
|
|
|
}
|
|
|
|
certTemplate.IsCA = true
|
|
|
|
certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign)
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning)
|
|
|
|
} else {
|
|
|
|
certTemplate.CRLDistributionPoints = caCert.CRLDistributionPoints
|
|
|
|
}
|
|
|
|
|
|
|
|
certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, csr.PublicKey, creationInfo.SigningBundle.PrivateKey)
|
2015-05-15 16:13:05 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
result.CertificateBytes = certBytes
|
|
|
|
result.Certificate, err = x509.ParseCertificate(certBytes)
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes
|
|
|
|
result.IssuingCA = creationInfo.SigningBundle.Certificate
|
2015-05-15 16:13:05 +00:00
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
return result, nil
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|