consul/agent/connect/x509_patch.go

130 lines
3.6 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// 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
}