github.com/Venafi/vcert/v5@v5.10.2/pkg/certificate/x509SubjectAltName.go (about)

     1  /*
     2   * Copyright 2022 Venafi, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *  http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package certificate
    18  
    19  import (
    20  	"crypto/x509"
    21  	"crypto/x509/pkix"
    22  	"encoding/asn1"
    23  	"fmt"
    24  	"log"
    25  	"net"
    26  	"net/url"
    27  )
    28  
    29  // userPrincipalName format for ASN.1
    30  type userPrincipalName struct {
    31  	Name string `asn1:"utf8"`
    32  }
    33  
    34  // otherName SAN value format for ASN.1
    35  type otherName struct {
    36  	OID   asn1.ObjectIdentifier
    37  	Value userPrincipalName `asn1:"tag:0"`
    38  }
    39  
    40  var (
    41  	oidExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
    42  	oidUserPrincipalName       = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3}
    43  )
    44  
    45  const (
    46  	nameTypeOther = 0
    47  	nameTypeEmail = 1
    48  	nameTypeDNS   = 2
    49  	nameTypeURI   = 6
    50  	nameTypeIP    = 7
    51  )
    52  
    53  // Workaround for lack of User Principal Name SAN support and ability to control SAN extension criticality in crypto/x509 package
    54  func addSubjectAltNames(req *x509.CertificateRequest, dnsNames []string, emailAddrs []string, ipAddrs []net.IP, URIs []*url.URL, UPNs []string) {
    55  	sanBytes, err := marshalSANs(dnsNames, emailAddrs, ipAddrs, URIs, UPNs)
    56  	if err != nil {
    57  		log.Fatal(err)
    58  	}
    59  
    60  	// Per RFC 5280, subjectAltName extension MUST be critical if Subject is empty
    61  	// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6
    62  	sanExtCritical := req.Subject.String() == ""
    63  
    64  	extSubjectAltName := pkix.Extension{
    65  		Id:       oidExtensionSubjectAltName,
    66  		Critical: sanExtCritical,
    67  		Value:    sanBytes,
    68  	}
    69  
    70  	updatedExts := []pkix.Extension{extSubjectAltName}
    71  
    72  	// Preserve any other extra extensions, if any
    73  	for _, ext := range req.ExtraExtensions {
    74  		if !ext.Id.Equal(oidExtensionSubjectAltName) {
    75  			updatedExts = append(updatedExts, ext)
    76  		}
    77  	}
    78  	req.ExtraExtensions = updatedExts
    79  
    80  	// Ensure SAN request attributes are not set to prevent SAN extension from being clobbered when CSR is generated
    81  	req.DNSNames = nil
    82  	req.EmailAddresses = nil
    83  	req.IPAddresses = nil
    84  	req.URIs = nil
    85  }
    86  
    87  // Enhance crypto/x509 marshalSANs method to additionally support User Principal Name SANs
    88  // Based on https://github.com/golang/go/blob/master/src/crypto/x509/x509.go#L1656-L1678
    89  func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, uPNames []string) (derBytes []byte, err error) {
    90  	rawValues := make([]asn1.RawValue, 0)
    91  	for _, name := range dnsNames {
    92  		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: 2, Bytes: []byte(name)})
    93  	}
    94  	for _, email := range emailAddresses {
    95  		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: 2, Bytes: []byte(email)})
    96  	}
    97  	for _, rawIP := range ipAddresses {
    98  		// If possible, we always want to encode IPv4 addresses in 4 bytes.
    99  		ip := rawIP.To4()
   100  		if ip == nil {
   101  			ip = rawIP
   102  		}
   103  		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: 2, Bytes: ip})
   104  	}
   105  	for _, uri := range uris {
   106  		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uri.String())})
   107  	}
   108  	for _, upn := range uPNames {
   109  		var raw asn1.RawValue
   110  		name, _ := asn1.Marshal(otherName{
   111  			OID: oidUserPrincipalName,
   112  			Value: userPrincipalName{
   113  				Name: upn,
   114  			},
   115  		})
   116  		_, err = asn1.Unmarshal(name, &raw)
   117  		if err != nil {
   118  			return nil, fmt.Errorf("could not parse otherName SAN: %v", err)
   119  		}
   120  		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeOther, Class: 2, IsCompound: true, Bytes: raw.Bytes})
   121  	}
   122  
   123  	return asn1.Marshal(rawValues)
   124  }
   125  
   126  // Since crypto/x509 package is not aware of UPN SANs, implement our own parsing method
   127  func getUserPrincipalNameSANs(cert *x509.Certificate) (ret []string, err error) {
   128  	for _, ext := range cert.Extensions {
   129  		if !ext.Id.Equal(oidExtensionSubjectAltName) {
   130  			continue
   131  		}
   132  
   133  		var seq asn1.RawValue
   134  		rest, err := asn1.Unmarshal(ext.Value, &seq)
   135  		if err != nil {
   136  			return nil, err
   137  		} else if len(rest) != 0 {
   138  			return nil, fmt.Errorf("unexpected trailing data after X.509 extension")
   139  		}
   140  		if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
   141  			return nil, asn1.StructuralError{Msg: "bad ASN.1 sequence for SAN"}
   142  		}
   143  
   144  		rest = seq.Bytes
   145  		for len(rest) > 0 {
   146  			var v asn1.RawValue
   147  			rest, err = asn1.Unmarshal(rest, &v)
   148  			if err != nil {
   149  				return nil, err
   150  			}
   151  
   152  			upn, err := parseUserPrincipalNameSAN(v.Tag, v.FullBytes)
   153  			if err != nil {
   154  				return nil, err
   155  			}
   156  			if upn != "" {
   157  				ret = append(ret, upn)
   158  			}
   159  		}
   160  	}
   161  
   162  	return ret, nil
   163  }
   164  
   165  func parseUserPrincipalNameSAN(tag int, data []byte) (name string, err error) {
   166  	if tag != 0 {
   167  		return "", nil // SAN is not an otherName
   168  	}
   169  
   170  	var other otherName
   171  	_, err = asn1.UnmarshalWithParams(data, &other, "tag:0")
   172  	if err != nil {
   173  		return "", fmt.Errorf("could not parse otherName SAN: %v", err)
   174  	}
   175  	if other.OID.Equal(oidUserPrincipalName) {
   176  		return other.Value.Name, nil
   177  	}
   178  	return "", nil // otherName SAN is not a user principal name
   179  }