istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/util/san.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package util 16 17 import ( 18 "crypto/x509/pkix" 19 "encoding/asn1" 20 "fmt" 21 "net/netip" 22 "strings" 23 24 "istio.io/istio/pkg/spiffe" 25 ) 26 27 // IdentityType represents type of an identity. This is used to properly encode 28 // an identity into a SAN extension. 29 type IdentityType int 30 31 const ( 32 // TypeDNS represents a DNS name. 33 TypeDNS IdentityType = iota 34 // TypeIP represents an IP address. 35 TypeIP 36 // TypeURI represents a universal resource identifier. 37 TypeURI 38 ) 39 40 var ( 41 // Mapping from the type of an identity to the OID tag value for the X.509 42 // SAN field (see https://tools.ietf.org/html/rfc5280#appendix-A.2) 43 // 44 // SubjectAltName ::= GeneralNames 45 // 46 // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName 47 // 48 // GeneralName ::= CHOICE { 49 // dNSName [2] IA5String, 50 // uniformResourceIdentifier [6] IA5String, 51 // iPAddress [7] OCTET STRING, 52 // } 53 oidTagMap = map[IdentityType]int{ 54 TypeDNS: 2, 55 TypeURI: 6, 56 TypeIP: 7, 57 } 58 59 // A reversed map that maps from an OID tag to the corresponding identity 60 // type. 61 identityTypeMap = generateReversedMap(oidTagMap) 62 63 // The OID for the SAN extension (See 64 // http://www.alvestrand.no/objectid/2.5.29.17.html). 65 oidSubjectAlternativeName = asn1.ObjectIdentifier{2, 5, 29, 17} 66 ) 67 68 // Identity is an object holding both the encoded identifier bytes as well as 69 // the type of the identity. 70 type Identity struct { 71 Type IdentityType 72 Value []byte 73 } 74 75 // BuildSubjectAltNameExtension builds the SAN extension for the certificate. 76 func BuildSubjectAltNameExtension(hosts string) (*pkix.Extension, error) { 77 ids := []Identity{} 78 for _, host := range strings.Split(hosts, ",") { 79 if ipa, _ := netip.ParseAddr(host); ipa.IsValid() { 80 // Use the 4-byte representation of the IP address when possible. 81 ip := ipa.AsSlice() 82 if ipa.Is4In6() { 83 eip := ipa.As4() 84 ip = eip[:] 85 } 86 ids = append(ids, Identity{Type: TypeIP, Value: ip}) 87 } else if strings.HasPrefix(host, spiffe.URIPrefix) { 88 ids = append(ids, Identity{Type: TypeURI, Value: []byte(host)}) 89 } else { 90 ids = append(ids, Identity{Type: TypeDNS, Value: []byte(host)}) 91 } 92 } 93 94 san, err := BuildSANExtension(ids) 95 if err != nil { 96 return nil, fmt.Errorf("SAN extension building failure (%v)", err) 97 } 98 99 return san, nil 100 } 101 102 // BuildSANExtension builds a `pkix.Extension` of type "Subject 103 // Alternative Name" based on the given identities. 104 func BuildSANExtension(identites []Identity) (*pkix.Extension, error) { 105 rawValues := []asn1.RawValue{} 106 for _, i := range identites { 107 tag, ok := oidTagMap[i.Type] 108 if !ok { 109 return nil, fmt.Errorf("unsupported identity type: %v", i.Type) 110 } 111 112 rawValues = append(rawValues, asn1.RawValue{ 113 Bytes: i.Value, 114 Class: asn1.ClassContextSpecific, 115 Tag: tag, 116 }) 117 } 118 119 bs, err := asn1.Marshal(rawValues) 120 if err != nil { 121 return nil, fmt.Errorf("failed to marshal the raw values for SAN field (err: %s)", err) 122 } 123 124 // SAN is Critical because the subject is empty. This is compliant with X.509 and SPIFFE standards. 125 return &pkix.Extension{Id: oidSubjectAlternativeName, Critical: true, Value: bs}, nil 126 } 127 128 // ExtractIDsFromSAN takes a SAN extension and extracts the identities. 129 // The logic is mostly borrowed from 130 // https://github.com/golang/go/blob/master/src/crypto/x509/x509.go, with the 131 // addition of supporting extracting URIs. 132 func ExtractIDsFromSAN(sanExt *pkix.Extension) ([]Identity, error) { 133 if !sanExt.Id.Equal(oidSubjectAlternativeName) { 134 return nil, fmt.Errorf("the input is not a SAN extension") 135 } 136 137 var sequence asn1.RawValue 138 if rest, err := asn1.Unmarshal(sanExt.Value, &sequence); err != nil { 139 return nil, err 140 } else if len(rest) != 0 { 141 return nil, fmt.Errorf("the SAN extension is incorrectly encoded") 142 } 143 144 // Check the rawValue is a sequence. 145 if !sequence.IsCompound || sequence.Tag != asn1.TagSequence || sequence.Class != asn1.ClassUniversal { 146 return nil, fmt.Errorf("the SAN extension is incorrectly encoded") 147 } 148 149 ids := []Identity{} 150 for bytes := sequence.Bytes; len(bytes) > 0; { 151 var rawValue asn1.RawValue 152 var err error 153 154 bytes, err = asn1.Unmarshal(bytes, &rawValue) 155 if err != nil { 156 return nil, err 157 } 158 ids = append(ids, Identity{Type: identityTypeMap[rawValue.Tag], Value: rawValue.Bytes}) 159 } 160 161 return ids, nil 162 } 163 164 // ExtractSANExtension extracts the "Subject Alternative Name" externsion from 165 // the given PKIX extension set. 166 func ExtractSANExtension(exts []pkix.Extension) *pkix.Extension { 167 for _, ext := range exts { 168 if ext.Id.Equal(oidSubjectAlternativeName) { 169 // We don't need to examine other extensions anymore since a certificate 170 // must not include more than one instance of a particular extension. See 171 // https://tools.ietf.org/html/rfc5280#section-4.2. 172 return &ext 173 } 174 } 175 return nil 176 } 177 178 // ExtractIDs first finds the SAN extension from the given extension set, then 179 // extract identities from the SAN extension. 180 func ExtractIDs(exts []pkix.Extension) ([]string, error) { 181 sanExt := ExtractSANExtension(exts) 182 if sanExt == nil { 183 return nil, fmt.Errorf("the SAN extension does not exist") 184 } 185 186 idsWithType, err := ExtractIDsFromSAN(sanExt) 187 if err != nil { 188 return nil, fmt.Errorf("failed to extract identities from SAN extension (error %v)", err) 189 } 190 191 ids := []string{} 192 for _, id := range idsWithType { 193 ids = append(ids, string(id.Value)) 194 } 195 return ids, nil 196 } 197 198 func generateReversedMap(m map[IdentityType]int) map[int]IdentityType { 199 reversed := make(map[int]IdentityType) 200 for key, value := range m { 201 reversed[value] = key 202 } 203 return reversed 204 }