1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package x509tools
18
19 import (
20 "crypto/x509"
21 "encoding/asn1"
22 "encoding/binary"
23 "fmt"
24 "strings"
25 "unicode/utf16"
26 )
27
28 type rdnAttr struct {
29 Type asn1.ObjectIdentifier
30 Value asn1.RawValue
31 }
32
33 type rdnNameSet []rdnAttr
34
35 type NameStyle int
36
37 const (
38 NameStyleOpenSsl NameStyle = iota
39 NameStyleLdap
40 NameStyleMsOsco
41 )
42
43 type attrName struct {
44 Type asn1.ObjectIdentifier
45 Name string
46 }
47
48 var nameStyleLdap = []attrName{
49 attrName{asn1.ObjectIdentifier{2, 5, 4, 3}, "CN"},
50 attrName{asn1.ObjectIdentifier{2, 5, 4, 4}, "surname"},
51 attrName{asn1.ObjectIdentifier{2, 5, 4, 5}, "serialNumber"},
52 attrName{asn1.ObjectIdentifier{2, 5, 4, 6}, "C"},
53 attrName{asn1.ObjectIdentifier{2, 5, 4, 7}, "L"},
54 attrName{asn1.ObjectIdentifier{2, 5, 4, 8}, "ST"},
55 attrName{asn1.ObjectIdentifier{2, 5, 4, 9}, "street"},
56 attrName{asn1.ObjectIdentifier{2, 5, 4, 10}, "O"},
57 attrName{asn1.ObjectIdentifier{2, 5, 4, 11}, "OU"},
58 attrName{asn1.ObjectIdentifier{2, 5, 4, 12}, "title"},
59 attrName{asn1.ObjectIdentifier{2, 5, 4, 13}, "description"},
60 attrName{asn1.ObjectIdentifier{2, 5, 4, 17}, "postalCode"},
61 attrName{asn1.ObjectIdentifier{2, 5, 4, 18}, "postOfficeBox"},
62 attrName{asn1.ObjectIdentifier{2, 5, 4, 20}, "telephoneNumber"},
63 attrName{asn1.ObjectIdentifier{2, 5, 4, 42}, "givenName"},
64 attrName{asn1.ObjectIdentifier{2, 5, 4, 43}, "initials"},
65 attrName{asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}, "dc"},
66 attrName{asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, "emailAddress"},
67 }
68
69
70
71 var nameStyleMsOsco = []attrName{
72 attrName{asn1.ObjectIdentifier{2, 5, 4, 3}, "CN"},
73 attrName{asn1.ObjectIdentifier{2, 5, 4, 7}, "L"},
74 attrName{asn1.ObjectIdentifier{2, 5, 4, 10}, "O"},
75 attrName{asn1.ObjectIdentifier{2, 5, 4, 11}, "OU"},
76 attrName{asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, "E"},
77 attrName{asn1.ObjectIdentifier{2, 5, 4, 6}, "C"},
78 attrName{asn1.ObjectIdentifier{2, 5, 4, 8}, "S"},
79 attrName{asn1.ObjectIdentifier{2, 5, 4, 9}, "STREET"},
80 attrName{asn1.ObjectIdentifier{2, 5, 4, 12}, "T"},
81 attrName{asn1.ObjectIdentifier{2, 5, 4, 42}, "G"},
82 attrName{asn1.ObjectIdentifier{2, 5, 4, 43}, "I"},
83 attrName{asn1.ObjectIdentifier{2, 5, 4, 4}, "SN"},
84 attrName{asn1.ObjectIdentifier{2, 5, 4, 5}, "SERIALNUMBER"},
85 attrName{asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}, "DC"},
86 attrName{asn1.ObjectIdentifier{2, 5, 4, 13}, "Description"},
87 attrName{asn1.ObjectIdentifier{2, 5, 4, 17}, "PostalCode"},
88 attrName{asn1.ObjectIdentifier{2, 5, 4, 18}, "POBox"},
89 attrName{asn1.ObjectIdentifier{2, 5, 4, 20}, "Phone"},
90 }
91
92
93
94 const InvalidName = "<invalid>"
95
96
97 func FormatPkixName(der []byte, style NameStyle) string {
98 var seq asn1.RawValue
99 if _, err := asn1.Unmarshal(der, &seq); err != nil {
100 return InvalidName
101 }
102 seqbytes := seq.Bytes
103 var formatted []string
104 for len(seqbytes) > 0 {
105 var rdnSet rdnNameSet
106 var err error
107 seqbytes, err = asn1.UnmarshalWithParams(seqbytes, &rdnSet, "set")
108 if err != nil {
109 return InvalidName
110 }
111 for _, attr := range rdnSet {
112 formatted = append(formatted, fmt.Sprintf("%s=%s", attName(attr.Type, style), attValue(attr.Value, style)))
113 }
114 }
115 if len(formatted) == 0 {
116 return ""
117 }
118 switch style {
119 case NameStyleOpenSsl:
120 return "/" + strings.Join(formatted, "/") + "/"
121 case NameStyleLdap, NameStyleMsOsco:
122
123 for i := 0; i < len(formatted)/2; i++ {
124 j := len(formatted) - i - 1
125 formatted[i], formatted[j] = formatted[j], formatted[i]
126 }
127 return strings.Join(formatted, ", ")
128 default:
129 panic("invalid style argument")
130 }
131 }
132
133 func attName(t asn1.ObjectIdentifier, style NameStyle) string {
134 var names []attrName
135 var defaultPrefix string
136 switch style {
137 case NameStyleLdap, NameStyleOpenSsl:
138 names = nameStyleLdap
139 case NameStyleMsOsco:
140 names = nameStyleMsOsco
141 defaultPrefix = "OID."
142 default:
143 panic("invalid style argument")
144 }
145 for _, name := range names {
146 if name.Type.Equal(t) {
147 return name.Name
148 }
149 }
150 return defaultPrefix + t.String()
151 }
152
153 func attValue(raw asn1.RawValue, style NameStyle) string {
154 var value string
155 switch raw.Tag {
156 case asn1.TagUTF8String, asn1.TagIA5String, asn1.TagPrintableString:
157 var ret interface{}
158 if _, err := asn1.Unmarshal(raw.FullBytes, &ret); err != nil {
159 return InvalidName
160 }
161 value = ret.(string)
162 case Asn1TagBMPString:
163 value = ParseBMPString(raw)
164 default:
165 return InvalidName
166 }
167 switch style {
168 case NameStyleOpenSsl:
169 value = strings.Replace(value, "/", "\\/", -1)
170 case NameStyleLdap, NameStyleMsOsco:
171 quote := false
172 if len(value) == 0 {
173 quote = true
174 }
175 if strings.HasPrefix(value, " ") || strings.HasSuffix(value, " ") {
176 quote = true
177 }
178 if i := strings.IndexAny(value, ",+=\n<>#;'\""); i >= 0 {
179 quote = true
180 }
181 value = strings.Replace(value, "\"", "\"\"", -1)
182 if quote {
183 value = "\"" + value + "\""
184 }
185 }
186 return value
187 }
188
189 func ParseBMPString(raw asn1.RawValue) string {
190 runes := make([]uint16, len(raw.Bytes)/2)
191 for i := range runes {
192 runes[i] = binary.BigEndian.Uint16(raw.Bytes[i*2:])
193 }
194 return string(utf16.Decode(runes))
195 }
196
197 func ToBMPString(value string) asn1.RawValue {
198 runes := utf16.Encode([]rune(value))
199 raw := make([]byte, 2*len(runes))
200 for i, r := range runes {
201 binary.BigEndian.PutUint16(raw[i*2:], r)
202 }
203 return asn1.RawValue{Tag: Asn1TagBMPString, Bytes: raw}
204 }
205
206
207 func FormatSubject(cert *x509.Certificate) string {
208 return FormatPkixName(cert.RawSubject, NameStyleLdap)
209 }
210
211
212 func FormatIssuer(cert *x509.Certificate) string {
213 return FormatPkixName(cert.RawIssuer, NameStyleLdap)
214 }
215
View as plain text