c3d5a2a5ab
As part of this change, we ensure that the SAN extensions are marked as critical when the subject is empty so that AWS PCA tolerates the loss of common names well and continues to function as a Connect CA provider. Parts of this currently hack around a bug in crypto/x509 and can be removed after https://go-review.googlesource.com/c/go/+/329129 lands in a Go release. Note: the AWS PCA tests do not run automatically, but the following passed locally for me: ENABLE_AWS_PCA_TESTS=1 go test ./agent/connect/ca -run TestAWS
127 lines
3.6 KiB
Go
127 lines
3.6 KiB
Go
package connect
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"unicode"
|
|
)
|
|
|
|
// NOTE: the contents of this file were lifted from
|
|
// $GOROOT/src/crypto/x509/x509.go from a Go 1.16.5 checkout.
|
|
//
|
|
//
|
|
// After https://go-review.googlesource.com/c/go/+/329129 lands in a Go release
|
|
// we are compiling against we can safely remove all of this code.
|
|
|
|
var (
|
|
x509_oidExtensionSubjectAltName = []int{2, 5, 29, 17}
|
|
)
|
|
|
|
const (
|
|
x509_nameTypeEmail = 1
|
|
x509_nameTypeDNS = 2
|
|
x509_nameTypeURI = 6
|
|
x509_nameTypeIP = 7
|
|
)
|
|
|
|
// HackSANExtensionForCSR will create a SAN extension on the CSR off of the
|
|
// convenience fields (DNSNames, EmailAddresses, IPAddresses, URIs) and
|
|
// appropriately marks that SAN extension as critical if the CSR has an empty
|
|
// subject.
|
|
//
|
|
// This is basically attempting to repeat this blob of code from the stdlib
|
|
// ourselves:
|
|
//
|
|
// https://github.com/golang/go/blob/0e67ce3d28320e816dd8e7cf7d701c1804fb977e/src/crypto/x509/x509.go#L1088
|
|
func HackSANExtensionForCSR(template *x509.CertificateRequest) {
|
|
switch {
|
|
case len(template.DNSNames) > 0:
|
|
case len(template.EmailAddresses) > 0:
|
|
case len(template.IPAddresses) > 0:
|
|
case len(template.URIs) > 0:
|
|
default:
|
|
return
|
|
}
|
|
|
|
if x509_oidInExtensions(x509_oidExtensionSubjectAltName, template.ExtraExtensions) {
|
|
return
|
|
}
|
|
|
|
value, err := x509_marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses, template.URIs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ext := pkix.Extension{
|
|
Id: x509_oidExtensionSubjectAltName,
|
|
// From RFC 5280, Section 4.2.1.6:
|
|
// “If the subject field contains an empty sequence ... then
|
|
// subjectAltName extension ... is marked as critical”
|
|
//
|
|
// Since we just cleared the subject above, it's critical.
|
|
Critical: true,
|
|
Value: value,
|
|
}
|
|
template.ExtraExtensions = append(template.ExtraExtensions, ext)
|
|
}
|
|
|
|
// x509_oidInExtensions reports whether an extension with the given oid exists in
|
|
// extensions.
|
|
func x509_oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) bool {
|
|
for _, e := range extensions {
|
|
if e.Id.Equal(oid) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// x509_marshalSANs marshals a list of addresses into a the contents of an X.509
|
|
// SubjectAlternativeName extension.
|
|
func x509_marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) (derBytes []byte, err error) {
|
|
var rawValues []asn1.RawValue
|
|
for _, name := range dnsNames {
|
|
if err := x509_isIA5String(name); err != nil {
|
|
return nil, err
|
|
}
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: x509_nameTypeDNS, Class: 2, Bytes: []byte(name)})
|
|
}
|
|
for _, email := range emailAddresses {
|
|
if err := x509_isIA5String(email); err != nil {
|
|
return nil, err
|
|
}
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: x509_nameTypeEmail, Class: 2, Bytes: []byte(email)})
|
|
}
|
|
for _, rawIP := range ipAddresses {
|
|
// If possible, we always want to encode IPv4 addresses in 4 bytes.
|
|
ip := rawIP.To4()
|
|
if ip == nil {
|
|
ip = rawIP
|
|
}
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: x509_nameTypeIP, Class: 2, Bytes: ip})
|
|
}
|
|
for _, uri := range uris {
|
|
uriStr := uri.String()
|
|
if err := x509_isIA5String(uriStr); err != nil {
|
|
return nil, err
|
|
}
|
|
rawValues = append(rawValues, asn1.RawValue{Tag: x509_nameTypeURI, Class: 2, Bytes: []byte(uriStr)})
|
|
}
|
|
return asn1.Marshal(rawValues)
|
|
}
|
|
|
|
func x509_isIA5String(s string) error {
|
|
for _, r := range s {
|
|
// Per RFC5280 "IA5String is limited to the set of ASCII characters"
|
|
if r > unicode.MaxASCII {
|
|
return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|