1
1
package ldap
2
2
3
3
import (
4
- "bytes"
5
4
"encoding/asn1"
6
5
"encoding/hex"
7
6
"errors"
8
7
"fmt"
9
8
"sort"
10
9
"strings"
10
+ "unicode"
11
+ "unicode/utf8"
11
12
)
12
13
13
14
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
@@ -34,6 +35,9 @@ func (a *AttributeTypeAndValue) setValue(s string) error {
34
35
// AttributeValue is represented by an number sign ('#' U+0023)
35
36
// character followed by the hexadecimal encoding of each of the octets
36
37
// of the BER encoding of the X.500 AttributeValue.
38
+ //
39
+ // WARNING: we only support hex-encoded ASN.1 DER values here, not
40
+ // BER encoding. This is a deviation from the RFC.
37
41
if len (s ) > 0 && s [0 ] == '#' {
38
42
decodedString , err := decodeEncodedString (s [1 :])
39
43
if err != nil {
@@ -56,59 +60,7 @@ func (a *AttributeTypeAndValue) setValue(s string) error {
56
60
// String returns a normalized string representation of this attribute type and
57
61
// value pair which is the lowercase join of the Type and Value with a "=".
58
62
func (a * AttributeTypeAndValue ) String () string {
59
- return strings .ToLower (a .Type ) + "=" + a .encodeValue ()
60
- }
61
-
62
- func (a * AttributeTypeAndValue ) encodeValue () string {
63
- // Normalize the value first.
64
- // value := strings.ToLower(a.Value)
65
- value := a .Value
66
-
67
- encodedBuf := bytes.Buffer {}
68
-
69
- escapeChar := func (c byte ) {
70
- encodedBuf .WriteByte ('\\' )
71
- encodedBuf .WriteByte (c )
72
- }
73
-
74
- escapeHex := func (c byte ) {
75
- encodedBuf .WriteByte ('\\' )
76
- encodedBuf .WriteString (hex .EncodeToString ([]byte {c }))
77
- }
78
-
79
- for i := 0 ; i < len (value ); i ++ {
80
- char := value [i ]
81
- if i == 0 && char == ' ' || char == '#' {
82
- // Special case leading space or number sign.
83
- escapeChar (char )
84
- continue
85
- }
86
- if i == len (value )- 1 && char == ' ' {
87
- // Special case trailing space.
88
- escapeChar (char )
89
- continue
90
- }
91
-
92
- switch char {
93
- case '"' , '+' , ',' , ';' , '<' , '>' , '\\' :
94
- // Each of these special characters must be escaped.
95
- escapeChar (char )
96
- continue
97
- }
98
-
99
- if char < ' ' || char > '~' {
100
- // All special character escapes are handled first
101
- // above. All bytes less than ASCII SPACE and all bytes
102
- // greater than ASCII TILDE must be hex-escaped.
103
- escapeHex (char )
104
- continue
105
- }
106
-
107
- // Any other character does not require escaping.
108
- encodedBuf .WriteByte (char )
109
- }
110
-
111
- return encodedBuf .String ()
63
+ return encodeString (foldString (a .Type ), false ) + "=" + encodeString (a .Value , true )
112
64
}
113
65
114
66
// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
@@ -142,16 +94,23 @@ func (d *DN) String() string {
142
94
return strings .Join (rdns , "," )
143
95
}
144
96
97
+ func stripLeadingAndTrailingSpaces (inVal string ) string {
98
+ noSpaces := strings .Trim (inVal , " " )
99
+
100
+ // Re-add the trailing space if it was an escaped space
101
+ if len (noSpaces ) > 0 && noSpaces [len (noSpaces )- 1 ] == '\\' && inVal [len (inVal )- 1 ] == ' ' {
102
+ noSpaces = noSpaces + " "
103
+ }
104
+
105
+ return noSpaces
106
+ }
107
+
145
108
// Remove leading and trailing spaces from the attribute type and value
146
109
// and unescape any escaped characters in these fields
147
110
//
148
111
// decodeString is based on https://github.com/inteon/cert-manager/blob/ed280d28cd02b262c5db46054d88e70ab518299c/pkg/util/pki/internal/dn.go#L170
149
112
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 )- 1 ] == ' ' {
153
- s = append (s , ' ' )
154
- }
113
+ s := []rune (stripLeadingAndTrailingSpaces (str ))
155
114
156
115
builder := strings.Builder {}
157
116
for i := 0 ; i < len (s ); i ++ {
@@ -212,6 +171,65 @@ func decodeString(str string) (string, error) {
212
171
return builder .String (), nil
213
172
}
214
173
174
+ // Escape a string according to RFC 4514
175
+ func encodeString (value string , isValue bool ) string {
176
+ builder := strings.Builder {}
177
+
178
+ escapeChar := func (c byte ) {
179
+ builder .WriteByte ('\\' )
180
+ builder .WriteByte (c )
181
+ }
182
+
183
+ escapeHex := func (c byte ) {
184
+ builder .WriteByte ('\\' )
185
+ builder .WriteString (hex .EncodeToString ([]byte {c }))
186
+ }
187
+
188
+ // Loop through each byte and escape as necessary.
189
+ // Runes that take up more than one byte are escaped
190
+ // byte by byte (since both bytes are non-ASCII).
191
+ for i := 0 ; i < len (value ); i ++ {
192
+ char := value [i ]
193
+ if i == 0 && (char == ' ' || char == '#' ) {
194
+ // Special case leading space or number sign.
195
+ escapeChar (char )
196
+ continue
197
+ }
198
+ if i == len (value )- 1 && char == ' ' {
199
+ // Special case trailing space.
200
+ escapeChar (char )
201
+ continue
202
+ }
203
+
204
+ switch char {
205
+ case '"' , '+' , ',' , ';' , '<' , '>' , '\\' :
206
+ // Each of these special characters must be escaped.
207
+ escapeChar (char )
208
+ continue
209
+ }
210
+
211
+ if ! isValue && char == '=' {
212
+ // Equal signs have to be escaped only in the type part of
213
+ // the attribute type and value pair.
214
+ escapeChar (char )
215
+ continue
216
+ }
217
+
218
+ if char < ' ' || char > '~' {
219
+ // All special character escapes are handled first
220
+ // above. All bytes less than ASCII SPACE and all bytes
221
+ // greater than ASCII TILDE must be hex-escaped.
222
+ escapeHex (char )
223
+ continue
224
+ }
225
+
226
+ // Any other character does not require escaping.
227
+ builder .WriteByte (char )
228
+ }
229
+
230
+ return builder .String ()
231
+ }
232
+
215
233
func decodeEncodedString (str string ) (string , error ) {
216
234
decoded , err := hex .DecodeString (str )
217
235
if err != nil {
@@ -253,6 +271,10 @@ func ParseDN(str string) (*DN, error) {
253
271
}
254
272
)
255
273
274
+ // Loop through each character in the string and
275
+ // build up the attribute type and value pairs.
276
+ // We only check for ascii characters here, which
277
+ // allows us to iterate over the string byte by byte.
256
278
for i := 0 ; i < len (str ); i ++ {
257
279
char := str [i ]
258
280
switch {
@@ -420,3 +442,34 @@ func (r *RelativeDN) hasAllAttributesFold(attrs []*AttributeTypeAndValue) bool {
420
442
func (a * AttributeTypeAndValue ) EqualFold (other * AttributeTypeAndValue ) bool {
421
443
return strings .EqualFold (a .Type , other .Type ) && strings .EqualFold (a .Value , other .Value )
422
444
}
445
+
446
+ // foldString returns a folded string such that foldString(x) == foldString(y)
447
+ // is identical to bytes.EqualFold(x, y).
448
+ // based on https://go.dev/src/encoding/json/fold.go
449
+ func foldString (s string ) string {
450
+ builder := strings.Builder {}
451
+ for _ , char := range s {
452
+ // Handle single-byte ASCII.
453
+ if char < utf8 .RuneSelf {
454
+ if 'A' <= char && char <= 'Z' {
455
+ char += 'a' - 'A'
456
+ }
457
+ builder .WriteRune (char )
458
+ continue
459
+ }
460
+
461
+ builder .WriteRune (foldRune (char ))
462
+ }
463
+ return builder .String ()
464
+ }
465
+
466
+ // foldRune is returns the smallest rune for all runes in the same fold set.
467
+ func foldRune (r rune ) rune {
468
+ for {
469
+ r2 := unicode .SimpleFold (r )
470
+ if r2 <= r {
471
+ return r
472
+ }
473
+ r = r2
474
+ }
475
+ }
0 commit comments