consul/agent/connect/x509_patch.go

130 lines
3.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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
}