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