Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Indefinite Length unmarshalling #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 46 additions & 6 deletions ber.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,9 +460,28 @@ func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset i
// Bottom 7 bits give the number of length bytes to follow.
numBytes := int(b & 0x7f)
if numBytes == 0 {
// TODO: Fix this for BER as it should be allowed. Not seen this in
// the wild with SNMP devices though.
err = asn1.SyntaxError{Msg: "indefinite length found (not DER)"}
if !ret.isCompound {
err = asn1.SyntaxError{Msg: "indefinite length for non-constructed type"}
return
}
ret.isIndefinite = true
innerOffset := offset
for innerOffset <= (len(bytes) - 2) {
if bytes[innerOffset] == 0x00 && bytes[innerOffset+1] == 0x00 {
ret.length = innerOffset - offset
return
}
var t tagAndLength
t, innerOffset, err = parseTagAndLength(bytes, innerOffset)
if err != nil {
return
}
innerOffset += t.length
if t.isIndefinite {
innerOffset += 2
}
}
err = asn1.SyntaxError{Msg: "missing end-of-contents octets"}
return
}
ret.length = 0
Expand Down Expand Up @@ -526,6 +545,9 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
return
}
offset += t.length
if t.isIndefinite {
offset += 2
}
numElements++
}
ret = reflect.MakeSlice(sliceType, numElements, numElements)
Expand Down Expand Up @@ -616,6 +638,9 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
}
}
offset += t.length
if t.isIndefinite {
offset += 2
}
if err != nil {
return
}
Expand All @@ -629,6 +654,7 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
if err != nil {
return
}
explicitIsIndefinite := params.explicit && t.isIndefinite
if params.explicit {
expectedClass := classContextSpecific
if params.application {
Expand Down Expand Up @@ -735,13 +761,28 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
err = asn1.SyntaxError{Msg: "data truncated"}
return
}
innerBytes := bytes[offset : offset+t.length]
err = parseFieldContents(t, v, universalTag, bytes[initOffset:offset+t.length], offset-initOffset)
if err != nil {
return
}
offset += t.length
if t.isIndefinite {
offset += 2
}
if explicitIsIndefinite {
offset += 2
}
return
}

func parseFieldContents(t tagAndLength, v reflect.Value, universalTag int, bytes []byte, offset int) (err error) {
innerBytes := bytes[offset:]
fieldType := v.Type()

// We deal with the structures defined in this package first.
switch fieldType {
case rawValueType:
result := asn1.RawValue{t.class, t.tag, t.isCompound, innerBytes, bytes[initOffset:offset]}
result := asn1.RawValue{t.class, t.tag, t.isCompound, innerBytes, bytes}
v.Set(reflect.ValueOf(result))
return
case objectIdentifierType:
Expand Down Expand Up @@ -826,7 +867,6 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam

if structType.NumField() > 0 &&
structType.Field(0).Type == rawContentsType {
bytes := bytes[initOffset:offset]
val.Field(0).Set(reflect.ValueOf(asn1.RawContent(bytes)))
}

Expand Down
20 changes: 10 additions & 10 deletions ber_asn1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,22 +367,22 @@ type tagAndLengthTest struct {
}

var tagAndLengthData = []tagAndLengthTest{
{[]byte{0x80, 0x01}, true, tagAndLength{2, 0, 1, false}},
{[]byte{0xa0, 0x01}, true, tagAndLength{2, 0, 1, true}},
{[]byte{0x02, 0x00}, true, tagAndLength{0, 2, 0, false}},
{[]byte{0xfe, 0x00}, true, tagAndLength{3, 30, 0, true}},
{[]byte{0x1f, 0x1f, 0x00}, true, tagAndLength{0, 31, 0, false}},
{[]byte{0x1f, 0x81, 0x00, 0x00}, true, tagAndLength{0, 128, 0, false}},
{[]byte{0x1f, 0x81, 0x80, 0x01, 0x00}, true, tagAndLength{0, 0x4001, 0, false}},
{[]byte{0x00, 0x81, 0x80}, true, tagAndLength{0, 0, 128, false}},
{[]byte{0x00, 0x82, 0x01, 0x00}, true, tagAndLength{0, 0, 256, false}},
{[]byte{0x80, 0x01}, true, tagAndLength{class: 2, tag: 0, length: 1, isCompound: false}},
{[]byte{0xa0, 0x01}, true, tagAndLength{class: 2, tag: 0, length: 1, isCompound: true}},
{[]byte{0x02, 0x00}, true, tagAndLength{class: 0, tag: 2, length: 0, isCompound: false}},
{[]byte{0xfe, 0x00}, true, tagAndLength{class: 3, tag: 30, length: 0, isCompound: true}},
{[]byte{0x1f, 0x1f, 0x00}, true, tagAndLength{class: 0, tag: 31, length: 0, isCompound: false}},
{[]byte{0x1f, 0x81, 0x00, 0x00}, true, tagAndLength{class: 0, tag: 128, length: 0, isCompound: false}},
{[]byte{0x1f, 0x81, 0x80, 0x01, 0x00}, true, tagAndLength{class: 0, tag: 0x4001, length: 0, isCompound: false}},
{[]byte{0x00, 0x81, 0x80}, true, tagAndLength{class: 0, tag: 0, length: 128, isCompound: false}},
{[]byte{0x00, 0x82, 0x01, 0x00}, true, tagAndLength{class: 0, tag: 0, length: 256, isCompound: false}},
{[]byte{0x00, 0x83, 0x01, 0x00}, false, tagAndLength{}},
{[]byte{0x1f, 0x85}, false, tagAndLength{}},
{[]byte{0x30, 0x80}, false, tagAndLength{}},
// Superfluous zeros in the length should be an error.
{[]byte{0xa0, 0x82, 0x00, 0xff}, false, tagAndLength{}},
// Lengths up to the maximum size of an int should work.
{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}},
{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{class: 2, tag: 0, length: 0x7fffffff, isCompound: true}},
// Lengths that would overflow an int should be rejected.
{[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}},
// Long length form may not be used for lengths that fit in short form.
Expand Down
45 changes: 32 additions & 13 deletions ber_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

func init() {
objectIdentifierTestData = append(objectIdentifierTestData, berObjectIdentifierTestData...)
unmarshalTestData = append(unmarshalTestData, berUnmarshalTestData...)
tagAndLengthData = berTagAndLengthData
}

Expand All @@ -25,20 +26,20 @@ var berObjectIdentifierTestData = []objectIdentifierTest{
// berTagAndLengthData replaces the tagAndLengthData contents, this makes it easier to see which test has failed
// as they are numbered
var berTagAndLengthData = []tagAndLengthTest{
{[]byte{0x80, 0x01}, true, tagAndLength{2, 0, 1, false}},
{[]byte{0xa0, 0x01}, true, tagAndLength{2, 0, 1, true}},
{[]byte{0x02, 0x00}, true, tagAndLength{0, 2, 0, false}},
{[]byte{0xfe, 0x00}, true, tagAndLength{3, 30, 0, true}},
{[]byte{0x1f, 0x1f, 0x00}, true, tagAndLength{0, 31, 0, false}},
{[]byte{0x1f, 0x81, 0x00, 0x00}, true, tagAndLength{0, 128, 0, false}},
{[]byte{0x1f, 0x81, 0x80, 0x01, 0x00}, true, tagAndLength{0, 0x4001, 0, false}},
{[]byte{0x00, 0x81, 0x80}, true, tagAndLength{0, 0, 128, false}},
{[]byte{0x00, 0x82, 0x01, 0x00}, true, tagAndLength{0, 0, 256, false}},
{[]byte{0x80, 0x01}, true, tagAndLength{2, 0, 1, false, false}},
{[]byte{0xa0, 0x01}, true, tagAndLength{2, 0, 1, true, false}},
{[]byte{0x02, 0x00}, true, tagAndLength{0, 2, 0, false, false}},
{[]byte{0xfe, 0x00}, true, tagAndLength{3, 30, 0, true, false}},
{[]byte{0x1f, 0x1f, 0x00}, true, tagAndLength{0, 31, 0, false, false}},
{[]byte{0x1f, 0x81, 0x00, 0x00}, true, tagAndLength{0, 128, 0, false, false}},
{[]byte{0x1f, 0x81, 0x80, 0x01, 0x00}, true, tagAndLength{0, 0x4001, 0, false, false}},
{[]byte{0x00, 0x81, 0x80}, true, tagAndLength{0, 0, 128, false, false}},
{[]byte{0x00, 0x82, 0x01, 0x00}, true, tagAndLength{0, 0, 256, false, false}},
{[]byte{0x00, 0x83, 0x01, 0x00}, false, tagAndLength{}},
{[]byte{0x1f, 0x85}, false, tagAndLength{}},
{[]byte{0x30, 0x80}, false, tagAndLength{}},
// {[]byte{0x30, 0x80}, false, tagAndLength{}},
// Lengths up to the maximum size of an int should work.
{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}},
{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true, false}},
// Lengths that would overflow an int should be rejected.
{[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}},
// Tag numbers which would overflow int32 are rejected. (The value below is 2^31.)
Expand All @@ -48,9 +49,27 @@ var berTagAndLengthData = []tagAndLengthTest{
// Long tag number form may not be used for tags that fit in short form.
{[]byte{0x1f, 0x1e, 0x00}, false, tagAndLength{}},
// Superfluous zeros in the length should be a accepted (different from DER).
{[]byte{0xa0, 0x82, 0x00, 0xff}, true, tagAndLength{2, 0, 0xff, true}},
{[]byte{0xa0, 0x82, 0x00, 0xff}, true, tagAndLength{2, 0, 0xff, true, false}},
// Lengths that would overflow an int should be rejected.
{[]byte{0xa0, 0x84, 0x88, 0x90, 0x80, 0x23}, false, tagAndLength{}},
// Long length form may be used for lengths that fit in short form (different from DER).
{[]byte{0xa0, 0x81, 0x7f}, true, tagAndLength{2, 0, 0x7f, true}},
{[]byte{0xa0, 0x81, 0x7f}, true, tagAndLength{2, 0, 0x7f, true, false}},
// Indefinite length.
{[]byte{0x30, 0x80, 0x02, 0x01, 0x04, 0x00, 0x00}, true, tagAndLength{0, 16, 3, true, true}},
}

type TestExplicitIndefinite struct {
T TestContextSpecificTags2 `asn1:"explicit,tag:2,set"`
Ints []int `asn1:"explicit,application"`
}

var berUnmarshalTestData = []struct {
in []byte
out interface{}
}{
{[]byte{0x30, 0x80, 0x31, 0x80, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}, &TestSet{Ints: []int{1, 2, 3}}},
{[]byte{0x30, 0x80, 0x13, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x22, 0x02, 0x01, 0x33, 0x00, 0x00}, &TestElementsAfterString{"foo", 0x22, 0x33}},
{[]byte{0x30, 0x80, 0xa2, 0x80, 0x31, 0x08, 0xa1, 0x03, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00,
0x60, 0x80, 0x30, 0x80, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
&TestExplicitIndefinite{TestContextSpecificTags2{1, 2}, []int{2}}},
}
1 change: 1 addition & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
type tagAndLength struct {
class, tag, length int
isCompound bool
isIndefinite bool
}

// ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead
Expand Down