@@ -2,13 +2,12 @@ package ldap
2
2
3
3
import (
4
4
"bytes"
5
- enchex "encoding/hex"
5
+ "encoding/asn1"
6
+ "encoding/hex"
6
7
"errors"
7
8
"fmt"
8
9
"sort"
9
10
"strings"
10
-
11
- ber "github.com/go-asn1-ber/asn1-ber"
12
11
)
13
12
14
13
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
@@ -19,8 +18,43 @@ type AttributeTypeAndValue struct {
19
18
Value string
20
19
}
21
20
21
+ func (a * AttributeTypeAndValue ) setType (str string ) error {
22
+ result , err := decodeString (str )
23
+ if err != nil {
24
+ return err
25
+ }
26
+ a .Type = result
27
+
28
+ return nil
29
+ }
30
+
31
+ func (a * AttributeTypeAndValue ) setValue (s string ) error {
32
+ // https://www.ietf.org/rfc/rfc4514.html#section-2.4
33
+ // If the AttributeType is of the dotted-decimal form, the
34
+ // AttributeValue is represented by an number sign ('#' U+0023)
35
+ // character followed by the hexadecimal encoding of each of the octets
36
+ // of the BER encoding of the X.500 AttributeValue.
37
+ if len (s ) > 0 && s [0 ] == '#' {
38
+ decodedString , err := decodeEncodedString (s [1 :])
39
+ if err != nil {
40
+ return err
41
+ }
42
+
43
+ a .Value = decodedString
44
+ return nil
45
+ } else {
46
+ decodedString , err := decodeString (s )
47
+ if err != nil {
48
+ return err
49
+ }
50
+
51
+ a .Value = decodedString
52
+ return nil
53
+ }
54
+ }
55
+
22
56
// String returns a normalized string representation of this attribute type and
23
- // value pair which is the a lowercased join of the Type and Value with a "=".
57
+ // value pair which is the lowercase join of the Type and Value with a "=".
24
58
func (a * AttributeTypeAndValue ) String () string {
25
59
return strings .ToLower (a .Type ) + "=" + a .encodeValue ()
26
60
}
@@ -39,7 +73,7 @@ func (a *AttributeTypeAndValue) encodeValue() string {
39
73
40
74
escapeHex := func (c byte ) {
41
75
encodedBuf .WriteByte ('\\' )
42
- encodedBuf .WriteString (enchex .EncodeToString ([]byte {c }))
76
+ encodedBuf .WriteString (hex .EncodeToString ([]byte {c }))
43
77
}
44
78
45
79
for i := 0 ; i < len (value ); i ++ {
@@ -108,114 +142,152 @@ func (d *DN) String() string {
108
142
return strings .Join (rdns , "," )
109
143
}
110
144
145
+ // Remove leading and trailing spaces from the attribute type and value
146
+ // and unescape any escaped characters in these fields
147
+ //
148
+ // decodeString is based on https://github.com/inteon/cert-manager/blob/ed280d28cd02b262c5db46054d88e70ab518299c/pkg/util/pki/internal/dn.go#L170
149
+ func decodeString (str string ) (string , error ) {
150
+ s := []rune (strings .TrimSpace (str ))
151
+ // Re-add the trailing space if the last character was an escaped space character
152
+ if len (s ) > 0 && s [len (s )- 1 ] == '\\' && str [len (str )- 2 ] == ' ' {
153
+ s = append (s , ' ' )
154
+ }
155
+
156
+ builder := strings.Builder {}
157
+ for i := 0 ; i < len (s ); i ++ {
158
+ char := s [i ]
159
+
160
+ // If the character is not an escape character, just add it to the
161
+ // builder and continue
162
+ if char != '\\' {
163
+ builder .WriteRune (char )
164
+ continue
165
+ }
166
+
167
+ // If the escape character is the last character, it's a corrupted
168
+ // escaped character
169
+ if i + 1 >= len (s ) {
170
+ return "" , fmt .Errorf ("got corrupted escaped character: '%s'" , string (s ))
171
+ }
172
+
173
+ // If the escaped character is a special character, just add it to
174
+ // the builder and continue
175
+ switch s [i + 1 ] {
176
+ case ' ' , '"' , '#' , '+' , ',' , ';' , '<' , '=' , '>' , '\\' :
177
+ builder .WriteRune (s [i + 1 ])
178
+ i ++
179
+ continue
180
+ }
181
+
182
+ // If the escaped character is not a special character, it should
183
+ // be a hex-encoded character of the form \XX if it's not at least
184
+ // two characters long, it's a corrupted escaped character
185
+ if i + 2 >= len (s ) {
186
+ return "" , errors .New ("failed to decode escaped character: encoding/hex: invalid byte: " + string (s [i + 1 ]))
187
+ }
188
+
189
+ // Get the runes for the two characters after the escape character
190
+ // and convert them to a byte slice
191
+ xx := []byte (string (s [i + 1 : i + 3 ]))
192
+
193
+ // If the two runes are not hex characters and result in more than
194
+ // two bytes when converted to a byte slice, it's a corrupted
195
+ // escaped character
196
+ if len (xx ) != 2 {
197
+ return "" , errors .New ("failed to decode escaped character: invalid byte: " + string (xx ))
198
+ }
199
+
200
+ // Decode the hex-encoded character and add it to the builder
201
+ dst := []byte {0 }
202
+ if n , err := hex .Decode (dst , xx ); err != nil {
203
+ return "" , errors .New ("failed to decode escaped character: " + err .Error ())
204
+ } else if n != 1 {
205
+ return "" , fmt .Errorf ("failed to decode escaped character: encoding/hex: expected 1 byte when un-escaping, got %d" , n )
206
+ }
207
+
208
+ builder .WriteByte (dst [0 ])
209
+ i += 2
210
+ }
211
+
212
+ return builder .String (), nil
213
+ }
214
+
215
+ func decodeEncodedString (str string ) (string , error ) {
216
+ decoded , err := hex .DecodeString (str )
217
+ if err != nil {
218
+ return "" , fmt .Errorf ("failed to decode BER encoding: %s" , err )
219
+ }
220
+
221
+ var rawValue asn1.RawValue
222
+ result , err := asn1 .Unmarshal (decoded , & rawValue )
223
+ if err != nil {
224
+ return "" , fmt .Errorf ("failed to unmarshal hex-encoded string: %s" , err )
225
+ }
226
+ if len (result ) != 0 {
227
+ return "" , errors .New ("trailing data after unmarshalling hex-encoded string" )
228
+ }
229
+
230
+ return string (rawValue .Bytes ), nil
231
+ }
232
+
111
233
// ParseDN returns a distinguishedName or an error.
112
234
// The function respects https://tools.ietf.org/html/rfc4514
113
235
func ParseDN (str string ) (* DN , error ) {
114
- dn := new (DN )
115
- dn .RDNs = make ([]* RelativeDN , 0 )
116
- rdn := new (RelativeDN )
117
- rdn .Attributes = make ([]* AttributeTypeAndValue , 0 )
118
- buffer := bytes.Buffer {}
119
- attribute := new (AttributeTypeAndValue )
120
- escaping := false
121
-
122
- unescapedTrailingSpaces := 0
123
- stringFromBuffer := func () string {
124
- s := buffer .String ()
125
- s = s [0 : len (s )- unescapedTrailingSpaces ]
126
- buffer .Reset ()
127
- unescapedTrailingSpaces = 0
128
- return s
236
+ var dn = & DN {RDNs : make ([]* RelativeDN , 0 )}
237
+ if str = strings .TrimSpace (str ); len (str ) == 0 {
238
+ return dn , nil
129
239
}
130
240
241
+ var (
242
+ rdn = & RelativeDN {}
243
+ attr = & AttributeTypeAndValue {}
244
+ escaping bool
245
+ startPos int
246
+ appendAttributesToRDN = func (end bool ) {
247
+ rdn .Attributes = append (rdn .Attributes , attr )
248
+ attr = & AttributeTypeAndValue {}
249
+ if end {
250
+ dn .RDNs = append (dn .RDNs , rdn )
251
+ rdn = & RelativeDN {}
252
+ }
253
+ }
254
+ )
255
+
131
256
for i := 0 ; i < len (str ); i ++ {
132
257
char := str [i ]
133
258
switch {
134
259
case escaping :
135
- unescapedTrailingSpaces = 0
136
260
escaping = false
137
- switch char {
138
- case ' ' , '"' , '#' , '+' , ',' , ';' , '<' , '=' , '>' , '\\' :
139
- buffer .WriteByte (char )
140
- continue
141
- }
142
- // Not a special character, assume hex encoded octet
143
- if len (str ) == i + 1 {
144
- return nil , errors .New ("got corrupted escaped character" )
145
- }
146
-
147
- dst := []byte {0 }
148
- n , err := enchex .Decode ([]byte (dst ), []byte (str [i :i + 2 ]))
149
- if err != nil {
150
- return nil , fmt .Errorf ("failed to decode escaped character: %s" , err )
151
- } else if n != 1 {
152
- return nil , fmt .Errorf ("expected 1 byte when un-escaping, got %d" , n )
153
- }
154
- buffer .WriteByte (dst [0 ])
155
- i ++
156
261
case char == '\\' :
157
- unescapedTrailingSpaces = 0
158
262
escaping = true
159
- case char == '=' && attribute .Type == "" :
160
- attribute .Type = stringFromBuffer ()
161
- // Special case: If the first character in the value is # the
162
- // following data is BER encoded so we can just fast forward
163
- // and decode.
164
- if len (str ) > i + 1 && str [i + 1 ] == '#' {
165
- i += 2
166
- index := strings .IndexAny (str [i :], ",+" )
167
- var data string
168
- if index > 0 {
169
- data = str [i : i + index ]
170
- } else {
171
- data = str [i :]
172
- }
173
- rawBER , err := enchex .DecodeString (data )
174
- if err != nil {
175
- return nil , fmt .Errorf ("failed to decode BER encoding: %s" , err )
176
- }
177
- packet , err := ber .DecodePacketErr (rawBER )
178
- if err != nil {
179
- return nil , fmt .Errorf ("failed to decode BER packet: %s" , err )
180
- }
181
- buffer .WriteString (packet .Data .String ())
182
- i += len (data ) - 1
263
+ case char == '=' && len (attr .Type ) == 0 :
264
+ if err := attr .setType (str [startPos :i ]); err != nil {
265
+ return nil , err
183
266
}
267
+ startPos = i + 1
184
268
case char == ',' || char == '+' || char == ';' :
185
- // We're done with this RDN or value, push it
186
- if len (attribute .Type ) == 0 {
187
- return nil , errors .New ("incomplete type, value pair" )
269
+ if len (attr .Type ) == 0 {
270
+ return dn , errors .New ("incomplete type, value pair" )
188
271
}
189
- attribute .Value = stringFromBuffer ()
190
- rdn .Attributes = append (rdn .Attributes , attribute )
191
- attribute = new (AttributeTypeAndValue )
192
- if char == ',' || char == ';' {
193
- dn .RDNs = append (dn .RDNs , rdn )
194
- rdn = new (RelativeDN )
195
- rdn .Attributes = make ([]* AttributeTypeAndValue , 0 )
272
+ if err := attr .setValue (str [startPos :i ]); err != nil {
273
+ return nil , err
196
274
}
197
- case char == ' ' && buffer .Len () == 0 :
198
- // ignore unescaped leading spaces
199
- continue
200
- default :
201
- if char == ' ' {
202
- // Track unescaped spaces in case they are trailing and we need to remove them
203
- unescapedTrailingSpaces ++
204
- } else {
205
- // Reset if we see a non-space char
206
- unescapedTrailingSpaces = 0
207
- }
208
- buffer .WriteByte (char )
275
+
276
+ startPos = i + 1
277
+ last := char == ',' || char == ';'
278
+ appendAttributesToRDN (last )
209
279
}
210
280
}
211
- if buffer .Len () > 0 {
212
- if len (attribute .Type ) == 0 {
213
- return nil , errors .New ("DN ended with incomplete type, value pair" )
214
- }
215
- attribute .Value = stringFromBuffer ()
216
- rdn .Attributes = append (rdn .Attributes , attribute )
217
- dn .RDNs = append (dn .RDNs , rdn )
281
+
282
+ if len (attr .Type ) == 0 {
283
+ return dn , errors .New ("DN ended with incomplete type, value pair" )
218
284
}
285
+
286
+ if err := attr .setValue (str [startPos :]); err != nil {
287
+ return dn , err
288
+ }
289
+ appendAttributesToRDN (true )
290
+
219
291
return dn , nil
220
292
}
221
293
0 commit comments