Support other names in SANs (#3889)

This commit is contained in:
Jeff Mitchell 2018-02-16 17:19:34 -05:00 committed by GitHub
parent e95b0d01b0
commit a43a854740
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 2262 additions and 252 deletions

View File

@ -2801,6 +2801,253 @@ func TestBackend_SignSelfIssued(t *testing.T) {
} }
} }
// This is a really tricky test because the Go stdlib asn1 package is incapable
// of doing the right thing with custom OID SANs (see comments in the package,
// it's readily admitted that it's too magic) but that means that any
// validation logic written for this test isn't being independently verified,
// as in, if cryptobytes is used to decode it to make the test work, that
// doesn't mean we're encoding and decoding correctly, only that we made the
// test pass. Instead, when run verbosely it will first perform a bunch of
// checks to verify that the OID SAN logic doesn't screw up other SANs, then
// will spit out the PEM. This can be validated independently.
//
// You want the hex dump of the octet string corresponding to the X509v3
// Subject Alternative Name. There's a nice online utility at
// https://lapo.it/asn1js that can be used to view the structure of an
// openssl-generated other SAN at
// https://lapo.it/asn1js/#3022A020060A2B060104018237140203A0120C106465766F7073406C6F63616C686F7374
// (openssl asn1parse can also be used with -strparse using an offset of the
// hex blob for the subject alternative names extension).
//
// The structure output from here should match that precisely (even if the OID
// itself doesn't) in the second test.
//
// The test that encodes two should have them be in separate elements in the
// top-level sequence; see
// https://lapo.it/asn1js/#3046A020060A2B060104018237140203A0120C106465766F7073406C6F63616C686F7374A022060A2B060104018237140204A0140C12322D6465766F7073406C6F63616C686F7374 for an openssl-generated example.
//
// The good news is that it's valid to simply copy and paste the PEM ouput from
// here into the form at that site as it will do the right thing so it's pretty
// easy to validate.
func TestBackend_OID_SANs(t *testing.T) {
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
var err error
err = client.Sys().Mount("root", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
})
if err != nil {
t.Fatal(err)
}
var resp *api.Secret
var certStr string
var block *pem.Block
var cert *x509.Certificate
_, err = client.Logical().Write("root/root/generate/internal", map[string]interface{}{
"ttl": "40h",
"common_name": "myvault.com",
})
if err != nil {
t.Fatal(err)
}
_, err = client.Logical().Write("root/roles/test", map[string]interface{}{
"allowed_domains": []string{"foobar.com", "zipzap.com"},
"allow_bare_domains": true,
"allow_subdomains": true,
"allow_ip_sans": true,
"allowed_other_sans": "1.3.6.1.4.1.311.20.2.3;UTF8:devops@*,1.3.6.1.4.1.311.20.2.4;utf8:d*e@foobar.com",
})
if err != nil {
t.Fatal(err)
}
// Get a baseline before adding OID SANs. In the next sections we'll verify
// that the SANs are all added even as the OID SAN inclusion forces other
// adding logic (custom rather than built-in Golang logic)
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
})
if err != nil {
t.Fatal(err)
}
certStr = resp.Data["certificate"].(string)
block, _ = pem.Decode([]byte(certStr))
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}
if cert.IPAddresses[0].String() != "1.2.3.4" {
t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String())
}
if cert.DNSNames[0] != "foobar.com" ||
cert.DNSNames[1] != "bar.foobar.com" ||
cert.DNSNames[2] != "foo.foobar.com" {
t.Fatalf("unexpected DNS SANs %v", cert.DNSNames)
}
// First test some bad stuff that shouldn't work
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
// Not a valid value for the first possibility
"other_sans": "1.3.6.1.4.1.311.20.2.3;UTF8:devop@nope.com",
})
if err == nil {
t.Fatal("expected error")
}
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
// Not a valid OID for the first possibility
"other_sans": "1.3.6.1.4.1.311.20.2.5;UTF8:devops@nope.com",
})
if err == nil {
t.Fatal("expected error")
}
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
// Not a valid name for the second possibility
"other_sans": "1.3.6.1.4.1.311.20.2.4;UTF8:d34g@foobar.com",
})
if err == nil {
t.Fatal("expected error")
}
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
// Not a valid OID for the second possibility
"other_sans": "1.3.6.1.4.1.311.20.2.5;UTF8:d34e@foobar.com",
})
if err == nil {
t.Fatal("expected error")
}
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
// Not a valid type
"other_sans": "1.3.6.1.4.1.311.20.2.5;UTF2:d34e@foobar.com",
})
if err == nil {
t.Fatal("expected error")
}
// Valid for first possibility
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"other_sans": "1.3.6.1.4.1.311.20.2.3;utf8:devops@nope.com",
})
if err != nil {
t.Fatal(err)
}
certStr = resp.Data["certificate"].(string)
block, _ = pem.Decode([]byte(certStr))
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}
if cert.IPAddresses[0].String() != "1.2.3.4" {
t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String())
}
if cert.DNSNames[0] != "foobar.com" ||
cert.DNSNames[1] != "bar.foobar.com" ||
cert.DNSNames[2] != "foo.foobar.com" {
t.Fatalf("unexpected DNS SANs %v", cert.DNSNames)
}
t.Logf("certificate 1 to check:\n%s", certStr)
// Valid for second possibility
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"other_sans": "1.3.6.1.4.1.311.20.2.4;UTF8:d234e@foobar.com",
})
if err != nil {
t.Fatal(err)
}
certStr = resp.Data["certificate"].(string)
block, _ = pem.Decode([]byte(certStr))
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}
if cert.IPAddresses[0].String() != "1.2.3.4" {
t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String())
}
if cert.DNSNames[0] != "foobar.com" ||
cert.DNSNames[1] != "bar.foobar.com" ||
cert.DNSNames[2] != "foo.foobar.com" {
t.Fatalf("unexpected DNS SANs %v", cert.DNSNames)
}
t.Logf("certificate 2 to check:\n%s", certStr)
// Valid for both
resp, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"other_sans": "1.3.6.1.4.1.311.20.2.3;utf8:devops@nope.com,1.3.6.1.4.1.311.20.2.4;utf8:d234e@foobar.com",
})
if err != nil {
t.Fatal(err)
}
certStr = resp.Data["certificate"].(string)
block, _ = pem.Decode([]byte(certStr))
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}
if cert.IPAddresses[0].String() != "1.2.3.4" {
t.Fatalf("unexpected IP SAN %q", cert.IPAddresses[0].String())
}
if cert.DNSNames[0] != "foobar.com" ||
cert.DNSNames[1] != "bar.foobar.com" ||
cert.DNSNames[2] != "foo.foobar.com" {
t.Fatalf("unexpected DNS SANs %v", cert.DNSNames)
}
t.Logf("certificate 3 to check:\n%s", certStr)
}
const ( const (
rsaCAKey string = `-----BEGIN RSA PRIVATE KEY----- rsaCAKey string = `-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmPQlK7xD5p+E8iLQ8XlVmll5uU2NKMxKY3UF5tbh+0vkc+Fy MIIEogIBAAKCAQEAmPQlK7xD5p+E8iLQ8XlVmll5uU2NKMxKY3UF5tbh+0vkc+Fy

View File

@ -36,6 +36,13 @@ func (b *backend) getGenerationParams(
AllowAnyName: true, AllowAnyName: true,
AllowIPSANs: true, AllowIPSANs: true,
EnforceHostnames: false, EnforceHostnames: false,
OU: data.Get("ou").([]string),
Organization: data.Get("organization").([]string),
Country: data.Get("country").([]string),
Locality: data.Get("locality").([]string),
Province: data.Get("province").([]string),
StreetAddress: data.Get("street_address").([]string),
PostalCode: data.Get("postal_code").([]string),
} }
if role.KeyType == "rsa" && role.KeyBits < 2048 { if role.KeyType == "rsa" && role.KeyBits < 2048 {

File diff suppressed because it is too large Load Diff

View File

@ -39,6 +39,12 @@ pkcs8 instead. Defaults to "der".`,
comma-delimited list`, comma-delimited list`,
} }
fields["other_sans"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `Requested other SANs, in an array with the format
<oid>;UTF8:<utf8 string value> for each entry.`,
}
return fields return fields
} }
@ -114,6 +120,48 @@ a CA cert or signing a CA cert, not when
generating a CSR for an intermediate CA.`, generating a CSR for an intermediate CA.`,
} }
fields["ou"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, OU (OrganizationalUnit) will be set to
this value.`,
}
fields["organization"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, O (Organization) will be set to
this value.`,
}
fields["country"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Country will be set to
this value.`,
}
fields["locality"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Locality will be set to
this value.`,
}
fields["province"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Province will be set to
this value.`,
}
fields["street_address"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Street Address will be set to
this value.`,
}
fields["postal_code"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Postal Code will be set to
this value.`,
}
return fields return fields
} }

View File

@ -63,7 +63,12 @@ func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Req
} }
var resp *logical.Response var resp *logical.Response
parsedBundle, err := generateIntermediateCSR(b, role, nil, req, data) input := &dataBundle{
role: role,
req: req,
apiData: data,
}
parsedBundle, err := generateIntermediateCSR(b, input)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:

View File

@ -175,12 +175,18 @@ func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, d
"error fetching CA certificate: %s", caErr)} "error fetching CA certificate: %s", caErr)}
} }
input := &dataBundle{
req: req,
apiData: data,
role: role,
signingBundle: signingBundle,
}
var parsedBundle *certutil.ParsedCertBundle var parsedBundle *certutil.ParsedCertBundle
var err error var err error
if useCSR { if useCSR {
parsedBundle, err = signCert(b, role, signingBundle, false, useCSRValues, req, data) parsedBundle, err = signCert(b, input, false, useCSRValues)
} else { } else {
parsedBundle, err = generateCert(ctx, b, role, signingBundle, false, req, data) parsedBundle, err = generateCert(ctx, b, input, false)
} }
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/parseutil"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
@ -114,6 +115,11 @@ CN and SANs. Defaults to true.`,
Any valid IP is accepted.`, Any valid IP is accepted.`,
}, },
"allowed_other_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, an array of allowed other names to put in SANs. These values support globbing.`,
},
"server_flag": &framework.FieldSchema{ "server_flag": &framework.FieldSchema{
Type: framework.TypeBool, Type: framework.TypeBool,
Default: true, Default: true,
@ -187,13 +193,43 @@ include the Common Name (cn). Defaults to true.`,
"ou": &framework.FieldSchema{ "ou": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: `If set, the OU (OrganizationalUnit) will be set to Description: `If set, OU (OrganizationalUnit) will be set to
this value in certificates issued by this role.`, this value in certificates issued by this role.`,
}, },
"organization": &framework.FieldSchema{ "organization": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: `If set, the O (Organization) will be set to Description: `If set, O (Organization) will be set to
this value in certificates issued by this role.`,
},
"country": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Country will be set to
this value in certificates issued by this role.`,
},
"locality": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Locality will be set to
this value in certificates issued by this role.`,
},
"province": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Province will be set to
this value in certificates issued by this role.`,
},
"street_address": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Street Address will be set to
this value in certificates issued by this role.`,
},
"postal_code": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, Postal Code will be set to
this value in certificates issued by this role.`, this value in certificates issued by this role.`,
}, },
@ -414,11 +450,25 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
KeyUsage: data.Get("key_usage").([]string), KeyUsage: data.Get("key_usage").([]string),
OU: data.Get("ou").([]string), OU: data.Get("ou").([]string),
Organization: data.Get("organization").([]string), Organization: data.Get("organization").([]string),
Country: data.Get("country").([]string),
Locality: data.Get("locality").([]string),
Province: data.Get("province").([]string),
StreetAddress: data.Get("street_address").([]string),
PostalCode: data.Get("postal_code").([]string),
GenerateLease: new(bool), GenerateLease: new(bool),
NoStore: data.Get("no_store").(bool), NoStore: data.Get("no_store").(bool),
RequireCN: data.Get("require_cn").(bool), RequireCN: data.Get("require_cn").(bool),
} }
otherSANs := data.Get("allowed_other_sans").([]string)
if len(otherSANs) > 0 {
_, err := parseOtherSANs(otherSANs)
if err != nil {
return logical.ErrorResponse(errwrap.Wrapf("error parsing allowed_other_sans: {{err}}", err).Error()), nil
}
entry.AllowedOtherSANs = otherSANs
}
// no_store implies generate_lease := false // no_store implies generate_lease := false
if entry.NoStore { if entry.NoStore {
*entry.GenerateLease = false *entry.GenerateLease = false
@ -545,9 +595,15 @@ type roleEntry struct {
OU []string `json:"ou_list" mapstructure:"ou"` OU []string `json:"ou_list" mapstructure:"ou"`
OrganizationOld string `json:"organization,omitempty"` OrganizationOld string `json:"organization,omitempty"`
Organization []string `json:"organization_list" mapstructure:"organization"` Organization []string `json:"organization_list" mapstructure:"organization"`
Country []string `json:"country" mapstructure:"country"`
Locality []string `json:"locality" mapstructure:"locality"`
Province []string `json:"province" mapstructure:"province"`
StreetAddress []string `json:"street_address" mapstructure:"street_address"`
PostalCode []string `json:"postal_code" mapstructure:"postal_code"`
GenerateLease *bool `json:"generate_lease,omitempty"` GenerateLease *bool `json:"generate_lease,omitempty"`
NoStore bool `json:"no_store" mapstructure:"no_store"` NoStore bool `json:"no_store" mapstructure:"no_store"`
RequireCN bool `json:"require_cn" mapstructure:"require_cn"` RequireCN bool `json:"require_cn" mapstructure:"require_cn"`
AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"`
// Used internally for signing intermediates // Used internally for signing intermediates
AllowExpirationPastCA bool AllowExpirationPastCA bool
@ -577,7 +633,13 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
"key_usage": r.KeyUsage, "key_usage": r.KeyUsage,
"ou": r.OU, "ou": r.OU,
"organization": r.Organization, "organization": r.Organization,
"country": r.Country,
"locality": r.Locality,
"province": r.Province,
"street_address": r.StreetAddress,
"postal_code": r.PostalCode,
"no_store": r.NoStore, "no_store": r.NoStore,
"allowed_other_sans": r.AllowedOtherSANs,
} }
if r.MaxPathLength != nil { if r.MaxPathLength != nil {
responseData["max_path_length"] = r.MaxPathLength responseData["max_path_length"] = r.MaxPathLength

View File

@ -136,7 +136,12 @@ func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request,
role.MaxPathLength = &maxPathLength role.MaxPathLength = &maxPathLength
} }
parsedBundle, err := generateCert(ctx, b, role, nil, true, req, data) input := &dataBundle{
req: req,
apiData: data,
role: role,
}
parsedBundle, err := generateCert(ctx, b, input, true)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:
@ -247,6 +252,13 @@ func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Reque
} }
role := &roleEntry{ role := &roleEntry{
OU: data.Get("ou").([]string),
Organization: data.Get("organization").([]string),
Country: data.Get("country").([]string),
Locality: data.Get("locality").([]string),
Province: data.Get("province").([]string),
StreetAddress: data.Get("street_address").([]string),
PostalCode: data.Get("postal_code").([]string),
TTL: (time.Duration(data.Get("ttl").(int)) * time.Second).String(), TTL: (time.Duration(data.Get("ttl").(int)) * time.Second).String(),
AllowLocalhost: true, AllowLocalhost: true,
AllowAnyName: true, AllowAnyName: true,
@ -279,7 +291,13 @@ func (b *backend) pathCASignIntermediate(ctx context.Context, req *logical.Reque
role.MaxPathLength = &maxPathLength role.MaxPathLength = &maxPathLength
} }
parsedBundle, err := signCert(b, role, signingBundle, true, useCSRValues, req, data) input := &dataBundle{
req: req,
apiData: data,
signingBundle: signingBundle,
role: role,
}
parsedBundle, err := signCert(b, input, true, useCSRValues)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/hashicorp/vault/helper/strutil"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
@ -100,3 +101,20 @@ func ParseBool(in interface{}) (bool, error) {
} }
return result, nil return result, nil
} }
func ParseCommaStringSlice(in interface{}) ([]string, error) {
var result []string
config := &mapstructure.DecoderConfig{
Result: &result,
WeaklyTypedInput: true,
DecodeHook: mapstructure.StringToSliceHookFunc(","),
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return nil, err
}
if err := decoder.Decode(in); err != nil {
return nil, err
}
return strutil.TrimStrings(result), nil
}

View File

@ -224,20 +224,11 @@ func (d *FieldData) getPrimitive(
return strutil.TrimStrings(result), true, nil return strutil.TrimStrings(result), true, nil
case TypeCommaStringSlice: case TypeCommaStringSlice:
var result []string res, err := parseutil.ParseCommaStringSlice(raw)
config := &mapstructure.DecoderConfig{
Result: &result,
WeaklyTypedInput: true,
DecodeHook: mapstructure.StringToSliceHookFunc(","),
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
if err := decoder.Decode(raw); err != nil { return res, true, nil
return nil, false, err
}
return strutil.TrimStrings(result), true, nil
case TypeKVPairs: case TypeKVPairs:
// First try to parse this as a map // First try to parse this as a map

732
vendor/golang.org/x/crypto/cryptobyte/asn1.go generated vendored Normal file
View File

@ -0,0 +1,732 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cryptobyte
import (
encoding_asn1 "encoding/asn1"
"fmt"
"math/big"
"reflect"
"time"
"golang.org/x/crypto/cryptobyte/asn1"
)
// This file contains ASN.1-related methods for String and Builder.
// Builder
// AddASN1Int64 appends a DER-encoded ASN.1 INTEGER.
func (b *Builder) AddASN1Int64(v int64) {
b.addASN1Signed(asn1.INTEGER, v)
}
// AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION.
func (b *Builder) AddASN1Enum(v int64) {
b.addASN1Signed(asn1.ENUM, v)
}
func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) {
b.AddASN1(tag, func(c *Builder) {
length := 1
for i := v; i >= 0x80 || i < -0x80; i >>= 8 {
length++
}
for ; length > 0; length-- {
i := v >> uint((length-1)*8) & 0xff
c.AddUint8(uint8(i))
}
})
}
// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER.
func (b *Builder) AddASN1Uint64(v uint64) {
b.AddASN1(asn1.INTEGER, func(c *Builder) {
length := 1
for i := v; i >= 0x80; i >>= 8 {
length++
}
for ; length > 0; length-- {
i := v >> uint((length-1)*8) & 0xff
c.AddUint8(uint8(i))
}
})
}
// AddASN1BigInt appends a DER-encoded ASN.1 INTEGER.
func (b *Builder) AddASN1BigInt(n *big.Int) {
if b.err != nil {
return
}
b.AddASN1(asn1.INTEGER, func(c *Builder) {
if n.Sign() < 0 {
// A negative number has to be converted to two's-complement form. So we
// invert and subtract 1. If the most-significant-bit isn't set then
// we'll need to pad the beginning with 0xff in order to keep the number
// negative.
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bytes := nMinus1.Bytes()
for i := range bytes {
bytes[i] ^= 0xff
}
if bytes[0]&0x80 == 0 {
c.add(0xff)
}
c.add(bytes...)
} else if n.Sign() == 0 {
c.add(0)
} else {
bytes := n.Bytes()
if bytes[0]&0x80 != 0 {
c.add(0)
}
c.add(bytes...)
}
})
}
// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING.
func (b *Builder) AddASN1OctetString(bytes []byte) {
b.AddASN1(asn1.OCTET_STRING, func(c *Builder) {
c.AddBytes(bytes)
})
}
const generalizedTimeFormatStr = "20060102150405Z0700"
// AddASN1GeneralizedTime appends a DER-encoded ASN.1 GENERALIZEDTIME.
func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
if t.Year() < 0 || t.Year() > 9999 {
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a GeneralizedTime", t)
return
}
b.AddASN1(asn1.GeneralizedTime, func(c *Builder) {
c.AddBytes([]byte(t.Format(generalizedTimeFormatStr)))
})
}
// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
// support BIT STRINGs that are not a whole number of bytes.
func (b *Builder) AddASN1BitString(data []byte) {
b.AddASN1(asn1.BIT_STRING, func(b *Builder) {
b.AddUint8(0)
b.AddBytes(data)
})
}
func (b *Builder) addBase128Int(n int64) {
var length int
if n == 0 {
length = 1
} else {
for i := n; i > 0; i >>= 7 {
length++
}
}
for i := length - 1; i >= 0; i-- {
o := byte(n >> uint(i*7))
o &= 0x7f
if i != 0 {
o |= 0x80
}
b.add(o)
}
}
func isValidOID(oid encoding_asn1.ObjectIdentifier) bool {
if len(oid) < 2 {
return false
}
if oid[0] > 2 || (oid[0] <= 1 && oid[1] >= 40) {
return false
}
for _, v := range oid {
if v < 0 {
return false
}
}
return true
}
func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) {
b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) {
if !isValidOID(oid) {
b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid)
return
}
b.addBase128Int(int64(oid[0])*40 + int64(oid[1]))
for _, v := range oid[2:] {
b.addBase128Int(int64(v))
}
})
}
func (b *Builder) AddASN1Boolean(v bool) {
b.AddASN1(asn1.BOOLEAN, func(b *Builder) {
if v {
b.AddUint8(0xff)
} else {
b.AddUint8(0)
}
})
}
func (b *Builder) AddASN1NULL() {
b.add(uint8(asn1.NULL), 0)
}
// MarshalASN1 calls encoding_asn1.Marshal on its input and appends the result if
// successful or records an error if one occurred.
func (b *Builder) MarshalASN1(v interface{}) {
// NOTE(martinkr): This is somewhat of a hack to allow propagation of
// encoding_asn1.Marshal errors into Builder.err. N.B. if you call MarshalASN1 with a
// value embedded into a struct, its tag information is lost.
if b.err != nil {
return
}
bytes, err := encoding_asn1.Marshal(v)
if err != nil {
b.err = err
return
}
b.AddBytes(bytes)
}
// AddASN1 appends an ASN.1 object. The object is prefixed with the given tag.
// Tags greater than 30 are not supported and result in an error (i.e.
// low-tag-number form only). The child builder passed to the
// BuilderContinuation can be used to build the content of the ASN.1 object.
func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
if b.err != nil {
return
}
// Identifiers with the low five bits set indicate high-tag-number format
// (two or more octets), which we don't support.
if tag&0x1f == 0x1f {
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag)
return
}
b.AddUint8(uint8(tag))
b.addLengthPrefixed(1, true, f)
}
// String
func (s *String) ReadASN1Boolean(out *bool) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.INTEGER) || len(bytes) != 1 {
return false
}
switch bytes[0] {
case 0:
*out = false
case 0xff:
*out = true
default:
return false
}
return true
}
var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem()
// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does
// not point to an integer or to a big.Int, it panics. It returns true on
// success and false on error.
func (s *String) ReadASN1Integer(out interface{}) bool {
if reflect.TypeOf(out).Kind() != reflect.Ptr {
panic("out is not a pointer")
}
switch reflect.ValueOf(out).Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var i int64
if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) {
return false
}
reflect.ValueOf(out).Elem().SetInt(i)
return true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
var u uint64
if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) {
return false
}
reflect.ValueOf(out).Elem().SetUint(u)
return true
case reflect.Struct:
if reflect.TypeOf(out).Elem() == bigIntType {
return s.readASN1BigInt(out.(*big.Int))
}
}
panic("out does not point to an integer type")
}
func checkASN1Integer(bytes []byte) bool {
if len(bytes) == 0 {
// An INTEGER is encoded with at least one octet.
return false
}
if len(bytes) == 1 {
return true
}
if bytes[0] == 0 && bytes[1]&0x80 == 0 || bytes[0] == 0xff && bytes[1]&0x80 == 0x80 {
// Value is not minimally encoded.
return false
}
return true
}
var bigOne = big.NewInt(1)
func (s *String) readASN1BigInt(out *big.Int) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
return false
}
if bytes[0]&0x80 == 0x80 {
// Negative number.
neg := make([]byte, len(bytes))
for i, b := range bytes {
neg[i] = ^b
}
out.SetBytes(neg)
out.Add(out, bigOne)
out.Neg(out)
} else {
out.SetBytes(bytes)
}
return true
}
func (s *String) readASN1Int64(out *int64) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) {
return false
}
return true
}
func asn1Signed(out *int64, n []byte) bool {
length := len(n)
if length > 8 {
return false
}
for i := 0; i < length; i++ {
*out <<= 8
*out |= int64(n[i])
}
// Shift up and down in order to sign extend the result.
*out <<= 64 - uint8(length)*8
*out >>= 64 - uint8(length)*8
return true
}
func (s *String) readASN1Uint64(out *uint64) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Unsigned(out, bytes) {
return false
}
return true
}
func asn1Unsigned(out *uint64, n []byte) bool {
length := len(n)
if length > 9 || length == 9 && n[0] != 0 {
// Too large for uint64.
return false
}
if n[0]&0x80 != 0 {
// Negative number.
return false
}
for i := 0; i < length; i++ {
*out <<= 8
*out |= uint64(n[i])
}
return true
}
// ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It returns
// true on success and false on error.
func (s *String) ReadASN1Enum(out *int) bool {
var bytes String
var i int64
if !s.ReadASN1(&bytes, asn1.ENUM) || !checkASN1Integer(bytes) || !asn1Signed(&i, bytes) {
return false
}
if int64(int(i)) != i {
return false
}
*out = int(i)
return true
}
func (s *String) readBase128Int(out *int) bool {
ret := 0
for i := 0; len(*s) > 0; i++ {
if i == 4 {
return false
}
ret <<= 7
b := s.read(1)[0]
ret |= int(b & 0x7f)
if b&0x80 == 0 {
*out = ret
return true
}
}
return false // truncated
}
// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and
// advances. It returns true on success and false on error.
func (s *String) ReadASN1ObjectIdentifier(out *encoding_asn1.ObjectIdentifier) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 {
return false
}
// In the worst case, we get two elements from the first byte (which is
// encoded differently) and then every varint is a single byte long.
components := make([]int, len(bytes)+1)
// The first varint is 40*value1 + value2:
// According to this packing, value1 can take the values 0, 1 and 2 only.
// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
// then there are no restrictions on value2.
var v int
if !bytes.readBase128Int(&v) {
return false
}
if v < 80 {
components[0] = v / 40
components[1] = v % 40
} else {
components[0] = 2
components[1] = v - 80
}
i := 2
for ; len(bytes) > 0; i++ {
if !bytes.readBase128Int(&v) {
return false
}
components[i] = v
}
*out = components[:i]
return true
}
// ReadASN1GeneralizedTime decodes an ASN.1 GENERALIZEDTIME into out and
// advances. It returns true on success and false on error.
func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.GeneralizedTime) {
return false
}
t := string(bytes)
res, err := time.Parse(generalizedTimeFormatStr, t)
if err != nil {
return false
}
if serialized := res.Format(generalizedTimeFormatStr); serialized != t {
return false
}
*out = res
return true
}
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. It
// returns true on success and false on error.
func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 {
return false
}
paddingBits := uint8(bytes[0])
bytes = bytes[1:]
if paddingBits > 7 ||
len(bytes) == 0 && paddingBits != 0 ||
len(bytes) > 0 && bytes[len(bytes)-1]&(1<<paddingBits-1) != 0 {
return false
}
out.BitLength = len(bytes)*8 - int(paddingBits)
out.Bytes = bytes
return true
}
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. It is
// an error if the BIT STRING is not a whole number of bytes. This function
// returns true on success and false on error.
func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
var bytes String
if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 {
return false
}
paddingBits := uint8(bytes[0])
if paddingBits != 0 {
return false
}
*out = bytes[1:]
return true
}
// ReadASN1Bytes reads the contents of a DER-encoded ASN.1 element (not including
// tag and length bytes) into out, and advances. The element must match the
// given tag. It returns true on success and false on error.
func (s *String) ReadASN1Bytes(out *[]byte, tag asn1.Tag) bool {
return s.ReadASN1((*String)(out), tag)
}
// ReadASN1 reads the contents of a DER-encoded ASN.1 element (not including
// tag and length bytes) into out, and advances. The element must match the
// given tag. It returns true on success and false on error.
//
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
func (s *String) ReadASN1(out *String, tag asn1.Tag) bool {
var t asn1.Tag
if !s.ReadAnyASN1(out, &t) || t != tag {
return false
}
return true
}
// ReadASN1Element reads the contents of a DER-encoded ASN.1 element (including
// tag and length bytes) into out, and advances. The element must match the
// given tag. It returns true on success and false on error.
//
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
func (s *String) ReadASN1Element(out *String, tag asn1.Tag) bool {
var t asn1.Tag
if !s.ReadAnyASN1Element(out, &t) || t != tag {
return false
}
return true
}
// ReadAnyASN1 reads the contents of a DER-encoded ASN.1 element (not including
// tag and length bytes) into out, sets outTag to its tag, and advances. It
// returns true on success and false on error.
//
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
func (s *String) ReadAnyASN1(out *String, outTag *asn1.Tag) bool {
return s.readASN1(out, outTag, true /* skip header */)
}
// ReadAnyASN1Element reads the contents of a DER-encoded ASN.1 element
// (including tag and length bytes) into out, sets outTag to is tag, and
// advances. It returns true on success and false on error.
//
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
func (s *String) ReadAnyASN1Element(out *String, outTag *asn1.Tag) bool {
return s.readASN1(out, outTag, false /* include header */)
}
// PeekASN1Tag returns true if the next ASN.1 value on the string starts with
// the given tag.
func (s String) PeekASN1Tag(tag asn1.Tag) bool {
if len(s) == 0 {
return false
}
return asn1.Tag(s[0]) == tag
}
// SkipASN1 reads and discards an ASN.1 element with the given tag.
func (s *String) SkipASN1(tag asn1.Tag) bool {
var unused String
return s.ReadASN1(&unused, tag)
}
// ReadOptionalASN1 attempts to read the contents of a DER-encoded ASN.1
// element (not including tag and length bytes) tagged with the given tag into
// out. It stores whether an element with the tag was found in outPresent,
// unless outPresent is nil. It returns true on success and false on error.
func (s *String) ReadOptionalASN1(out *String, outPresent *bool, tag asn1.Tag) bool {
present := s.PeekASN1Tag(tag)
if outPresent != nil {
*outPresent = present
}
if present && !s.ReadASN1(out, tag) {
return false
}
return true
}
// SkipOptionalASN1 advances s over an ASN.1 element with the given tag, or
// else leaves s unchanged.
func (s *String) SkipOptionalASN1(tag asn1.Tag) bool {
if !s.PeekASN1Tag(tag) {
return true
}
var unused String
return s.ReadASN1(&unused, tag)
}
// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER
// explicitly tagged with tag into out and advances. If no element with a
// matching tag is present, it writes defaultValue into out instead. If out
// does not point to an integer or to a big.Int, it panics. It returns true on
// success and false on error.
func (s *String) ReadOptionalASN1Integer(out interface{}, tag asn1.Tag, defaultValue interface{}) bool {
if reflect.TypeOf(out).Kind() != reflect.Ptr {
panic("out is not a pointer")
}
var present bool
var i String
if !s.ReadOptionalASN1(&i, &present, tag) {
return false
}
if !present {
switch reflect.ValueOf(out).Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
reflect.ValueOf(out).Elem().Set(reflect.ValueOf(defaultValue))
case reflect.Struct:
if reflect.TypeOf(out).Elem() != bigIntType {
panic("invalid integer type")
}
if reflect.TypeOf(defaultValue).Kind() != reflect.Ptr ||
reflect.TypeOf(defaultValue).Elem() != bigIntType {
panic("out points to big.Int, but defaultValue does not")
}
out.(*big.Int).Set(defaultValue.(*big.Int))
default:
panic("invalid integer type")
}
return true
}
if !i.ReadASN1Integer(out) || !i.Empty() {
return false
}
return true
}
// ReadOptionalASN1OctetString attempts to read an optional ASN.1 OCTET STRING
// explicitly tagged with tag into out and advances. If no element with a
// matching tag is present, it writes defaultValue into out instead. It returns
// true on success and false on error.
func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag asn1.Tag) bool {
var present bool
var child String
if !s.ReadOptionalASN1(&child, &present, tag) {
return false
}
if outPresent != nil {
*outPresent = present
}
if present {
var oct String
if !child.ReadASN1(&oct, asn1.OCTET_STRING) || !child.Empty() {
return false
}
*out = oct
} else {
*out = nil
}
return true
}
// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
var present bool
var child String
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
return false
}
if !present {
*out = defaultValue
return true
}
return s.ReadASN1Boolean(out)
}
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
if len(*s) < 2 {
return false
}
tag, lenByte := (*s)[0], (*s)[1]
if tag&0x1f == 0x1f {
// ITU-T X.690 section 8.1.2
//
// An identifier octet with a tag part of 0x1f indicates a high-tag-number
// form identifier with two or more octets. We only support tags less than
// 31 (i.e. low-tag-number form, single octet identifier).
return false
}
if outTag != nil {
*outTag = asn1.Tag(tag)
}
// ITU-T X.690 section 8.1.3
//
// Bit 8 of the first length byte indicates whether the length is short- or
// long-form.
var length, headerLen uint32 // length includes headerLen
if lenByte&0x80 == 0 {
// Short-form length (section 8.1.3.4), encoded in bits 1-7.
length = uint32(lenByte) + 2
headerLen = 2
} else {
// Long-form length (section 8.1.3.5). Bits 1-7 encode the number of octets
// used to encode the length.
lenLen := lenByte & 0x7f
var len32 uint32
if lenLen == 0 || lenLen > 4 || len(*s) < int(2+lenLen) {
return false
}
lenBytes := String((*s)[2 : 2+lenLen])
if !lenBytes.readUnsigned(&len32, int(lenLen)) {
return false
}
// ITU-T X.690 section 10.1 (DER length forms) requires encoding the length
// with the minimum number of octets.
if len32 < 128 {
// Length should have used short-form encoding.
return false
}
if len32>>((lenLen-1)*8) == 0 {
// Leading octet is 0. Length should have been at least one byte shorter.
return false
}
headerLen = 2 + uint32(lenLen)
if headerLen+len32 < len32 {
// Overflow.
return false
}
length = headerLen + len32
}
if uint32(int(length)) != length || !s.ReadBytes((*[]byte)(out), int(length)) {
return false
}
if skipHeader && !out.Skip(int(headerLen)) {
panic("cryptobyte: internal error")
}
return true
}

46
vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package asn1 contains supporting types for parsing and building ASN.1
// messages with the cryptobyte package.
package asn1 // import "golang.org/x/crypto/cryptobyte/asn1"
// Tag represents an ASN.1 identifier octet, consisting of a tag number
// (indicating a type) and class (such as context-specific or constructed).
//
// Methods in the cryptobyte package only support the low-tag-number form, i.e.
// a single identifier octet with bits 7-8 encoding the class and bits 1-6
// encoding the tag number.
type Tag uint8
const (
classConstructed = 0x20
classContextSpecific = 0x80
)
// Constructed returns t with the constructed class bit set.
func (t Tag) Constructed() Tag { return t | classConstructed }
// ContextSpecific returns t with the context-specific class bit set.
func (t Tag) ContextSpecific() Tag { return t | classContextSpecific }
// The following is a list of standard tag and class combinations.
const (
BOOLEAN = Tag(1)
INTEGER = Tag(2)
BIT_STRING = Tag(3)
OCTET_STRING = Tag(4)
NULL = Tag(5)
OBJECT_IDENTIFIER = Tag(6)
ENUM = Tag(10)
UTF8String = Tag(12)
SEQUENCE = Tag(16 | classConstructed)
SET = Tag(17 | classConstructed)
PrintableString = Tag(19)
T61String = Tag(20)
IA5String = Tag(22)
UTCTime = Tag(23)
GeneralizedTime = Tag(24)
GeneralString = Tag(27)
)

309
vendor/golang.org/x/crypto/cryptobyte/builder.go generated vendored Normal file
View File

@ -0,0 +1,309 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cryptobyte
import (
"errors"
"fmt"
)
// A Builder builds byte strings from fixed-length and length-prefixed values.
// Builders either allocate space as needed, or are fixed, which means that
// they write into a given buffer and produce an error if it's exhausted.
//
// The zero value is a usable Builder that allocates space as needed.
//
// Simple values are marshaled and appended to a Builder using methods on the
// Builder. Length-prefixed values are marshaled by providing a
// BuilderContinuation, which is a function that writes the inner contents of
// the value to a given Builder. See the documentation for BuilderContinuation
// for details.
type Builder struct {
err error
result []byte
fixedSize bool
child *Builder
offset int
pendingLenLen int
pendingIsASN1 bool
inContinuation *bool
}
// NewBuilder creates a Builder that appends its output to the given buffer.
// Like append(), the slice will be reallocated if its capacity is exceeded.
// Use Bytes to get the final buffer.
func NewBuilder(buffer []byte) *Builder {
return &Builder{
result: buffer,
}
}
// NewFixedBuilder creates a Builder that appends its output into the given
// buffer. This builder does not reallocate the output buffer. Writes that
// would exceed the buffer's capacity are treated as an error.
func NewFixedBuilder(buffer []byte) *Builder {
return &Builder{
result: buffer,
fixedSize: true,
}
}
// Bytes returns the bytes written by the builder or an error if one has
// occurred during during building.
func (b *Builder) Bytes() ([]byte, error) {
if b.err != nil {
return nil, b.err
}
return b.result[b.offset:], nil
}
// BytesOrPanic returns the bytes written by the builder or panics if an error
// has occurred during building.
func (b *Builder) BytesOrPanic() []byte {
if b.err != nil {
panic(b.err)
}
return b.result[b.offset:]
}
// AddUint8 appends an 8-bit value to the byte string.
func (b *Builder) AddUint8(v uint8) {
b.add(byte(v))
}
// AddUint16 appends a big-endian, 16-bit value to the byte string.
func (b *Builder) AddUint16(v uint16) {
b.add(byte(v>>8), byte(v))
}
// AddUint24 appends a big-endian, 24-bit value to the byte string. The highest
// byte of the 32-bit input value is silently truncated.
func (b *Builder) AddUint24(v uint32) {
b.add(byte(v>>16), byte(v>>8), byte(v))
}
// AddUint32 appends a big-endian, 32-bit value to the byte string.
func (b *Builder) AddUint32(v uint32) {
b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
}
// AddBytes appends a sequence of bytes to the byte string.
func (b *Builder) AddBytes(v []byte) {
b.add(v...)
}
// BuilderContinuation is continuation-passing interface for building
// length-prefixed byte sequences. Builder methods for length-prefixed
// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation
// supplied to them. The child builder passed to the continuation can be used
// to build the content of the length-prefixed sequence. For example:
//
// parent := cryptobyte.NewBuilder()
// parent.AddUint8LengthPrefixed(func (child *Builder) {
// child.AddUint8(42)
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
// grandchild.AddUint8(5)
// })
// })
//
// It is an error to write more bytes to the child than allowed by the reserved
// length prefix. After the continuation returns, the child must be considered
// invalid, i.e. users must not store any copies or references of the child
// that outlive the continuation.
//
// If the continuation panics with a value of type BuildError then the inner
// error will be returned as the error from Bytes. If the child panics
// otherwise then Bytes will repanic with the same value.
type BuilderContinuation func(child *Builder)
// BuildError wraps an error. If a BuilderContinuation panics with this value,
// the panic will be recovered and the inner error will be returned from
// Builder.Bytes.
type BuildError struct {
Err error
}
// AddUint8LengthPrefixed adds a 8-bit length-prefixed byte sequence.
func (b *Builder) AddUint8LengthPrefixed(f BuilderContinuation) {
b.addLengthPrefixed(1, false, f)
}
// AddUint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence.
func (b *Builder) AddUint16LengthPrefixed(f BuilderContinuation) {
b.addLengthPrefixed(2, false, f)
}
// AddUint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence.
func (b *Builder) AddUint24LengthPrefixed(f BuilderContinuation) {
b.addLengthPrefixed(3, false, f)
}
// AddUint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence.
func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) {
b.addLengthPrefixed(4, false, f)
}
func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) {
if !*b.inContinuation {
*b.inContinuation = true
defer func() {
*b.inContinuation = false
r := recover()
if r == nil {
return
}
if buildError, ok := r.(BuildError); ok {
b.err = buildError.Err
} else {
panic(r)
}
}()
}
f(arg)
}
func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) {
// Subsequent writes can be ignored if the builder has encountered an error.
if b.err != nil {
return
}
offset := len(b.result)
b.add(make([]byte, lenLen)...)
if b.inContinuation == nil {
b.inContinuation = new(bool)
}
b.child = &Builder{
result: b.result,
fixedSize: b.fixedSize,
offset: offset,
pendingLenLen: lenLen,
pendingIsASN1: isASN1,
inContinuation: b.inContinuation,
}
b.callContinuation(f, b.child)
b.flushChild()
if b.child != nil {
panic("cryptobyte: internal error")
}
}
func (b *Builder) flushChild() {
if b.child == nil {
return
}
b.child.flushChild()
child := b.child
b.child = nil
if child.err != nil {
b.err = child.err
return
}
length := len(child.result) - child.pendingLenLen - child.offset
if length < 0 {
panic("cryptobyte: internal error") // result unexpectedly shrunk
}
if child.pendingIsASN1 {
// For ASN.1, we reserved a single byte for the length. If that turned out
// to be incorrect, we have to move the contents along in order to make
// space.
if child.pendingLenLen != 1 {
panic("cryptobyte: internal error")
}
var lenLen, lenByte uint8
if int64(length) > 0xfffffffe {
b.err = errors.New("pending ASN.1 child too long")
return
} else if length > 0xffffff {
lenLen = 5
lenByte = 0x80 | 4
} else if length > 0xffff {
lenLen = 4
lenByte = 0x80 | 3
} else if length > 0xff {
lenLen = 3
lenByte = 0x80 | 2
} else if length > 0x7f {
lenLen = 2
lenByte = 0x80 | 1
} else {
lenLen = 1
lenByte = uint8(length)
length = 0
}
// Insert the initial length byte, make space for successive length bytes,
// and adjust the offset.
child.result[child.offset] = lenByte
extraBytes := int(lenLen - 1)
if extraBytes != 0 {
child.add(make([]byte, extraBytes)...)
childStart := child.offset + child.pendingLenLen
copy(child.result[childStart+extraBytes:], child.result[childStart:])
}
child.offset++
child.pendingLenLen = extraBytes
}
l := length
for i := child.pendingLenLen - 1; i >= 0; i-- {
child.result[child.offset+i] = uint8(l)
l >>= 8
}
if l != 0 {
b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, child.pendingLenLen)
return
}
if !b.fixedSize {
b.result = child.result // In case child reallocated result.
}
}
func (b *Builder) add(bytes ...byte) {
if b.err != nil {
return
}
if b.child != nil {
panic("attempted write while child is pending")
}
if len(b.result)+len(bytes) < len(bytes) {
b.err = errors.New("cryptobyte: length overflow")
}
if b.fixedSize && len(b.result)+len(bytes) > cap(b.result) {
b.err = errors.New("cryptobyte: Builder is exceeding its fixed-size buffer")
return
}
b.result = append(b.result, bytes...)
}
// A MarshalingValue marshals itself into a Builder.
type MarshalingValue interface {
// Marshal is called by Builder.AddValue. It receives a pointer to a builder
// to marshal itself into. It may return an error that occurred during
// marshaling, such as unset or invalid values.
Marshal(b *Builder) error
}
// AddValue calls Marshal on v, passing a pointer to the builder to append to.
// If Marshal returns an error, it is set on the Builder so that subsequent
// appends don't have an effect.
func (b *Builder) AddValue(v MarshalingValue) {
err := v.Marshal(b)
if err != nil {
b.err = err
}
}

167
vendor/golang.org/x/crypto/cryptobyte/string.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cryptobyte contains types that help with parsing and constructing
// length-prefixed, binary messages, including ASN.1 DER. (The asn1 subpackage
// contains useful ASN.1 constants.)
//
// The String type is for parsing. It wraps a []byte slice and provides helper
// functions for consuming structures, value by value.
//
// The Builder type is for constructing messages. It providers helper functions
// for appending values and also for appending length-prefixed submessages
// without having to worry about calculating the length prefix ahead of time.
//
// See the documentation and examples for the Builder and String types to get
// started.
package cryptobyte // import "golang.org/x/crypto/cryptobyte"
// String represents a string of bytes. It provides methods for parsing
// fixed-length and length-prefixed values from it.
type String []byte
// read advances a String by n bytes and returns them. If less than n bytes
// remain, it returns nil.
func (s *String) read(n int) []byte {
if len(*s) < n {
return nil
}
v := (*s)[:n]
*s = (*s)[n:]
return v
}
// Skip advances the String by n byte and reports whether it was successful.
func (s *String) Skip(n int) bool {
return s.read(n) != nil
}
// ReadUint8 decodes an 8-bit value into out and advances over it. It
// returns true on success and false on error.
func (s *String) ReadUint8(out *uint8) bool {
v := s.read(1)
if v == nil {
return false
}
*out = uint8(v[0])
return true
}
// ReadUint16 decodes a big-endian, 16-bit value into out and advances over it.
// It returns true on success and false on error.
func (s *String) ReadUint16(out *uint16) bool {
v := s.read(2)
if v == nil {
return false
}
*out = uint16(v[0])<<8 | uint16(v[1])
return true
}
// ReadUint24 decodes a big-endian, 24-bit value into out and advances over it.
// It returns true on success and false on error.
func (s *String) ReadUint24(out *uint32) bool {
v := s.read(3)
if v == nil {
return false
}
*out = uint32(v[0])<<16 | uint32(v[1])<<8 | uint32(v[2])
return true
}
// ReadUint32 decodes a big-endian, 32-bit value into out and advances over it.
// It returns true on success and false on error.
func (s *String) ReadUint32(out *uint32) bool {
v := s.read(4)
if v == nil {
return false
}
*out = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3])
return true
}
func (s *String) readUnsigned(out *uint32, length int) bool {
v := s.read(length)
if v == nil {
return false
}
var result uint32
for i := 0; i < length; i++ {
result <<= 8
result |= uint32(v[i])
}
*out = result
return true
}
func (s *String) readLengthPrefixed(lenLen int, outChild *String) bool {
lenBytes := s.read(lenLen)
if lenBytes == nil {
return false
}
var length uint32
for _, b := range lenBytes {
length = length << 8
length = length | uint32(b)
}
if int(length) < 0 {
// This currently cannot overflow because we read uint24 at most, but check
// anyway in case that changes in the future.
return false
}
v := s.read(int(length))
if v == nil {
return false
}
*outChild = v
return true
}
// ReadUint8LengthPrefixed reads the content of an 8-bit length-prefixed value
// into out and advances over it. It returns true on success and false on
// error.
func (s *String) ReadUint8LengthPrefixed(out *String) bool {
return s.readLengthPrefixed(1, out)
}
// ReadUint16LengthPrefixed reads the content of a big-endian, 16-bit
// length-prefixed value into out and advances over it. It returns true on
// success and false on error.
func (s *String) ReadUint16LengthPrefixed(out *String) bool {
return s.readLengthPrefixed(2, out)
}
// ReadUint24LengthPrefixed reads the content of a big-endian, 24-bit
// length-prefixed value into out and advances over it. It returns true on
// success and false on error.
func (s *String) ReadUint24LengthPrefixed(out *String) bool {
return s.readLengthPrefixed(3, out)
}
// ReadBytes reads n bytes into out and advances over them. It returns true on
// success and false and error.
func (s *String) ReadBytes(out *[]byte, n int) bool {
v := s.read(n)
if v == nil {
return false
}
*out = v
return true
}
// CopyBytes copies len(out) bytes into out and advances over them. It returns
// true on success and false on error.
func (s *String) CopyBytes(out []byte) bool {
n := len(out)
v := s.read(n)
if v == nil {
return false
}
return copy(out, v) == n
}
// Empty reports whether the string does not contain any bytes.
func (s String) Empty() bool {
return len(s) == 0
}

12
vendor/vendor.json vendored
View File

@ -1716,6 +1716,18 @@
"revision": "5119cf507ed5294cc409c092980c7497ee5d6fd2", "revision": "5119cf507ed5294cc409c092980c7497ee5d6fd2",
"revisionTime": "2018-01-22T10:39:14Z" "revisionTime": "2018-01-22T10:39:14Z"
}, },
{
"checksumSHA1": "t3UubYn5RuLw3vTUghV1Q1Q9VEY=",
"path": "golang.org/x/crypto/cryptobyte",
"revision": "1875d0a70c90e57f11972aefd42276df65e895b9",
"revisionTime": "2018-01-27T19:02:20Z"
},
{
"checksumSHA1": "YEoV2AiZZPDuF7pMVzDt7buS9gc=",
"path": "golang.org/x/crypto/cryptobyte/asn1",
"revision": "1875d0a70c90e57f11972aefd42276df65e895b9",
"revisionTime": "2018-01-27T19:02:20Z"
},
{ {
"checksumSHA1": "1zB843WyoSh8oMdbeDfgByEa2TE=", "checksumSHA1": "1zB843WyoSh8oMdbeDfgByEa2TE=",
"path": "golang.org/x/crypto/chacha20poly1305", "path": "golang.org/x/crypto/chacha20poly1305",

View File

@ -435,6 +435,12 @@ can be set in a CSR are supported.
- `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative - `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative
Names, in a comma-delimited list. Names, in a comma-delimited list.
- `other_sans` `(string: "")`  Specifies custom OID/UTF8-string SANs. These
must match values specified on the role in `allowed_other_sans` (globbing
allowed). The format is the same as OpenSSL: `<oid>;<type>:<value>` where the
only current valid type is `UTF8`. This can be a comma-delimited list or a
JSON string slice.
- `format` `(string: "")`  Specifies the format for returned data. This can be - `format` `(string: "")`  Specifies the format for returned data. This can be
`pem`, `der`, or `pem_bundle`; defaults to `pem`. If `der`, the output is `pem`, `der`, or `pem_bundle`; defaults to `pem`. If `der`, the output is
base64 encoded. If `pem_bundle`, the `csr` field will contain the private key base64 encoded. If `pem_bundle`, the `csr` field will contain the private key
@ -456,6 +462,34 @@ can be set in a CSR are supported.
Useful if the CN is not a hostname or email address, but is instead some Useful if the CN is not a hostname or email address, but is instead some
human-readable identifier. human-readable identifier.
- `ou` `(string: "")`  Specifies the OU (OrganizationalUnit) values in the
subject field of the resulting CSR. This is a comma-separated string
or JSON array.
- `organization` `(string: "")`  Specifies the O (Organization) values in the
subject field of the resulting CSR. This is a comma-separated string
or JSON array.
- `country` `(string: "")`  Specifies the C (Country) values in the subject
field of the resulting CSR. This is a comma-separated string or JSON
array.
- `locality` `(string: "")`  Specifies the L (Locality) values in the subject
field of the resulting CSR. This is a comma-separated string or JSON
array.
- `province` `(string: "")`  Specifies the ST (Province) values in the subject
field of the resulting CSR. This is a comma-separated string or JSON
array.
- `street_address` `(string: "")`  Specifies the Street Address values in the
subject field of the resulting CSR. This is a comma-separated string
or JSON array.
- `postal_code` `(string: "")`  Specifies the Postal Code values in the
subject field of the resulting CSR. This is a comma-separated string
or JSON array.
### Sample Payload ### Sample Payload
```json ```json
@ -553,6 +587,12 @@ need to request a new certificate.**
in a comma-delimited list. Only valid if the role allows IP SANs (which is the in a comma-delimited list. Only valid if the role allows IP SANs (which is the
default). default).
- `other_sans` `(string: "")`  Specifies custom OID/UTF8-string SANs. These
must match values specified on the role in `allowed_other_sans` (globbing
allowed). The format is the same as OpenSSL: `<oid>;<type>:<value>` where the
only current valid type is `UTF8`. This can be a comma-delimited list or a
JSON string slice.
- `ttl` `(string: "")` Specifies requested Time To Live. Cannot be greater - `ttl` `(string: "")` Specifies requested Time To Live. Cannot be greater
than the role's `max_ttl` value. If not provided, the role's `ttl` value will than the role's `max_ttl` value. If not provided, the role's `ttl` value will
be used. Note that the role values default to system values if not explicitly be used. Note that the role values default to system values if not explicitly
@ -720,6 +760,11 @@ request is denied.
Alternative Names. No authorization checking is performed except to verify Alternative Names. No authorization checking is performed except to verify
that the given values are valid IP addresses. that the given values are valid IP addresses.
- `allowed_other_sans` `(string: "")`  Defines allowed custom OID/UTF8-string
SANs. This field supports globbing. The format is the same as OpenSSL:
`<oid>;<type>:<value>` where the only current valid type is `UTF8`. This can
be a comma-delimited list or a JSON string slice.
- `server_flag` `(bool: true)`  Specifies if certificates are flagged for - `server_flag` `(bool: true)`  Specifies if certificates are flagged for
server use. server use.
@ -757,10 +802,32 @@ request is denied.
`use_csr_common_name` for that. `use_csr_common_name` for that.
- `ou` `(string: "")`  Specifies the OU (OrganizationalUnit) values in the - `ou` `(string: "")`  Specifies the OU (OrganizationalUnit) values in the
subject field of issued certificates. This is a comma-separated string. subject field of issued certificates. This is a comma-separated string or
JSON array.
- `organization` `(string: "")`  Specifies the O (Organization) values in the - `organization` `(string: "")`  Specifies the O (Organization) values in the
subject field of issued certificates. This is a comma-separated string. subject field of issued certificates. This is a comma-separated string or
JSON array.
- `country` `(string: "")`  Specifies the C (Country) values in the
subject field of issued certificates. This is a comma-separated string or
JSON array.
- `locality` `(string: "")`  Specifies the L (Locality) values in the
subject field of issued certificates. This is a comma-separated string or
JSON array.
- `province` `(string: "")`  Specifies the ST (Province) values in the
subject field of issued certificates. This is a comma-separated string or
JSON array.
- `street_address` `(string: "")`  Specifies the Street Address values in the
subject field of issued certificates. This is a comma-separated string or
JSON array.
- `postal_code` `(string: "")`  Specifies the Postal Code values in the
subject field of issued certificates. This is a comma-separated string or
JSON array.
- `generate_lease` `(bool: false)`  Specifies if certificates issued/signed - `generate_lease` `(bool: false)`  Specifies if certificates issued/signed
against this role will have Vault leases attached to them. Certificates can be against this role will have Vault leases attached to them. Certificates can be
@ -935,6 +1002,12 @@ existing cert/key with new values.
- `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative - `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative
Names, in a comma-delimited list. Names, in a comma-delimited list.
- `other_sans` `(string: "")`  Specifies custom OID/UTF8-string SANs. These
must match values specified on the role in `allowed_other_sans` (globbing
allowed). The format is the same as OpenSSL: `<oid>;<type>:<value>` where the
only current valid type is `UTF8`. This can be a comma-delimited list or a
JSON string slice.
- `ttl` `(string: "")`  Specifies the requested Time To Live (after which the - `ttl` `(string: "")`  Specifies the requested Time To Live (after which the
certificate will be expired). This cannot be larger than the engine's max (or, certificate will be expired). This cannot be larger than the engine's max (or,
if not set, the system max). if not set, the system max).
@ -973,6 +1046,34 @@ existing cert/key with new values.
the domain, as per the domain, as per
[RFC](https://tools.ietf.org/html/rfc5280#section-4.2.1.10). [RFC](https://tools.ietf.org/html/rfc5280#section-4.2.1.10).
- `ou` `(string: "")`  Specifies the OU (OrganizationalUnit) values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
- `organization` `(string: "")`  Specifies the O (Organization) values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
- `country` `(string: "")`  Specifies the C (Country) values in the subject
field of the resulting certificate. This is a comma-separated string or JSON
array.
- `locality` `(string: "")`  Specifies the L (Locality) values in the subject
field of the resulting certificate. This is a comma-separated string or JSON
array.
- `province` `(string: "")`  Specifies the ST (Province) values in the subject
field of the resulting certificate. This is a comma-separated string or JSON
array.
- `street_address` `(string: "")`  Specifies the Street Address values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
- `postal_code` `(string: "")`  Specifies the Postal Code values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
### Sample Payload ### Sample Payload
```json ```json
@ -1053,6 +1154,12 @@ verbatim.
- `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative - `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative
Names, in a comma-delimited list. Names, in a comma-delimited list.
- `other_sans` `(string: "")`  Specifies custom OID/UTF8-string SANs. These
must match values specified on the role in `allowed_other_sans` (globbing
allowed). The format is the same as OpenSSL: `<oid>;<type>:<value>` where the
only current valid type is `UTF8`. This can be a comma-delimited list or a
JSON string slice.
- `ttl` `(string: "")`  Specifies the requested Time To Live (after which the - `ttl` `(string: "")`  Specifies the requested Time To Live (after which the
certificate will be expired). This cannot be larger than the engine's max (or, certificate will be expired). This cannot be larger than the engine's max (or,
if not set, the system max). However, this can be after the expiration of the if not set, the system max). However, this can be after the expiration of the
@ -1089,6 +1196,35 @@ verbatim.
the domain, as per the domain, as per
[RFC](https://tools.ietf.org/html/rfc5280#section-4.2.1.10). [RFC](https://tools.ietf.org/html/rfc5280#section-4.2.1.10).
- `ou` `(string: "")`  Specifies the OU (OrganizationalUnit) values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
- `organization` `(string: "")`  Specifies the O (Organization) values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
- `country` `(string: "")`  Specifies the C (Country) values in the subject
field of the resulting certificate. This is a comma-separated string or JSON
array.
- `locality` `(string: "")`  Specifies the L (Locality) values in the subject
field of the resulting certificate. This is a comma-separated string or JSON
array.
- `province` `(string: "")`  Specifies the ST (Province) values in the subject
field of the resulting certificate. This is a comma-separated string or JSON
array.
- `street_address` `(string: "")`  Specifies the Street Address values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
- `postal_code` `(string: "")`  Specifies the Postal Code values in the
subject field of the resulting certificate. This is a comma-separated string
or JSON array.
### Sample Payload ### Sample Payload
```json ```json
@ -1207,6 +1343,12 @@ root CA need be in a client's trust store.
they will be parsed into their respective fields. If any requested names do they will be parsed into their respective fields. If any requested names do
not match role policy, the entire request will be denied. not match role policy, the entire request will be denied.
- `other_sans` `(string: "")`  Specifies custom OID/UTF8-string SANs. These
must match values specified on the role in `allowed_other_sans` (globbing
allowed). The format is the same as OpenSSL: `<oid>;<type>:<value>` where the
only current valid type is `UTF8`. This can be a comma-delimited list or a
JSON string slice.
- `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative - `ip_sans` `(string: "")`  Specifies the requested IP Subject Alternative
Names, in a comma-delimited list. Only valid if the role allows IP SANs (which Names, in a comma-delimited list. Only valid if the role allows IP SANs (which
is the default). is the default).