Set some basic key usages by default.

Some programs (such as OpenVPN) don't like it if you don't include key
usages. This adds a default set that should suffice for most extended
usages. However, since things get twitchy when these are set in ways
various crypto stacks don't like, it's fully controllable by the user.

Fixes #1476
This commit is contained in:
Jeff Mitchell
2016-06-22 16:08:24 -04:00
parent d9beaffa7c
commit 48bd5db7af
3 changed files with 133 additions and 43 deletions

View File

@@ -29,8 +29,9 @@ import (
)
var (
stepCount = 0
serialUnderTest string
stepCount = 0
serialUnderTest string
parsedKeyUsageUnderTest int
)
// Performs basic tests on CA functionality
@@ -366,7 +367,7 @@ func TestBackend_ECRoles_CSR(t *testing.T) {
}
// Performs some validity checking on the returned bundles
func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage certUsage, validity time.Duration, certBundle *certutil.CertBundle) (*certutil.ParsedCertBundle, error) {
func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUsage, extUsage x509.ExtKeyUsage, validity time.Duration, certBundle *certutil.CertBundle) (*certutil.ParsedCertBundle, error) {
parsedCertBundle, err := certBundle.ToParsedCertBundle()
if err != nil {
return nil, fmt.Errorf("Error parsing cert bundle: %s", err)
@@ -407,27 +408,32 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage certUsage,
}
cert := parsedCertBundle.Certificate
// There should only be one usage type, because only one is requested
if usage != cert.KeyUsage {
return nil, fmt.Errorf("Expected usage of %#v, got %#v; ext usage is %#v", usage, cert.KeyUsage, cert.ExtKeyUsage)
}
// There should only be one ext usage type, because only one is requested
// in the tests
if len(cert.ExtKeyUsage) != 1 {
return nil, fmt.Errorf("Got wrong size key usage in generated cert; values are %#v", cert.ExtKeyUsage)
}
switch usage {
case emailProtectionUsage:
switch extUsage {
case x509.ExtKeyUsageEmailProtection:
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageEmailProtection {
return nil, fmt.Errorf("Bad key usage")
return nil, fmt.Errorf("Bad extended key usage")
}
case serverUsage:
case x509.ExtKeyUsageServerAuth:
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageServerAuth {
return nil, fmt.Errorf("Bad key usage")
return nil, fmt.Errorf("Bad extended key usage")
}
case clientUsage:
case x509.ExtKeyUsageClientAuth:
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageClientAuth {
return nil, fmt.Errorf("Bad key usage")
return nil, fmt.Errorf("Bad extended key usage")
}
case codeSigningUsage:
case x509.ExtKeyUsageCodeSigning:
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageCodeSigning {
return nil, fmt.Errorf("Bad key usage")
return nil, fmt.Errorf("Bad extended key usage")
}
}
@@ -1497,14 +1503,14 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
// Returns a TestCheckFunc that performs various validity checks on the
// returned certificate information, mostly within checkCertsAndPrivateKey
getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage certUsage, validity time.Duration) logicaltest.TestCheckFunc {
getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage x509.KeyUsage, extUsage x509.ExtKeyUsage, validity time.Duration) logicaltest.TestCheckFunc {
var certBundle certutil.CertBundle
return func(resp *logical.Response) error {
err := mapstructure.Decode(resp.Data, &certBundle)
if err != nil {
return err
}
parsedCertBundle, err := checkCertsAndPrivateKey(role.KeyType, key, usage, validity, &certBundle)
parsedCertBundle, err := checkCertsAndPrivateKey(role.KeyType, key, usage, extUsage, validity, &certBundle)
if err != nil {
return fmt.Errorf("Error checking generated certificate: %s", err)
}
@@ -1567,20 +1573,54 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
roleVals.ClientFlag = false
roleVals.CodeSigningFlag = false
roleVals.EmailProtectionFlag = false
var usage certUsage
i := mathRand.Int()
var usage string
if mathRand.Int()%2 == 1 {
usage = usage + ",DigitalSignature"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",ContentCoMmitment"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",KeyEncipherment"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",DataEncipherment"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",KeyAgreemEnt"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",CertSign"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",CRLSign"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",EncipherOnly"
}
if mathRand.Int()%2 == 1 {
usage = usage + ",DecipherOnly"
}
roleVals.KeyUsage = usage
parsedKeyUsage := parseKeyUsages(roleVals.KeyUsage)
parsedKeyUsageUnderTest = parsedKeyUsage
var extUsage x509.ExtKeyUsage
i := mathRand.Int() % 4
switch {
case i%5 == 0:
usage = emailProtectionUsage
case i == 0:
extUsage = x509.ExtKeyUsageEmailProtection
roleVals.EmailProtectionFlag = true
case i%3 == 0:
usage = serverUsage
case i == 1:
extUsage = x509.ExtKeyUsageServerAuth
roleVals.ServerFlag = true
case i%2 == 0:
usage = clientUsage
case i == 2:
extUsage = x509.ExtKeyUsageClientAuth
roleVals.ClientFlag = true
default:
usage = codeSigningUsage
extUsage = x509.ExtKeyUsageCodeSigning
roleVals.CodeSigningFlag = true
}
@@ -1666,9 +1706,9 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
}
issueVals.CSR = strings.TrimSpace(string(pem.EncodeToMemory(&block)))
addTests(getCnCheck(issueVals.CommonName, roleVals, privKey, usage, validity))
addTests(getCnCheck(issueVals.CommonName, roleVals, privKey, x509.KeyUsage(parsedKeyUsage), extUsage, validity))
} else {
addTests(getCnCheck(issueVals.CommonName, roleVals, nil, usage, validity))
addTests(getCnCheck(issueVals.CommonName, roleVals, nil, x509.KeyUsage(parsedKeyUsage), extUsage, validity))
}
}
}

View File

@@ -20,14 +20,13 @@ import (
"github.com/hashicorp/vault/logical/framework"
)
type certUsage int
type certExtKeyUsage int
const (
serverExtUsage certUsage = 1 << iota
clientExtUsage
codeSigningExtUsage
emailProtectionExtUsage
caUsage
serverExtKeyUsage certExtKeyUsage = 1 << iota
clientExtKeyUsage
codeSigningExtKeyUsage
emailProtectionExtKeyUsage
)
type creationBundle struct {
@@ -40,7 +39,8 @@ type creationBundle struct {
KeyBits int
SigningBundle *caInfoBundle
TTL time.Duration
Usage certUsage
KeyUsage x509.KeyUsage
ExtKeyUsage certExtKeyUsage
// Only used when signing a CA cert
UseCSRValues bool
@@ -679,19 +679,19 @@ func generateCreationBundle(b *backend,
}
// Build up usages
var usage certUsage
var extUsage certExtKeyUsage
{
if role.ServerFlag {
usage = usage | serverExtUsage
extUsage = extUsage | serverExtKeyUsage
}
if role.ClientFlag {
usage = usage | clientExtUsage
extUsage = extUsage | clientExtKeyUsage
}
if role.CodeSigningFlag {
usage = usage | codeSigningExtUsage
extUsage = extUsage | codeSigningExtKeyUsage
}
if role.EmailProtectionFlag {
usage = usage | emailProtectionExtUsage
extUsage = extUsage | emailProtectionExtKeyUsage
}
}
@@ -704,7 +704,8 @@ func generateCreationBundle(b *backend,
KeyBits: role.KeyBits,
SigningBundle: signingBundle,
TTL: ttl,
Usage: usage,
KeyUsage: x509.KeyUsage(role.ParsedKeyUsage),
ExtKeyUsage: extUsage,
}
// Don't deal with URLs or max path length if it's self-signed, as these
@@ -747,16 +748,18 @@ func addKeyUsages(creationInfo *creationBundle, certTemplate *x509.Certificate)
return
}
if creationInfo.Usage&serverExtUsage != 0 {
certTemplate.KeyUsage = creationInfo.KeyUsage
if creationInfo.ExtKeyUsage&serverExtKeyUsage != 0 {
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
}
if creationInfo.Usage&clientExtUsage != 0 {
if creationInfo.ExtKeyUsage&clientExtKeyUsage != 0 {
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
}
if creationInfo.Usage&codeSigningExtUsage != 0 {
if creationInfo.ExtKeyUsage&codeSigningExtKeyUsage != 0 {
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning)
}
if creationInfo.Usage&emailProtectionExtUsage != 0 {
if creationInfo.ExtKeyUsage&emailProtectionExtKeyUsage != 0 {
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection)
}
}

View File

@@ -1,6 +1,7 @@
package pki
import (
"crypto/x509"
"fmt"
"strings"
"time"
@@ -147,6 +148,17 @@ certainly want to change this if you adjust
the key_type.`,
},
"key_usage": &framework.FieldSchema{
Type: framework.TypeString,
Default: "DigitalSignature,KeyAgreement,KeyEncipherment",
Description: `A comma-separated set of key usages (not extended
key usages). Valid values can be found at
https://golang.org/pkg/crypto/x509/#KeyUsage
-- simply drop the "KeyUsage" part of the name.
To remove all key usages from being set, set
this value to an empty string.`,
},
"use_csr_common_name": &framework.FieldSchema{
Type: framework.TypeBool,
Default: true,
@@ -315,6 +327,7 @@ func (b *backend) pathRoleCreate(
KeyType: data.Get("key_type").(string),
KeyBits: data.Get("key_bits").(int),
UseCSRCommonName: data.Get("use_csr_common_name").(bool),
KeyUsage: data.Get("key_usage").(string),
}
if entry.KeyType == "rsa" && entry.KeyBits < 2048 {
@@ -364,6 +377,9 @@ func (b *backend) pathRoleCreate(
return errResp, nil
}
// Parse key usages
entry.ParsedKeyUsage = parseKeyUsages(entry.KeyUsage)
// Store it
jsonEntry, err := logical.StorageEntryJSON("role/"+name, entry)
if err != nil {
@@ -376,6 +392,35 @@ func (b *backend) pathRoleCreate(
return nil, nil
}
func parseKeyUsages(input string) int {
var parsedKeyUsages x509.KeyUsage
splitKeyUsage := strings.Split(input, ",")
for _, k := range splitKeyUsage {
switch strings.ToLower(k) {
case "digitalsignature":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageDigitalSignature
case "contentcommitment":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageContentCommitment
case "keyencipherment":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageKeyEncipherment
case "dataencipherment":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageDataEncipherment
case "keyagreement":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageKeyAgreement
case "certsign":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageCertSign
case "crlsign":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageCRLSign
case "encipheronly":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageEncipherOnly
case "decipheronly":
parsedKeyUsages = parsedKeyUsages | x509.KeyUsageDecipherOnly
}
}
return int(parsedKeyUsages)
}
type roleEntry struct {
LeaseMax string `json:"lease_max" structs:"lease_max" mapstructure:"lease_max"`
Lease string `json:"lease" structs:"lease" mapstructure:"lease"`
@@ -399,6 +444,8 @@ type roleEntry struct {
KeyType string `json:"key_type" structs:"key_type" mapstructure:"key_type"`
KeyBits int `json:"key_bits" structs:"key_bits" mapstructure:"key_bits"`
MaxPathLength *int `json:",omitempty" structs:",omitempty"`
KeyUsage string `json:"key_usage" structs:"key_usage" mapstructure:"key_usage"`
ParsedKeyUsage int `json:"parsed_key_usage" structs:"parsed_key_usage" mapstructure:"parsed_key_usage"`
}
const pathListRolesHelpSyn = `List the existing roles in this backend`