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