2015-05-15 16:13:05 +00:00
|
|
|
package pki
|
|
|
|
|
|
|
|
import (
|
2015-06-12 01:57:05 +00:00
|
|
|
"crypto/rand"
|
2015-08-29 13:03:02 +00:00
|
|
|
"crypto/sha1"
|
2015-05-15 16:13:05 +00:00
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
2015-11-18 15:16:09 +00:00
|
|
|
"encoding/asn1"
|
2015-09-30 01:48:31 +00:00
|
|
|
"encoding/pem"
|
2015-05-15 16:13:05 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
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
|
|
|
)
|
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
type creationBundle struct {
|
2015-10-02 18:27:30 +00:00
|
|
|
CommonName string
|
|
|
|
DNSNames []string
|
|
|
|
EmailAddresses []string
|
|
|
|
IPAddresses []net.IP
|
2015-10-09 17:45:17 +00:00
|
|
|
IsCA bool
|
2015-10-02 18:27:30 +00:00
|
|
|
KeyType string
|
|
|
|
KeyBits int
|
2015-10-09 17:45:17 +00:00
|
|
|
SigningBundle *caInfoBundle
|
2015-10-02 18:27:30 +00:00
|
|
|
TTL time.Duration
|
|
|
|
Usage certUsage
|
2015-10-09 17:45:17 +00:00
|
|
|
|
2015-10-09 18:04:20 +00:00
|
|
|
// Only used when signing a CA cert
|
2015-10-13 00:46:58 +00:00
|
|
|
UseCSRValues bool
|
2015-10-14 01:13:40 +00:00
|
|
|
|
|
|
|
// URLs to encode into the certificate
|
|
|
|
URLs *urlEntries
|
2015-10-14 19:53:57 +00:00
|
|
|
|
|
|
|
// The maximum path length to encode
|
|
|
|
MaxPathLength int
|
2015-10-09 17:45:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type caInfoBundle struct {
|
|
|
|
certutil.ParsedCertBundle
|
2015-11-12 16:24:32 +00:00
|
|
|
URLs *urlEntries
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
var (
|
|
|
|
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])$`)
|
|
|
|
oidExtensionBasicConstraints = []int{2, 5, 29, 19}
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateKeyTypeLength(keyType string, keyBits int) *logical.Response {
|
|
|
|
switch keyType {
|
|
|
|
case "rsa":
|
|
|
|
switch keyBits {
|
|
|
|
case 1024:
|
|
|
|
case 2048:
|
|
|
|
case 4096:
|
|
|
|
case 8192:
|
|
|
|
default:
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"unsupported bit length for RSA key: %d", keyBits))
|
|
|
|
}
|
|
|
|
case "ec":
|
|
|
|
switch keyBits {
|
|
|
|
case 224:
|
|
|
|
case 256:
|
|
|
|
case 384:
|
|
|
|
case 521:
|
|
|
|
default:
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"unsupported bit length for EC key: %d", keyBits))
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"unknown key type %s", keyType))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
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
|
2015-10-09 17:45:17 +00:00
|
|
|
func fetchCAInfo(req *logical.Request) (*caInfoBundle, error) {
|
2015-06-19 16:48:18 +00:00
|
|
|
bundleEntry, err := req.Storage.Get("config/ca_bundle")
|
2015-05-15 16:13:05 +00:00
|
|
|
if err != nil {
|
2015-11-12 16:24:32 +00:00
|
|
|
return nil, certutil.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 {
|
2015-11-12 16:24:32 +00:00
|
|
|
return nil, certutil.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 {
|
2015-11-12 16:24:32 +00:00
|
|
|
return nil, certutil.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 {
|
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 {
|
2015-11-12 16:24:32 +00:00
|
|
|
return nil, certutil.InternalError{Err: "stored CA information not able to be parsed"}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-11-12 16:24:32 +00:00
|
|
|
caInfo := &caInfoBundle{*parsedBundle, nil}
|
2015-10-09 17:45:17 +00:00
|
|
|
|
2015-10-14 01:13:40 +00:00
|
|
|
entries, err := getURLs(req)
|
2015-10-09 17:45:17 +00:00
|
|
|
if err != nil {
|
2015-11-12 16:24:32 +00:00
|
|
|
return nil, certutil.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 {
|
|
|
|
entries = &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.
|
|
|
|
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-11-12 16:24:32 +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 {
|
2015-11-12 16:24:32 +00:00
|
|
|
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-10-02 18:27:30 +00:00
|
|
|
func validateNames(req *logical.Request, names []string, role *roleEntry) (string, error) {
|
|
|
|
for _, name := range names {
|
2015-05-15 16:13:05 +00:00
|
|
|
sanitizedName := name
|
2015-10-02 19:47:45 +00:00
|
|
|
emailDomain := name
|
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.
|
2015-10-02 18:27:30 +00:00
|
|
|
if strings.Contains(name, "@") {
|
|
|
|
splitEmail := strings.Split(name, "@")
|
|
|
|
if len(splitEmail) != 2 {
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2015-08-20 21:33:37 +00:00
|
|
|
if role.EnforceHostnames {
|
|
|
|
if !hostnameRegex.MatchString(sanitizedName) {
|
|
|
|
return name, nil
|
|
|
|
}
|
2015-10-14 15:26:56 +00:00
|
|
|
}
|
|
|
|
|
2015-11-23 19:15:32 +00:00
|
|
|
// Self-explanatory
|
2015-10-14 15:26:56 +00:00
|
|
|
if role.AllowAnyName {
|
|
|
|
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
|
|
|
//
|
|
|
|
// 2) If there is a perfect match on either the name itself or it's an
|
|
|
|
// 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
|
|
|
|
|
2015-10-02 19:47:45 +00:00
|
|
|
if role.AllowLocalhost {
|
2015-11-23 19:15:32 +00:00
|
|
|
if name == "localhost" ||
|
|
|
|
name == "localdomain" ||
|
|
|
|
(isEmail && emailDomain == "localhost") ||
|
|
|
|
(isEmail && emailDomain == "localdomain") {
|
2015-10-02 19:47:45 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
if role.AllowTokenDisplayName {
|
2015-11-23 19:15:32 +00:00
|
|
|
if name == req.DisplayName {
|
2015-05-15 16:13:05 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if 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
|
|
|
|
if strings.Contains(req.DisplayName, "@") {
|
|
|
|
splitDisplay := strings.Split(req.DisplayName, "@")
|
|
|
|
if len(splitDisplay) == 2 {
|
|
|
|
// Compare the sanitized name against the hostname
|
|
|
|
// portion of the email address in the roken
|
|
|
|
// display name
|
|
|
|
if strings.HasSuffix(sanitizedName, "."+splitDisplay[1]) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2015-10-02 18:27:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-23 19:15:32 +00:00
|
|
|
if strings.HasSuffix(sanitizedName, "."+req.DisplayName) ||
|
2015-12-01 04:49:11 +00:00
|
|
|
(isWildcard && sanitizedName == req.DisplayName) {
|
2015-05-15 16:13:05 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-01 04:49:11 +00:00
|
|
|
if role.AllowedDomains != "" {
|
|
|
|
valid := false
|
|
|
|
for _, currDomain := range strings.Split(role.AllowedDomains, ",") {
|
|
|
|
// 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
|
|
|
|
|
|
|
// First, allow an exact match of the base domain if that role flag
|
|
|
|
// is enabled
|
|
|
|
if role.AllowBareDomains &&
|
|
|
|
(name == currDomain ||
|
|
|
|
(isEmail && emailDomain == currDomain)) {
|
|
|
|
valid = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if role.AllowSubdomains {
|
|
|
|
if strings.HasSuffix(sanitizedName, "."+currDomain) ||
|
|
|
|
(isWildcard && sanitizedName == currDomain) {
|
|
|
|
valid = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if valid {
|
|
|
|
continue
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-01 04:49:11 +00:00
|
|
|
//panic(fmt.Sprintf("\nName is %s\nRole is\n%#v\n", name, role))
|
2015-05-15 16:13:05 +00:00
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
func generateCert(b *backend,
|
|
|
|
role *roleEntry,
|
2015-10-09 17:45:17 +00:00
|
|
|
signingBundle *caInfoBundle,
|
|
|
|
isCA bool,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certutil.ParsedCertBundle, error) {
|
|
|
|
|
2015-10-09 17:45:17 +00:00
|
|
|
creationBundle, err := generateCreationBundle(b, role, signingBundle, nil, req, data)
|
2015-08-29 13:03:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-10-09 17:45:17 +00:00
|
|
|
if isCA {
|
|
|
|
creationBundle.IsCA = isCA
|
2015-11-16 16:42:06 +00:00
|
|
|
|
|
|
|
if signingBundle == nil {
|
|
|
|
// Generating a self-signed root certificate
|
|
|
|
entries, err := getURLs(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)}
|
|
|
|
}
|
|
|
|
if entries == nil {
|
|
|
|
entries = &urlEntries{
|
|
|
|
IssuingCertificates: []string{},
|
|
|
|
CRLDistributionPoints: []string{},
|
|
|
|
OCSPServers: []string{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
creationBundle.URLs = entries
|
|
|
|
|
|
|
|
if role.MaxPathLength == nil {
|
|
|
|
creationBundle.MaxPathLength = -1
|
|
|
|
} else {
|
|
|
|
creationBundle.MaxPathLength = *role.MaxPathLength
|
|
|
|
}
|
|
|
|
}
|
2015-10-09 17:45:17 +00:00
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
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-11-16 16:42:06 +00:00
|
|
|
func generateIntermediateCSR(b *backend,
|
2015-08-29 13:03:02 +00:00
|
|
|
role *roleEntry,
|
2015-10-09 17:45:17 +00:00
|
|
|
signingBundle *caInfoBundle,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certutil.ParsedCSRBundle, error) {
|
|
|
|
|
2015-11-12 16:24:32 +00:00
|
|
|
creationBundle, err := generateCreationBundle(b, role, signingBundle, nil, req, data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
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,
|
2015-10-09 17:45:17 +00:00
|
|
|
signingBundle *caInfoBundle,
|
|
|
|
isCA bool,
|
2015-10-13 00:46:58 +00:00
|
|
|
useCSRValues bool,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
|
|
|
data *framework.FieldData) (*certutil.ParsedCertBundle, error) {
|
|
|
|
|
2015-11-12 16:24:32 +00:00
|
|
|
csrString := data.Get("csr").(string)
|
2015-09-30 01:48:31 +00:00
|
|
|
if csrString == "" {
|
2015-11-12 16:24:32 +00:00
|
|
|
return nil, certutil.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 {
|
|
|
|
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-10-09 17:45:17 +00:00
|
|
|
creationBundle, err := generateCreationBundle(b, role, signingBundle, csr, req, data)
|
2015-08-29 13:03:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-12-14 19:23:51 +00:00
|
|
|
if useCSRValues && !isCA {
|
|
|
|
return nil, certutil.UserError{Err: "cannot use CSR values with a non-CA certificate"}
|
|
|
|
}
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
creationBundle.IsCA = isCA
|
|
|
|
creationBundle.UseCSRValues = useCSRValues
|
2015-10-09 17:45:17 +00:00
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
parsedBundle, err := signCertificate(creationBundle, csr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return parsedBundle, nil
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// generateCreationBundle is a shared function that reads parameters supplied
|
|
|
|
// from the various endpoints and generates a creationBundle with the
|
|
|
|
// parameters that can be used to issue or sign
|
2015-08-29 13:03:02 +00:00
|
|
|
func generateCreationBundle(b *backend,
|
|
|
|
role *roleEntry,
|
2015-10-09 17:45:17 +00:00
|
|
|
signingBundle *caInfoBundle,
|
|
|
|
csr *x509.CertificateRequest,
|
2015-08-29 13:03:02 +00:00
|
|
|
req *logical.Request,
|
2015-11-16 21:32:49 +00:00
|
|
|
data *framework.FieldData) (*creationBundle, error) {
|
2015-08-29 13:03:02 +00:00
|
|
|
var err error
|
2015-11-19 21:51:27 +00:00
|
|
|
var ok bool
|
2015-08-29 13:03:02 +00:00
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// Get the common name
|
2015-11-16 15:10:03 +00:00
|
|
|
var cn string
|
2015-11-19 21:51:27 +00:00
|
|
|
{
|
|
|
|
if csr != nil {
|
|
|
|
if role.UseCSRCommonName {
|
|
|
|
cn = csr.Subject.CommonName
|
|
|
|
}
|
2015-11-16 15:10:03 +00:00
|
|
|
}
|
|
|
|
if cn == "" {
|
2015-11-19 21:51:27 +00:00
|
|
|
cn = data.Get("common_name").(string)
|
|
|
|
if cn == "" {
|
|
|
|
return nil, certutil.UserError{Err: `the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true`}
|
|
|
|
}
|
2015-10-09 17:45:17 +00:00
|
|
|
}
|
2015-09-29 23:13:54 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// Read in alternate names -- DNS and email addresses
|
2015-10-02 18:27:30 +00:00
|
|
|
dnsNames := []string{}
|
|
|
|
emailAddresses := []string{}
|
2015-11-19 21:51:27 +00:00
|
|
|
{
|
|
|
|
if strings.Contains(cn, "@") {
|
2015-11-23 19:15:32 +00:00
|
|
|
// 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
|
2015-11-19 21:51:27 +00:00
|
|
|
emailAddresses = append(emailAddresses, cn)
|
|
|
|
} else {
|
|
|
|
dnsNames = append(dnsNames, cn)
|
|
|
|
}
|
|
|
|
cnAltInt, ok := data.GetOk("alt_names")
|
|
|
|
if ok {
|
|
|
|
cnAlt := cnAltInt.(string)
|
|
|
|
if len(cnAlt) != 0 {
|
|
|
|
for _, v := range strings.Split(cnAlt, ",") {
|
|
|
|
if strings.Contains(v, "@") {
|
|
|
|
emailAddresses = append(emailAddresses, cn)
|
|
|
|
} else {
|
|
|
|
dnsNames = append(dnsNames, v)
|
|
|
|
}
|
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
|
|
|
|
|
|
|
// Check for bad email and/or DNS names
|
|
|
|
badName, err := validateNames(req, dnsNames, 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)}
|
|
|
|
}
|
|
|
|
|
|
|
|
badName, err = validateNames(req, emailAddresses, role)
|
|
|
|
if len(badName) != 0 {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"email %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)}
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
var ipAltInt interface{}
|
|
|
|
{
|
|
|
|
ipAltInt, ok = data.GetOk("ip_sans")
|
|
|
|
if ok {
|
|
|
|
ipAlt := ipAltInt.(string)
|
|
|
|
if len(ipAlt) != 0 {
|
|
|
|
if !role.AllowIPSANs {
|
2015-09-29 23:13:54 +00:00
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
2015-11-19 21:51:27 +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)}
|
|
|
|
}
|
|
|
|
ipAddresses = append(ipAddresses, parsedIP)
|
2015-09-29 23:13:54 +00:00
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// Get the TTL and very it against the max allowed
|
2015-09-29 23:13:54 +00:00
|
|
|
var ttlField string
|
2015-08-29 13:03:02 +00:00
|
|
|
var ttl time.Duration
|
|
|
|
var maxTTL time.Duration
|
2015-11-19 21:51:27 +00:00
|
|
|
var ttlFieldInt interface{}
|
|
|
|
{
|
|
|
|
ttlFieldInt, ok = data.GetOk("ttl")
|
|
|
|
if !ok {
|
|
|
|
ttlField = role.TTL
|
|
|
|
} else {
|
|
|
|
ttlField = ttlFieldInt.(string)
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(ttlField) == 0 {
|
2015-11-19 21:51:27 +00:00
|
|
|
ttl = b.System().DefaultLeaseTTL()
|
2015-08-29 13:03:02 +00:00
|
|
|
} else {
|
2015-11-19 21:51:27 +00:00
|
|
|
ttl, err = time.ParseDuration(ttlField)
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.UserError{Err: fmt.Sprintf(
|
|
|
|
"invalid requested ttl: %s", err)}
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
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)}
|
|
|
|
}
|
|
|
|
}
|
2015-10-03 01:02:16 +00:00
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
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 (%d)", maxTTL/time.Second)}
|
|
|
|
}
|
|
|
|
}
|
2015-10-02 18:27:30 +00:00
|
|
|
|
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
|
|
|
|
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")}
|
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// Build up usages
|
2015-08-29 13:03:02 +00:00
|
|
|
var usage certUsage
|
2015-11-19 21:51:27 +00:00
|
|
|
{
|
|
|
|
if role.ServerFlag {
|
|
|
|
usage = usage | serverUsage
|
|
|
|
}
|
|
|
|
if role.ClientFlag {
|
|
|
|
usage = usage | clientUsage
|
|
|
|
}
|
|
|
|
if role.CodeSigningFlag {
|
|
|
|
usage = usage | codeSigningUsage
|
|
|
|
}
|
|
|
|
if role.EmailProtectionFlag {
|
|
|
|
usage = usage | emailProtectionUsage
|
|
|
|
}
|
2015-10-02 15:55:30 +00:00
|
|
|
}
|
2015-08-29 13:03:02 +00:00
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
creationBundle := &creationBundle{
|
2015-10-02 18:27:30 +00:00
|
|
|
CommonName: cn,
|
|
|
|
DNSNames: dnsNames,
|
|
|
|
EmailAddresses: emailAddresses,
|
|
|
|
IPAddresses: ipAddresses,
|
|
|
|
KeyType: role.KeyType,
|
|
|
|
KeyBits: role.KeyBits,
|
|
|
|
SigningBundle: signingBundle,
|
|
|
|
TTL: ttl,
|
|
|
|
Usage: usage,
|
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
|
2015-11-16 16:42:06 +00:00
|
|
|
if signingBundle == nil {
|
|
|
|
return creationBundle, nil
|
|
|
|
}
|
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
|
2015-11-16 16:42:06 +00:00
|
|
|
creationBundle.URLs = signingBundle.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
|
2015-11-16 16:42:06 +00:00
|
|
|
if role.MaxPathLength != nil {
|
|
|
|
creationBundle.MaxPathLength = *role.MaxPathLength
|
|
|
|
} else {
|
|
|
|
switch {
|
|
|
|
case signingBundle.Certificate.MaxPathLen < 0:
|
2015-10-14 19:53:57 +00:00
|
|
|
creationBundle.MaxPathLength = -1
|
2015-11-16 16:42:06 +00:00
|
|
|
case signingBundle.Certificate.MaxPathLen == 0 &&
|
|
|
|
signingBundle.Certificate.MaxPathLenZero:
|
|
|
|
// The signing function will ensure that we do not issue a CA cert
|
|
|
|
creationBundle.MaxPathLength = 0
|
|
|
|
default:
|
|
|
|
// If this takes it to zero, we handle this case later if
|
|
|
|
// necessary
|
|
|
|
creationBundle.MaxPathLength = signingBundle.Certificate.MaxPathLen - 1
|
2015-10-14 19:53:57 +00:00
|
|
|
}
|
2015-10-14 01:13:40 +00:00
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
return creationBundle, nil
|
|
|
|
}
|
|
|
|
|
2015-12-14 19:23:51 +00:00
|
|
|
// addKeyUsages adds approrpiate key usages to the template given the creation
|
|
|
|
// information
|
|
|
|
func addKeyUsages(creationInfo *creationBundle, certTemplate *x509.Certificate) {
|
2016-01-29 15:26:35 +00:00
|
|
|
if creationInfo.IsCA {
|
|
|
|
certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign)
|
|
|
|
return
|
|
|
|
}
|
2015-12-14 19:23:51 +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)
|
|
|
|
}
|
|
|
|
if creationInfo.Usage&emailProtectionUsage != 0 {
|
|
|
|
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-19 16:48:18 +00:00
|
|
|
// Performs the heavy lifting of creating a certificate. Returns
|
|
|
|
// a fully-filled-in ParsedCertBundle.
|
2015-11-16 21:32:49 +00:00
|
|
|
func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle, error) {
|
2015-05-15 16:13:05 +00:00
|
|
|
var err error
|
2015-06-19 16:48:18 +00:00
|
|
|
result := &certutil.ParsedCertBundle{}
|
2015-06-12 01:57:05 +00:00
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
serialNumber, err := certutil.GenerateSerialNumber()
|
2015-06-12 01:57:05 +00:00
|
|
|
if err != nil {
|
2015-11-16 21:32:49 +00:00
|
|
|
return nil, err
|
2015-06-12 01:57:05 +00:00
|
|
|
}
|
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
if err := certutil.GeneratePrivateKey(creationInfo.KeyType,
|
|
|
|
creationInfo.KeyBits,
|
2015-11-19 19:15:36 +00:00
|
|
|
result); err != nil {
|
2015-11-16 21:32:49 +00:00
|
|
|
return nil, err
|
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-10-12 14:16:13 +00:00
|
|
|
CommonName: creationInfo.CommonName,
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
certTemplate := &x509.Certificate{
|
2015-11-18 15:16:09 +00:00
|
|
|
SerialNumber: serialNumber,
|
|
|
|
Subject: subject,
|
2016-02-07 18:54:46 +00:00
|
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
2015-11-18 15:16:09 +00:00
|
|
|
NotAfter: time.Now().Add(creationInfo.TTL),
|
2015-10-02 18:27:30 +00:00
|
|
|
IsCA: false,
|
|
|
|
SubjectKeyId: subjKeyID,
|
|
|
|
DNSNames: creationInfo.DNSNames,
|
|
|
|
EmailAddresses: creationInfo.EmailAddresses,
|
|
|
|
IPAddresses: creationInfo.IPAddresses,
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-12-14 19:23:51 +00:00
|
|
|
// Add this before calling addKeyUsages
|
|
|
|
if creationInfo.SigningBundle == nil {
|
|
|
|
certTemplate.IsCA = true
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
2015-12-14 19:23:51 +00:00
|
|
|
addKeyUsages(creationInfo, certTemplate)
|
|
|
|
|
2015-10-14 18:48:51 +00:00
|
|
|
certTemplate.IssuingCertificateURL = creationInfo.URLs.IssuingCertificates
|
2015-10-14 01:13:40 +00:00
|
|
|
certTemplate.CRLDistributionPoints = creationInfo.URLs.CRLDistributionPoints
|
|
|
|
certTemplate.OCSPServer = creationInfo.URLs.OCSPServers
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
var certBytes []byte
|
|
|
|
if creationInfo.SigningBundle != nil {
|
2015-10-05 17:00:45 +00:00
|
|
|
switch creationInfo.SigningBundle.PrivateKeyType {
|
|
|
|
case certutil.RSAPrivateKey:
|
|
|
|
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
|
|
|
|
case certutil.ECPrivateKey:
|
|
|
|
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
|
|
|
|
}
|
2015-10-13 00:46:58 +00:00
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
caCert := creationInfo.SigningBundle.Certificate
|
2015-10-13 00:46:58 +00:00
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, result.PrivateKey.Public(), creationInfo.SigningBundle.PrivateKey)
|
2015-08-29 13:03:02 +00:00
|
|
|
} else {
|
2015-10-09 17:45:17 +00:00
|
|
|
// Creating a self-signed root
|
2015-11-12 16:24:32 +00:00
|
|
|
if creationInfo.MaxPathLength == 0 {
|
|
|
|
certTemplate.MaxPathLen = 0
|
|
|
|
certTemplate.MaxPathLenZero = true
|
|
|
|
} else {
|
|
|
|
certTemplate.MaxPathLen = creationInfo.MaxPathLength
|
|
|
|
}
|
|
|
|
|
2015-10-05 17:00:45 +00:00
|
|
|
switch creationInfo.KeyType {
|
|
|
|
case "rsa":
|
|
|
|
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
|
|
|
|
case "ec":
|
|
|
|
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
|
|
|
|
}
|
2015-10-14 01:13:40 +00:00
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
certTemplate.BasicConstraintsValid = true
|
2015-11-16 21:32:49 +00:00
|
|
|
certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, result.PrivateKey.Public(), result.PrivateKey)
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2015-11-16 21:32:49 +00:00
|
|
|
func createCSR(creationInfo *creationBundle) (*certutil.ParsedCSRBundle, error) {
|
2015-08-29 13:03:02 +00:00
|
|
|
var err error
|
|
|
|
result := &certutil.ParsedCSRBundle{}
|
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
if err := certutil.GeneratePrivateKey(creationInfo.KeyType,
|
|
|
|
creationInfo.KeyBits,
|
2015-11-19 19:15:36 +00:00
|
|
|
result); err != nil {
|
2015-11-16 21:32:49 +00:00
|
|
|
return nil, err
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Like many root CAs, other information is ignored
|
|
|
|
subject := pkix.Name{
|
2015-10-02 18:27:30 +00:00
|
|
|
CommonName: creationInfo.CommonName,
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
csrTemplate := &x509.CertificateRequest{
|
2015-11-12 16:24:32 +00:00
|
|
|
Subject: subject,
|
|
|
|
DNSNames: creationInfo.DNSNames,
|
|
|
|
EmailAddresses: creationInfo.EmailAddresses,
|
|
|
|
IPAddresses: creationInfo.IPAddresses,
|
2015-10-05 17:00:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch creationInfo.KeyType {
|
|
|
|
case "rsa":
|
|
|
|
csrTemplate.SignatureAlgorithm = x509.SHA256WithRSA
|
|
|
|
case "ec":
|
|
|
|
csrTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2015-11-16 21:32:49 +00:00
|
|
|
func signCertificate(creationInfo *creationBundle,
|
2015-08-29 13:03:02 +00:00
|
|
|
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"}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := csr.CheckSignature()
|
|
|
|
if err != nil {
|
|
|
|
return nil, certutil.UserError{Err: "request signature invalid"}
|
|
|
|
}
|
|
|
|
|
|
|
|
result := &certutil.ParsedCertBundle{}
|
|
|
|
|
2015-11-16 21:32:49 +00:00
|
|
|
serialNumber, err := certutil.GenerateSerialNumber()
|
2015-08-29 13:03:02 +00:00
|
|
|
if err != nil {
|
2015-11-16 21:32:49 +00:00
|
|
|
return nil, err
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2015-10-13 00:46:58 +00:00
|
|
|
subject := pkix.Name{
|
|
|
|
CommonName: creationInfo.CommonName,
|
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
certTemplate := &x509.Certificate{
|
2015-11-18 15:16:09 +00:00
|
|
|
SerialNumber: serialNumber,
|
|
|
|
Subject: subject,
|
2016-02-07 18:54:46 +00:00
|
|
|
NotBefore: time.Now().Add(-30 * time.Second),
|
2015-11-18 15:16:09 +00:00
|
|
|
NotAfter: time.Now().Add(creationInfo.TTL),
|
|
|
|
SubjectKeyId: subjKeyID[:],
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
2015-10-05 17:00:45 +00:00
|
|
|
switch creationInfo.SigningBundle.PrivateKeyType {
|
|
|
|
case certutil.RSAPrivateKey:
|
|
|
|
certTemplate.SignatureAlgorithm = x509.SHA256WithRSA
|
|
|
|
case certutil.ECPrivateKey:
|
|
|
|
certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256
|
|
|
|
}
|
|
|
|
|
2015-10-13 00:46:58 +00:00
|
|
|
if creationInfo.UseCSRValues {
|
|
|
|
certTemplate.Subject = csr.Subject
|
|
|
|
|
|
|
|
certTemplate.DNSNames = csr.DNSNames
|
|
|
|
certTemplate.EmailAddresses = csr.EmailAddresses
|
|
|
|
certTemplate.IPAddresses = csr.IPAddresses
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
certTemplate.ExtraExtensions = csr.Extensions
|
2015-10-13 00:46:58 +00:00
|
|
|
} else {
|
|
|
|
certTemplate.DNSNames = creationInfo.DNSNames
|
|
|
|
certTemplate.EmailAddresses = creationInfo.EmailAddresses
|
|
|
|
certTemplate.IPAddresses = creationInfo.IPAddresses
|
2015-08-29 13:03:02 +00:00
|
|
|
}
|
|
|
|
|
2015-12-14 19:23:51 +00:00
|
|
|
addKeyUsages(creationInfo, certTemplate)
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
var certBytes []byte
|
|
|
|
caCert := creationInfo.SigningBundle.Certificate
|
|
|
|
|
2015-10-14 18:48:51 +00:00
|
|
|
certTemplate.IssuingCertificateURL = creationInfo.URLs.IssuingCertificates
|
2015-10-14 01:13:40 +00:00
|
|
|
certTemplate.CRLDistributionPoints = creationInfo.URLs.CRLDistributionPoints
|
|
|
|
certTemplate.OCSPServer = creationInfo.SigningBundle.URLs.OCSPServers
|
2015-10-09 17:45:17 +00:00
|
|
|
|
2015-11-12 16:24:32 +00:00
|
|
|
if creationInfo.IsCA {
|
2015-11-18 15:16:09 +00:00
|
|
|
certTemplate.BasicConstraintsValid = true
|
2015-11-12 16:24:32 +00:00
|
|
|
certTemplate.IsCA = true
|
|
|
|
|
2015-11-16 15:42:45 +00:00
|
|
|
if creationInfo.SigningBundle.Certificate.MaxPathLen == 0 &&
|
|
|
|
creationInfo.SigningBundle.Certificate.MaxPathLenZero {
|
|
|
|
return nil, certutil.UserError{Err: "signing certificate has a max path length of zero, and cannot issue further CA certificates"}
|
|
|
|
}
|
|
|
|
|
2015-10-14 19:53:57 +00:00
|
|
|
certTemplate.MaxPathLen = creationInfo.MaxPathLength
|
2015-11-12 16:24:32 +00:00
|
|
|
if certTemplate.MaxPathLen == 0 {
|
|
|
|
certTemplate.MaxPathLenZero = true
|
|
|
|
}
|
2015-10-14 19:53:57 +00:00
|
|
|
}
|
|
|
|
|
2015-08-29 13:03:02 +00:00
|
|
|
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
|
|
|
}
|