Skip to content

Commit f0cea84

Browse files
rolandshoemakerFiloSottile
authored andcommitted
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 GitHub-Last-Rev: e52fc43 GitHub-Pull-Request: #38228 Reviewed-on: https://go-review.googlesource.com/c/go/+/226984 Reviewed-by: Filippo Valsorda <[email protected]> Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent f81aa23 commit f0cea84

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

src/encoding/asn1/marshal.go

Lines changed: 56 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,48 @@ func (m multiEncoder) Encode(dst []byte) {
7880
}
7981
}
8082

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

558+
if params.set {
559+
return setEncoder(m), nil
560+
}
514561
return multiEncoder(m), nil
515562
}
516563
case reflect.String:
@@ -618,6 +665,15 @@ func makeField(v reflect.Value, params fieldParameters) (e encoder, err error) {
618665
tag = TagSet
619666
}
620667

668+
// makeField can be called for a slice that should be treated as a SET
669+
// but doesn't have params.set set, for instance when using a slice
670+
// with the SET type name suffix. In this case getUniversalType returns
671+
// TagSet, but makeBody doesn't know about that so will treat the slice
672+
// as a sequence. To work around this we set params.set.
673+
if tag == TagSet && !params.set {
674+
params.set = true
675+
}
676+
621677
t := new(taggedEncoder)
622678

623679
t.body, err = makeBody(v, params)

src/encoding/asn1/marshal_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,60 @@ 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, expectedOrder)
351+
}
352+
}
353+
354+
func TestSetEncoderSETSliceSuffix(t *testing.T) {
355+
type testSetSET []string
356+
testSet := testSetSET{"a", "aa", "b", "bb", "c", "cc"}
357+
358+
// Expected ordering of the SET should be:
359+
// a, b, c, aa, bb, cc
360+
361+
output, err := Marshal(testSet)
362+
if err != nil {
363+
t.Errorf("%v", err)
364+
}
365+
366+
expectedOrder := testSetSET{"a", "b", "c", "aa", "bb", "cc"}
367+
var resultSet testSetSET
368+
rest, err := Unmarshal(output, &resultSet)
369+
if err != nil {
370+
t.Errorf("%v", err)
371+
}
372+
if len(rest) != 0 {
373+
t.Error("Unmarshal returned extra garbage")
374+
}
375+
if !reflect.DeepEqual(expectedOrder, resultSet) {
376+
t.Errorf("Unexpected SET content. got: %s, want: %s", resultSet, expectedOrder)
377+
}
378+
}

0 commit comments

Comments
 (0)