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 }