Skip to content

Commit 331ab1f

Browse files
encoding/asn1: sort order of 'SET of' components during Marshal
Per X690 Section 11.6 sort the order of SET of components when generating DER. This CL makes no changes to Unmarshal, meaning unordered components will still be accepted, and won't be re-ordered during parsing. In order to sort the components a new encoder, setEncoder, which is similar to multiEncoder is added. The functional difference is that setEncoder encodes each component to a [][]byte, sorts the slice using a sort.Sort interface, and then writes it out to the destination slice. The ordering matches the output of OpenSSL. Fixes #24254 Change-Id: Iff4560f0b8c2dce5aae616ba30226f39c10b972e
1 parent 9baafab commit 331ab1f

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

src/encoding/asn1/marshal.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
package asn1
66

77
import (
8+
"bytes"
89
"errors"
910
"fmt"
1011
"math/big"
1112
"reflect"
13+
"sort"
1214
"time"
1315
"unicode/utf8"
1416
)
@@ -78,6 +80,60 @@ func (m multiEncoder) Encode(dst []byte) {
7880
}
7981
}
8082

83+
type octetSorter [][]byte
84+
85+
func (s octetSorter) Len() int {
86+
return len(s)
87+
}
88+
89+
func (s octetSorter) Swap(i, j int) {
90+
s[i], s[j] = s[j], s[i]
91+
}
92+
93+
func (s octetSorter) Less(i, j int) bool {
94+
// Since we are using bytes.Compare to compare TLV encodings we
95+
// don't need to right pad s[i] and s[j] to the same length as
96+
// suggested in X690. If len(s[i]) < len(s[j]) the length octet of
97+
// s[i], which is the first determining byte, will inherently be
98+
// smaller than the length octet of s[j]. This lets us skip the
99+
// padding step.
100+
return bytes.Compare(s[i], s[j]) < 0
101+
}
102+
103+
type setEncoder []encoder
104+
105+
func (s setEncoder) Len() int {
106+
var size int
107+
for _, e := range s {
108+
size += e.Len()
109+
}
110+
return size
111+
}
112+
113+
func (s setEncoder) Encode(dst []byte) {
114+
// Per X690 Section 11.6: The encodings of the component values of a
115+
// set-of value shall appear in ascending order, the encodings being
116+
// compared as octet strings with the shorter components being padded
117+
// at their trailing end with 0-octets.
118+
//
119+
// First we encode each element to its TLV encoding and then use
120+
// octetSort to get the ordering expected by X690 DER rules before
121+
// writing the sorted encodings out to dst.
122+
l := make([][]byte, len(s))
123+
for i, e := range s {
124+
l[i] = make([]byte, e.Len())
125+
e.Encode(l[i])
126+
}
127+
128+
sort.Sort(octetSorter(l))
129+
130+
var off int
131+
for _, b := range l {
132+
copy(dst[off:], b)
133+
off += len(b)
134+
}
135+
}
136+
81137
type taggedEncoder struct {
82138
// scratch contains temporary space for encoding the tag and length of
83139
// an element in order to avoid extra allocations.
@@ -511,6 +567,9 @@ func makeBody(value reflect.Value, params fieldParameters) (e encoder, err error
511567
}
512568
}
513569

570+
if params.set {
571+
return setEncoder(m), nil
572+
}
514573
return multiEncoder(m), nil
515574
}
516575
case reflect.String:

src/encoding/asn1/marshal_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,34 @@ func BenchmarkMarshal(b *testing.B) {
319319
}
320320
}
321321
}
322+
323+
func TestSetEncoder(t *testing.T) {
324+
testStruct := struct {
325+
Strings []string `asn1:"set"`
326+
}{
327+
Strings: []string{"a", "aa", "b", "bb", "c", "cc"},
328+
}
329+
330+
// Expected ordering of the SET should be:
331+
// a, b, c, aa, bb, cc
332+
333+
output, err := Marshal(testStruct)
334+
if err != nil {
335+
t.Errorf("%v", err)
336+
}
337+
338+
expectedOrder := []string{"a", "b", "c", "aa", "bb", "cc"}
339+
var resultStruct struct {
340+
Strings []string `asn1:"set"`
341+
}
342+
rest, err := Unmarshal(output, &resultStruct)
343+
if err != nil {
344+
t.Errorf("%v", err)
345+
}
346+
if len(rest) != 0 {
347+
t.Error("Unmarshal returned extra garbage")
348+
}
349+
if !reflect.DeepEqual(expectedOrder, resultStruct.Strings) {
350+
t.Errorf("Unexpected SET content. got: %s, want: %s", resultStruct.Strings, resultStruct.Strings)
351+
}
352+
}

0 commit comments

Comments
 (0)