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