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