From 5dea9e5d1c3e44b8837928c164833df4c6a9a464 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 31 Jul 2023 20:44:50 +0800 Subject: [PATCH 01/28] feat: asn.1 first version Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 139 ++++++++++++++++++++++++++ internal/encoding/asn1/asn1_test.go | 68 +++++++++++++ internal/encoding/asn1/common.go | 37 +++++++ internal/encoding/asn1/constructed.go | 50 +++++++++ internal/encoding/asn1/io.go | 9 ++ internal/encoding/asn1/primitive.go | 33 ++++++ internal/encoding/asn1/slice.go | 67 +++++++++++++ 7 files changed, 403 insertions(+) create mode 100644 internal/encoding/asn1/asn1.go create mode 100644 internal/encoding/asn1/asn1_test.go create mode 100644 internal/encoding/asn1/common.go create mode 100644 internal/encoding/asn1/constructed.go create mode 100644 internal/encoding/asn1/io.go create mode 100644 internal/encoding/asn1/primitive.go create mode 100644 internal/encoding/asn1/slice.go diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go new file mode 100644 index 00000000..73e435ab --- /dev/null +++ b/internal/encoding/asn1/asn1.go @@ -0,0 +1,139 @@ +// Package asn1 decodes BER-encoded ASN.1 data structures and encodes in DER. +// Note: DER is a subset of BER. +// Reference: http://luca.ntop.org/Teaching/Appunti/asn1.html +package asn1 + +import ( + "bytes" + "encoding/asn1" + "io" +) + +// Common errors +var ( + ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} + ErrExpectConstructed = asn1.SyntaxError{Msg: "constructed value expected"} + ErrExpectPrimitive = asn1.SyntaxError{Msg: "primitive value expected"} + ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} + ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} + ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} +) + +// Value represents an ASN.1 value. +type Value interface { + // Encode encodes the value to the value writer in DER. + Encode(ValueWriter) error + + // EncodedLen returns the length in bytes of the encoded data. + EncodedLen() int +} + +// ConvertToDER converts BER-encoded ASN.1 data structures to DER-encoded. +func ConvertToDER(ber []byte) ([]byte, error) { + v, err := decode(newReadOnlySlice(ber)) + if err != nil { + return nil, err + } + buf := bytes.NewBuffer(make([]byte, 0, v.EncodedLen())) + if err = v.Encode(buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// decode decodes BER-encoded ASN.1 data structures. +func decode(r ReadOnlySlice) (Value, error) { + identifier, isPrimitiveValue, err := identifierValue(r) + if err != nil { + return nil, err + } + expectedLength, err := decodeLength(r) + if err != nil { + return nil, err + } + content, err := r.Slice(r.Offset(), r.Offset()+expectedLength) + if err != nil { + return nil, err + } + if err = r.Seek(r.Offset() + expectedLength); err != nil { + return nil, err + } + + if isPrimitiveValue { + return newPrimitiveValue(identifier, content) + } + return newConstructedValue(identifier, expectedLength, content) +} + +// identifierValue decodes identifierValue octets. +func identifierValue(r ReadOnlySlice) (ReadOnlySlice, bool, error) { + b, err := r.ReadByte() + if err != nil { + return nil, false, err + } + isPrimitiveValue := isPrimitive(b) + + tagBytesCount := 1 + // high-tag-number form + if b&0x1f == 0x1f { + for { + b, err = r.ReadByte() + if err != nil { + return nil, false, err + } + tagBytesCount++ + if b&0x80 != 0 { + break + } + } + } + + identifier, err := r.Slice(r.Offset()-tagBytesCount, r.Offset()) + if err != nil { + return nil, false, err + } + return identifier, isPrimitiveValue, nil +} + +// isPrimitive checks the primitive flag in the identifier. +// Returns true if the value is primitive. +func isPrimitive(identifier byte) bool { + return identifier&0x20 == 0 +} + +// decodeLength decodes length octets. +// Indefinite length is not supported +func decodeLength(r io.ByteReader) (int, error) { + b, err := r.ReadByte() + if err != nil { + return 0, err + } + switch { + case b < 0x80: + // short form + return int(b), nil + case b == 0x80: + // Indefinite-length method is not supported. + return 0, ErrUnsupportedLength + } + + // long form + n := int(b & 0x7f) + if n > 4 { + // length must fit the memory space of the int type. + return 0, ErrUnsupportedLength + } + var length int + for i := 0; i < n; i++ { + b, err = r.ReadByte() + if err != nil { + return 0, err + } + length = (length << 8) | int(b) + } + if length < 0 { + // double check in case that length is over 31 bits. + return 0, ErrUnsupportedLength + } + return length, nil +} diff --git a/internal/encoding/asn1/asn1_test.go b/internal/encoding/asn1/asn1_test.go new file mode 100644 index 00000000..c2fa90d1 --- /dev/null +++ b/internal/encoding/asn1/asn1_test.go @@ -0,0 +1,68 @@ +package asn1 + +import ( + "encoding/asn1" + "reflect" + "testing" +) + +func TestConvertToDER(t *testing.T) { + type data struct { + Type asn1.ObjectIdentifier + Value []byte + } + + want := data{ + Type: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}, + Value: []byte{ + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, + }, + } + + ber := []byte{ + // Constructed value + 0x30, + // Constructed value length + 0x2e, + + // Type identifier + 0x06, + // Type length + 0x09, + // Type content + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + + // Value identifier + 0x04, + // Value length in BER + 0x81, 0x20, + // Value content + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, + } + + der, err := ConvertToDER(ber) + if err != nil { + t.Errorf("ConvertToDER() error = %v", err) + return + } + + var got data + rest, err := asn1.Unmarshal(der, &got) + if err != nil { + t.Errorf("Failed to decode converted data: %v", err) + return + } + if len(rest) > 0 { + t.Errorf("Unexpected rest data: %v", rest) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got = %v, want %v", got, want) + } +} diff --git a/internal/encoding/asn1/common.go b/internal/encoding/asn1/common.go new file mode 100644 index 00000000..dbdc9912 --- /dev/null +++ b/internal/encoding/asn1/common.go @@ -0,0 +1,37 @@ +package asn1 + +import "io" + +// encodeLength encodes length octets in DER. +func encodeLength(w io.ByteWriter, length int) error { + // DER restriction: short form must be used for length less than 128 + if length < 0x80 { + return w.WriteByte(byte(length)) + } + + // DER restriction: long form must be encoded in the minimum number of octets + lengthSize := encodedLengthSize(length) + err := w.WriteByte(0x80 | byte(lengthSize-1)) + if err != nil { + return err + } + for i := lengthSize - 1; i > 0; i-- { + if err = w.WriteByte(byte(length >> (8 * (i - 1)))); err != nil { + return err + } + } + return nil +} + +// encodedLengthSize gives the number of octets used for encoding the length. +func encodedLengthSize(length int) int { + if length < 0x80 { + return 1 + } + + lengthSize := 1 + for ; length > 0; lengthSize++ { + length >>= 8 + } + return lengthSize +} diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go new file mode 100644 index 00000000..cb559e18 --- /dev/null +++ b/internal/encoding/asn1/constructed.go @@ -0,0 +1,50 @@ +package asn1 + +// ConstructedValue represents a value in constructed encoding. +type ConstructedValue struct { + identifier ReadOnlySlice + length int + members []Value +} + +// newConstructedValue builds the constructed value. +func newConstructedValue(identifier ReadOnlySlice, expectedLength int, content ReadOnlySlice) (Value, error) { + var members []Value + encodedLength := 0 + for content.Offset() < content.Length() { + value, err := decode(content) + if err != nil { + return nil, err + } + members = append(members, value) + encodedLength += value.EncodedLen() + } + + return ConstructedValue{ + identifier: identifier, + length: encodedLength, + members: members, + }, nil +} + +// Encode encodes the constructed value to the value writer in DER. +func (v ConstructedValue) Encode(w ValueWriter) error { + _, err := w.ReadFrom(v.identifier) + if err != nil { + return err + } + if err = encodeLength(w, v.length); err != nil { + return err + } + for _, value := range v.members { + if err = value.Encode(w); err != nil { + return err + } + } + return nil +} + +// EncodedLen returns the length in bytes of the encoded data. +func (v ConstructedValue) EncodedLen() int { + return v.identifier.Length() + encodedLengthSize(v.length) + v.length +} diff --git a/internal/encoding/asn1/io.go b/internal/encoding/asn1/io.go new file mode 100644 index 00000000..737235fa --- /dev/null +++ b/internal/encoding/asn1/io.go @@ -0,0 +1,9 @@ +package asn1 + +import "io" + +// ValueWriter is the interface for writing a value. +type ValueWriter interface { + io.ReaderFrom + io.ByteWriter +} diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go new file mode 100644 index 00000000..b19cb9fc --- /dev/null +++ b/internal/encoding/asn1/primitive.go @@ -0,0 +1,33 @@ +package asn1 + +// PrimitiveValue represents a value in primitive encoding. +type PrimitiveValue struct { + identifier ReadOnlySlice + content ReadOnlySlice +} + +// newPrimitiveValue builds the primitive value. +func newPrimitiveValue(identifier ReadOnlySlice, content ReadOnlySlice) (Value, error) { + return PrimitiveValue{ + identifier: identifier, + content: content, + }, nil +} + +// Encode encodes the primitive value to the value writer in DER. +func (v PrimitiveValue) Encode(w ValueWriter) error { + _, err := w.ReadFrom(v.identifier) + if err != nil { + return err + } + if err = encodeLength(w, v.content.Length()); err != nil { + return err + } + _, err = w.ReadFrom(v.content) + return err +} + +// EncodedLen returns the length in bytes of the encoded data. +func (v PrimitiveValue) EncodedLen() int { + return v.identifier.Length() + encodedLengthSize(v.content.Length()) + v.content.Length() +} diff --git a/internal/encoding/asn1/slice.go b/internal/encoding/asn1/slice.go new file mode 100644 index 00000000..af124559 --- /dev/null +++ b/internal/encoding/asn1/slice.go @@ -0,0 +1,67 @@ +package asn1 + +import "io" + +type ReadOnlySlice interface { + io.ByteReader + io.Reader + Length() int + Offset() int + Seek(offset int) error + Slice(begin int, end int) (ReadOnlySlice, error) +} + +type readOnlySlice struct { + data []byte + offset int +} + +func newReadOnlySlice(data []byte) ReadOnlySlice { + return &readOnlySlice{ + data: data, + offset: 0, + } +} + +func (r *readOnlySlice) ReadByte() (byte, error) { + if r.offset >= len(r.data) { + return 0, ErrEarlyEOF + } + defer func() { r.offset++ }() + return r.data[r.offset], nil +} + +func (r *readOnlySlice) Read(p []byte) (int, error) { + if r.offset >= len(r.data) { + return 0, io.EOF + } + n := copy(p, r.data[r.offset:]) + r.offset += n + return n, nil +} + +func (r *readOnlySlice) Length() int { + return len(r.data) +} + +func (r *readOnlySlice) Offset() int { + return r.offset +} + +func (r *readOnlySlice) Seek(offset int) error { + if offset < 0 || offset > len(r.data) { + return ErrInvalidOffset + } + r.offset = offset + return nil +} + +func (r *readOnlySlice) Slice(begin int, end int) (ReadOnlySlice, error) { + if begin < 0 || end < 0 || begin > end || end > len(r.data) { + return nil, ErrInvalidSlice + } + return &readOnlySlice{ + data: r.data[begin:end], + offset: 0, + }, nil +} From b3de155b8866f3ceb0a0e6b8ff258ef036efee73 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 31 Jul 2023 20:51:48 +0800 Subject: [PATCH 02/28] fix: set non-exportable type Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 8 ++++---- internal/encoding/asn1/constructed.go | 16 ++++++++-------- internal/encoding/asn1/io.go | 4 ++-- internal/encoding/asn1/primitive.go | 12 ++++++------ internal/encoding/asn1/slice.go | 5 +++-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 73e435ab..c48d7d3b 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -19,10 +19,10 @@ var ( ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} ) -// Value represents an ASN.1 value. -type Value interface { +// value represents an ASN.1 value. +type value interface { // Encode encodes the value to the value writer in DER. - Encode(ValueWriter) error + Encode(valueWriter) error // EncodedLen returns the length in bytes of the encoded data. EncodedLen() int @@ -42,7 +42,7 @@ func ConvertToDER(ber []byte) ([]byte, error) { } // decode decodes BER-encoded ASN.1 data structures. -func decode(r ReadOnlySlice) (Value, error) { +func decode(r ReadOnlySlice) (value, error) { identifier, isPrimitiveValue, err := identifierValue(r) if err != nil { return nil, err diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index cb559e18..2e10af40 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -1,15 +1,15 @@ package asn1 -// ConstructedValue represents a value in constructed encoding. -type ConstructedValue struct { +// constructedValue represents a value in constructed encoding. +type constructedValue struct { identifier ReadOnlySlice length int - members []Value + members []value } // newConstructedValue builds the constructed value. -func newConstructedValue(identifier ReadOnlySlice, expectedLength int, content ReadOnlySlice) (Value, error) { - var members []Value +func newConstructedValue(identifier ReadOnlySlice, expectedLength int, content ReadOnlySlice) (value, error) { + var members []value encodedLength := 0 for content.Offset() < content.Length() { value, err := decode(content) @@ -20,7 +20,7 @@ func newConstructedValue(identifier ReadOnlySlice, expectedLength int, content R encodedLength += value.EncodedLen() } - return ConstructedValue{ + return constructedValue{ identifier: identifier, length: encodedLength, members: members, @@ -28,7 +28,7 @@ func newConstructedValue(identifier ReadOnlySlice, expectedLength int, content R } // Encode encodes the constructed value to the value writer in DER. -func (v ConstructedValue) Encode(w ValueWriter) error { +func (v constructedValue) Encode(w valueWriter) error { _, err := w.ReadFrom(v.identifier) if err != nil { return err @@ -45,6 +45,6 @@ func (v ConstructedValue) Encode(w ValueWriter) error { } // EncodedLen returns the length in bytes of the encoded data. -func (v ConstructedValue) EncodedLen() int { +func (v constructedValue) EncodedLen() int { return v.identifier.Length() + encodedLengthSize(v.length) + v.length } diff --git a/internal/encoding/asn1/io.go b/internal/encoding/asn1/io.go index 737235fa..e9f5ec26 100644 --- a/internal/encoding/asn1/io.go +++ b/internal/encoding/asn1/io.go @@ -2,8 +2,8 @@ package asn1 import "io" -// ValueWriter is the interface for writing a value. -type ValueWriter interface { +// valueWriter is the interface for writing a value. +type valueWriter interface { io.ReaderFrom io.ByteWriter } diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index b19cb9fc..36971175 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -1,21 +1,21 @@ package asn1 -// PrimitiveValue represents a value in primitive encoding. -type PrimitiveValue struct { +// primitiveValue represents a value in primitive encoding. +type primitiveValue struct { identifier ReadOnlySlice content ReadOnlySlice } // newPrimitiveValue builds the primitive value. -func newPrimitiveValue(identifier ReadOnlySlice, content ReadOnlySlice) (Value, error) { - return PrimitiveValue{ +func newPrimitiveValue(identifier ReadOnlySlice, content ReadOnlySlice) (value, error) { + return primitiveValue{ identifier: identifier, content: content, }, nil } // Encode encodes the primitive value to the value writer in DER. -func (v PrimitiveValue) Encode(w ValueWriter) error { +func (v primitiveValue) Encode(w valueWriter) error { _, err := w.ReadFrom(v.identifier) if err != nil { return err @@ -28,6 +28,6 @@ func (v PrimitiveValue) Encode(w ValueWriter) error { } // EncodedLen returns the length in bytes of the encoded data. -func (v PrimitiveValue) EncodedLen() int { +func (v primitiveValue) EncodedLen() int { return v.identifier.Length() + encodedLengthSize(v.content.Length()) + v.content.Length() } diff --git a/internal/encoding/asn1/slice.go b/internal/encoding/asn1/slice.go index af124559..a585c3ed 100644 --- a/internal/encoding/asn1/slice.go +++ b/internal/encoding/asn1/slice.go @@ -27,8 +27,9 @@ func (r *readOnlySlice) ReadByte() (byte, error) { if r.offset >= len(r.data) { return 0, ErrEarlyEOF } - defer func() { r.offset++ }() - return r.data[r.offset], nil + b := r.data[r.offset] + r.offset++ + return b, nil } func (r *readOnlySlice) Read(p []byte) (int, error) { From 45480e5e93508e433fffe6b23ee3a6685277b3aa Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 31 Jul 2023 21:22:20 +0800 Subject: [PATCH 03/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 24 ++++++--------- internal/encoding/asn1/constructed.go | 4 +-- internal/encoding/asn1/primitive.go | 8 ++--- internal/encoding/asn1/slice.go | 43 +++++++++++++++++++-------- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index c48d7d3b..6e3915d3 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -42,36 +42,36 @@ func ConvertToDER(ber []byte) ([]byte, error) { } // decode decodes BER-encoded ASN.1 data structures. -func decode(r ReadOnlySlice) (value, error) { +func decode(r readOnlySlice) (value, error) { identifier, isPrimitiveValue, err := identifierValue(r) if err != nil { return nil, err } - expectedLength, err := decodeLength(r) + contentLength, err := decodeLength(r) if err != nil { return nil, err } - content, err := r.Slice(r.Offset(), r.Offset()+expectedLength) + content, err := r.Slice(r.Offset(), r.Offset()+contentLength) if err != nil { return nil, err } - if err = r.Seek(r.Offset() + expectedLength); err != nil { + if err = r.Seek(r.Offset() + contentLength); err != nil { return nil, err } if isPrimitiveValue { - return newPrimitiveValue(identifier, content) + return newPrimitiveValue(identifier, content), nil } - return newConstructedValue(identifier, expectedLength, content) + return newConstructedValue(identifier, content) } // identifierValue decodes identifierValue octets. -func identifierValue(r ReadOnlySlice) (ReadOnlySlice, bool, error) { +func identifierValue(r readOnlySlice) (readOnlySlice, bool, error) { b, err := r.ReadByte() if err != nil { return nil, false, err } - isPrimitiveValue := isPrimitive(b) + isPrimitive := b&0x20 == 0 tagBytesCount := 1 // high-tag-number form @@ -92,13 +92,7 @@ func identifierValue(r ReadOnlySlice) (ReadOnlySlice, bool, error) { if err != nil { return nil, false, err } - return identifier, isPrimitiveValue, nil -} - -// isPrimitive checks the primitive flag in the identifier. -// Returns true if the value is primitive. -func isPrimitive(identifier byte) bool { - return identifier&0x20 == 0 + return identifier, isPrimitive, nil } // decodeLength decodes length octets. diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index 2e10af40..2fe37abe 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -2,13 +2,13 @@ package asn1 // constructedValue represents a value in constructed encoding. type constructedValue struct { - identifier ReadOnlySlice + identifier readOnlySlice length int members []value } // newConstructedValue builds the constructed value. -func newConstructedValue(identifier ReadOnlySlice, expectedLength int, content ReadOnlySlice) (value, error) { +func newConstructedValue(identifier readOnlySlice, content readOnlySlice) (value, error) { var members []value encodedLength := 0 for content.Offset() < content.Length() { diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index 36971175..0c9719d2 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -2,16 +2,16 @@ package asn1 // primitiveValue represents a value in primitive encoding. type primitiveValue struct { - identifier ReadOnlySlice - content ReadOnlySlice + identifier readOnlySlice + content readOnlySlice } // newPrimitiveValue builds the primitive value. -func newPrimitiveValue(identifier ReadOnlySlice, content ReadOnlySlice) (value, error) { +func newPrimitiveValue(identifier readOnlySlice, content readOnlySlice) value { return primitiveValue{ identifier: identifier, content: content, - }, nil + } } // Encode encodes the primitive value to the value writer in DER. diff --git a/internal/encoding/asn1/slice.go b/internal/encoding/asn1/slice.go index a585c3ed..5a5574ea 100644 --- a/internal/encoding/asn1/slice.go +++ b/internal/encoding/asn1/slice.go @@ -2,28 +2,42 @@ package asn1 import "io" -type ReadOnlySlice interface { +// readOnlySlice is an interface that represents a read-only slice of bytes. +type readOnlySlice interface { io.ByteReader io.Reader + + // Length returns the length of the slice. Length() int + + // Offset returns the current offset of the slice. Offset() int + + // Seek sets the current offset of the slice to the given value. Seek(offset int) error - Slice(begin int, end int) (ReadOnlySlice, error) + + // Slice returns a new ReadOnlySlice that represents a sub-slice of the current slice. + // The sub-slice starts at the given begin index and ends at the given end index (exclusive). + Slice(begin int, end int) (readOnlySlice, error) } -type readOnlySlice struct { +// byteSlice is a struct that implements the ReadOnlySlice interface. +type byteSlice struct { data []byte offset int } -func newReadOnlySlice(data []byte) ReadOnlySlice { - return &readOnlySlice{ +// newReadOnlySlice creates a new ReadOnlySlice from the given byte slice. +func newReadOnlySlice(data []byte) readOnlySlice { + return &byteSlice{ data: data, offset: 0, } } -func (r *readOnlySlice) ReadByte() (byte, error) { +// ReadByte reads and returns a single byte from the slice. +// If the end of the slice has been reached, it returns an error. +func (r *byteSlice) ReadByte() (byte, error) { if r.offset >= len(r.data) { return 0, ErrEarlyEOF } @@ -32,7 +46,8 @@ func (r *readOnlySlice) ReadByte() (byte, error) { return b, nil } -func (r *readOnlySlice) Read(p []byte) (int, error) { +// Read reads up to len(p) bytes from the slice into p. +func (r *byteSlice) Read(p []byte) (int, error) { if r.offset >= len(r.data) { return 0, io.EOF } @@ -41,15 +56,18 @@ func (r *readOnlySlice) Read(p []byte) (int, error) { return n, nil } -func (r *readOnlySlice) Length() int { +// Length returns the length of the slice. +func (r *byteSlice) Length() int { return len(r.data) } -func (r *readOnlySlice) Offset() int { +// Offset returns the current offset of the slice. +func (r *byteSlice) Offset() int { return r.offset } -func (r *readOnlySlice) Seek(offset int) error { +// Seek sets the current offset of the slice to the given value. +func (r *byteSlice) Seek(offset int) error { if offset < 0 || offset > len(r.data) { return ErrInvalidOffset } @@ -57,11 +75,12 @@ func (r *readOnlySlice) Seek(offset int) error { return nil } -func (r *readOnlySlice) Slice(begin int, end int) (ReadOnlySlice, error) { +// Slice returns a new ReadOnlySlice that represents a sub-slice of the current slice. +func (r *byteSlice) Slice(begin int, end int) (readOnlySlice, error) { if begin < 0 || end < 0 || begin > end || end > len(r.data) { return nil, ErrInvalidSlice } - return &readOnlySlice{ + return &byteSlice{ data: r.data[begin:end], offset: 0, }, nil From cbce4c135f14caa3ca73ea7ba5680208c62ef9e7 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Tue, 1 Aug 2023 09:25:34 +0800 Subject: [PATCH 04/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 6e3915d3..d334ab88 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -11,12 +11,11 @@ import ( // Common errors var ( - ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} - ErrExpectConstructed = asn1.SyntaxError{Msg: "constructed value expected"} - ErrExpectPrimitive = asn1.SyntaxError{Msg: "primitive value expected"} - ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} - ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} - ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} + ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} + ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} + ErrUnsupportedIndefinedLenth = asn1.StructuralError{Msg: "indefinite length not supported"} + ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} + ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} ) // value represents an ASN.1 value. @@ -108,7 +107,7 @@ func decodeLength(r io.ByteReader) (int, error) { return int(b), nil case b == 0x80: // Indefinite-length method is not supported. - return 0, ErrUnsupportedLength + return 0, ErrUnsupportedIndefinedLenth } // long form From efa75756326adcf8b1bb2cfedd2b31c4d7a9f5e6 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Tue, 1 Aug 2023 09:38:57 +0800 Subject: [PATCH 05/28] fix: update decodeIdentifier function name Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index d334ab88..18f1b5f1 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -42,7 +42,7 @@ func ConvertToDER(ber []byte) ([]byte, error) { // decode decodes BER-encoded ASN.1 data structures. func decode(r readOnlySlice) (value, error) { - identifier, isPrimitiveValue, err := identifierValue(r) + identifier, isPrimitiveValue, err := decodeIdentifier(r) if err != nil { return nil, err } @@ -64,8 +64,8 @@ func decode(r readOnlySlice) (value, error) { return newConstructedValue(identifier, content) } -// identifierValue decodes identifierValue octets. -func identifierValue(r readOnlySlice) (readOnlySlice, bool, error) { +// decodeIdentifier decodes decodeIdentifier octets. +func decodeIdentifier(r readOnlySlice) (readOnlySlice, bool, error) { b, err := r.ReadByte() if err != nil { return nil, false, err From 4fd944a74330254fc3e9805cd349e40b3afebc86 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Tue, 1 Aug 2023 21:50:10 +0800 Subject: [PATCH 06/28] fix: remove readOnlySlice Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 106 +++++++++++++------------- internal/encoding/asn1/common.go | 14 ++-- internal/encoding/asn1/constructed.go | 24 +++--- internal/encoding/asn1/io.go | 9 --- internal/encoding/asn1/primitive.go | 18 +++-- internal/encoding/asn1/slice.go | 87 --------------------- 6 files changed, 84 insertions(+), 174 deletions(-) delete mode 100644 internal/encoding/asn1/io.go delete mode 100644 internal/encoding/asn1/slice.go diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 18f1b5f1..fe7a4a21 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -6,22 +6,21 @@ package asn1 import ( "bytes" "encoding/asn1" - "io" ) // Common errors var ( - ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} - ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} - ErrUnsupportedIndefinedLenth = asn1.StructuralError{Msg: "indefinite length not supported"} - ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} - ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} + ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} + ErrUnsupportedLen = asn1.StructuralError{Msg: "length method not supported"} + ErrUnsupportedIndefinedLen = asn1.StructuralError{Msg: "indefinite length not supported"} + ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} + ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} ) // value represents an ASN.1 value. type value interface { // Encode encodes the value to the value writer in DER. - Encode(valueWriter) error + Encode(*bytes.Buffer) error // EncodedLen returns the length in bytes of the encoded data. EncodedLen() int @@ -29,7 +28,7 @@ type value interface { // ConvertToDER converts BER-encoded ASN.1 data structures to DER-encoded. func ConvertToDER(ber []byte) ([]byte, error) { - v, err := decode(newReadOnlySlice(ber)) + v, _, err := decode(ber) if err != nil { return nil, err } @@ -41,92 +40,89 @@ func ConvertToDER(ber []byte) ([]byte, error) { } // decode decodes BER-encoded ASN.1 data structures. -func decode(r readOnlySlice) (value, error) { - identifier, isPrimitiveValue, err := decodeIdentifier(r) +func decode(r []byte) (value, []byte, error) { + identifier, r, err := decodeIdentifier(r) if err != nil { - return nil, err + return nil, nil, err } - contentLength, err := decodeLength(r) + contentLength, r, err := decodeLength(r) if err != nil { - return nil, err + return nil, nil, err } - content, err := r.Slice(r.Offset(), r.Offset()+contentLength) - if err != nil { - return nil, err + + if contentLength > len(r) { + return nil, nil, ErrEarlyEOF } - if err = r.Seek(r.Offset() + contentLength); err != nil { - return nil, err + content := r[:contentLength] + r = r[contentLength:] + + isPrimitive := identifier[0]&0x20 == 0 + // primitive value + if isPrimitive { + return newPrimitiveValue(identifier, content), r, nil } - if isPrimitiveValue { - return newPrimitiveValue(identifier, content), nil + // constructed value + v, err := newConstructedValue(identifier, content) + if err != nil { + return nil, nil, err } - return newConstructedValue(identifier, content) + return v, r, nil } // decodeIdentifier decodes decodeIdentifier octets. -func decodeIdentifier(r readOnlySlice) (readOnlySlice, bool, error) { - b, err := r.ReadByte() - if err != nil { - return nil, false, err +func decodeIdentifier(r []byte) ([]byte, []byte, error) { + offset := 0 + if len(r) < 1 { + return nil, nil, ErrEarlyEOF } - isPrimitive := b&0x20 == 0 + b := r[offset] + offset++ - tagBytesCount := 1 // high-tag-number form if b&0x1f == 0x1f { - for { - b, err = r.ReadByte() - if err != nil { - return nil, false, err - } - tagBytesCount++ - if b&0x80 != 0 { - break - } + for offset < len(r) && r[offset]&0x80 == 0x80 { + offset++ } } - - identifier, err := r.Slice(r.Offset()-tagBytesCount, r.Offset()) - if err != nil { - return nil, false, err - } - return identifier, isPrimitive, nil + return r[:offset], r[offset:], nil } // decodeLength decodes length octets. // Indefinite length is not supported -func decodeLength(r io.ByteReader) (int, error) { - b, err := r.ReadByte() - if err != nil { - return 0, err +func decodeLength(r []byte) (int, []byte, error) { + offset := 0 + if len(r) < 1 { + return 0, nil, ErrEarlyEOF } + b := r[offset] + offset++ switch { case b < 0x80: // short form - return int(b), nil + return int(b), r[offset:], nil case b == 0x80: // Indefinite-length method is not supported. - return 0, ErrUnsupportedIndefinedLenth + return 0, nil, ErrUnsupportedIndefinedLen } // long form n := int(b & 0x7f) if n > 4 { // length must fit the memory space of the int type. - return 0, ErrUnsupportedLength + return 0, nil, ErrUnsupportedLen } var length int for i := 0; i < n; i++ { - b, err = r.ReadByte() - if err != nil { - return 0, err + if offset >= len(r) { + return 0, nil, ErrEarlyEOF } - length = (length << 8) | int(b) + length = (length << 8) | int(r[offset]) + offset++ } if length < 0 { // double check in case that length is over 31 bits. - return 0, ErrUnsupportedLength + return 0, nil, ErrUnsupportedLen } - return length, nil + return length, r[offset:], nil } diff --git a/internal/encoding/asn1/common.go b/internal/encoding/asn1/common.go index dbdc9912..0c904cb1 100644 --- a/internal/encoding/asn1/common.go +++ b/internal/encoding/asn1/common.go @@ -1,16 +1,18 @@ package asn1 -import "io" +import ( + "bytes" +) -// encodeLength encodes length octets in DER. -func encodeLength(w io.ByteWriter, length int) error { +// encodeLen encodes length octets in DER. +func encodeLen(w *bytes.Buffer, length int) error { // DER restriction: short form must be used for length less than 128 if length < 0x80 { return w.WriteByte(byte(length)) } // DER restriction: long form must be encoded in the minimum number of octets - lengthSize := encodedLengthSize(length) + lengthSize := encodedLenSize(length) err := w.WriteByte(0x80 | byte(lengthSize-1)) if err != nil { return err @@ -23,8 +25,8 @@ func encodeLength(w io.ByteWriter, length int) error { return nil } -// encodedLengthSize gives the number of octets used for encoding the length. -func encodedLengthSize(length int) int { +// encodedLenSize gives the number of octets used for encoding the length. +func encodedLenSize(length int) int { if length < 0x80 { return 1 } diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index 2fe37abe..26a142f1 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -1,18 +1,24 @@ package asn1 +import "bytes" + // constructedValue represents a value in constructed encoding. type constructedValue struct { - identifier readOnlySlice + identifier []byte length int members []value } // newConstructedValue builds the constructed value. -func newConstructedValue(identifier readOnlySlice, content readOnlySlice) (value, error) { - var members []value +func newConstructedValue(identifier []byte, content []byte) (value, error) { + var ( + members []value + value value + err error + ) encodedLength := 0 - for content.Offset() < content.Length() { - value, err := decode(content) + for len(content) > 0 { + value, content, err = decode(content) if err != nil { return nil, err } @@ -28,12 +34,12 @@ func newConstructedValue(identifier readOnlySlice, content readOnlySlice) (value } // Encode encodes the constructed value to the value writer in DER. -func (v constructedValue) Encode(w valueWriter) error { - _, err := w.ReadFrom(v.identifier) +func (v constructedValue) Encode(w *bytes.Buffer) error { + _, err := w.Write(v.identifier) if err != nil { return err } - if err = encodeLength(w, v.length); err != nil { + if err = encodeLen(w, v.length); err != nil { return err } for _, value := range v.members { @@ -46,5 +52,5 @@ func (v constructedValue) Encode(w valueWriter) error { // EncodedLen returns the length in bytes of the encoded data. func (v constructedValue) EncodedLen() int { - return v.identifier.Length() + encodedLengthSize(v.length) + v.length + return len(v.identifier) + encodedLenSize(v.length) + v.length } diff --git a/internal/encoding/asn1/io.go b/internal/encoding/asn1/io.go deleted file mode 100644 index e9f5ec26..00000000 --- a/internal/encoding/asn1/io.go +++ /dev/null @@ -1,9 +0,0 @@ -package asn1 - -import "io" - -// valueWriter is the interface for writing a value. -type valueWriter interface { - io.ReaderFrom - io.ByteWriter -} diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index 0c9719d2..f13f2d7b 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -1,13 +1,15 @@ package asn1 +import "bytes" + // primitiveValue represents a value in primitive encoding. type primitiveValue struct { - identifier readOnlySlice - content readOnlySlice + identifier []byte + content []byte } // newPrimitiveValue builds the primitive value. -func newPrimitiveValue(identifier readOnlySlice, content readOnlySlice) value { +func newPrimitiveValue(identifier []byte, content []byte) value { return primitiveValue{ identifier: identifier, content: content, @@ -15,19 +17,19 @@ func newPrimitiveValue(identifier readOnlySlice, content readOnlySlice) value { } // Encode encodes the primitive value to the value writer in DER. -func (v primitiveValue) Encode(w valueWriter) error { - _, err := w.ReadFrom(v.identifier) +func (v primitiveValue) Encode(w *bytes.Buffer) error { + _, err := w.Write(v.identifier) if err != nil { return err } - if err = encodeLength(w, v.content.Length()); err != nil { + if err = encodeLen(w, len(v.content)); err != nil { return err } - _, err = w.ReadFrom(v.content) + _, err = w.Write(v.content) return err } // EncodedLen returns the length in bytes of the encoded data. func (v primitiveValue) EncodedLen() int { - return v.identifier.Length() + encodedLengthSize(v.content.Length()) + v.content.Length() + return len(v.identifier) + encodedLenSize(len(v.content)) + len(v.content) } diff --git a/internal/encoding/asn1/slice.go b/internal/encoding/asn1/slice.go deleted file mode 100644 index 5a5574ea..00000000 --- a/internal/encoding/asn1/slice.go +++ /dev/null @@ -1,87 +0,0 @@ -package asn1 - -import "io" - -// readOnlySlice is an interface that represents a read-only slice of bytes. -type readOnlySlice interface { - io.ByteReader - io.Reader - - // Length returns the length of the slice. - Length() int - - // Offset returns the current offset of the slice. - Offset() int - - // Seek sets the current offset of the slice to the given value. - Seek(offset int) error - - // Slice returns a new ReadOnlySlice that represents a sub-slice of the current slice. - // The sub-slice starts at the given begin index and ends at the given end index (exclusive). - Slice(begin int, end int) (readOnlySlice, error) -} - -// byteSlice is a struct that implements the ReadOnlySlice interface. -type byteSlice struct { - data []byte - offset int -} - -// newReadOnlySlice creates a new ReadOnlySlice from the given byte slice. -func newReadOnlySlice(data []byte) readOnlySlice { - return &byteSlice{ - data: data, - offset: 0, - } -} - -// ReadByte reads and returns a single byte from the slice. -// If the end of the slice has been reached, it returns an error. -func (r *byteSlice) ReadByte() (byte, error) { - if r.offset >= len(r.data) { - return 0, ErrEarlyEOF - } - b := r.data[r.offset] - r.offset++ - return b, nil -} - -// Read reads up to len(p) bytes from the slice into p. -func (r *byteSlice) Read(p []byte) (int, error) { - if r.offset >= len(r.data) { - return 0, io.EOF - } - n := copy(p, r.data[r.offset:]) - r.offset += n - return n, nil -} - -// Length returns the length of the slice. -func (r *byteSlice) Length() int { - return len(r.data) -} - -// Offset returns the current offset of the slice. -func (r *byteSlice) Offset() int { - return r.offset -} - -// Seek sets the current offset of the slice to the given value. -func (r *byteSlice) Seek(offset int) error { - if offset < 0 || offset > len(r.data) { - return ErrInvalidOffset - } - r.offset = offset - return nil -} - -// Slice returns a new ReadOnlySlice that represents a sub-slice of the current slice. -func (r *byteSlice) Slice(begin int, end int) (readOnlySlice, error) { - if begin < 0 || end < 0 || begin > end || end > len(r.data) { - return nil, ErrInvalidSlice - } - return &byteSlice{ - data: r.data[begin:end], - offset: 0, - }, nil -} From 936ba2bc9a578b8ace72b4976e561f05213860e1 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 2 Aug 2023 11:36:02 +0800 Subject: [PATCH 07/28] fix: remove recusive decoding Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 126 +++++++++++++++++++++----- internal/encoding/asn1/constructed.go | 31 +------ internal/encoding/asn1/primitive.go | 8 -- 3 files changed, 106 insertions(+), 59 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index fe7a4a21..007257ca 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -10,11 +10,13 @@ import ( // Common errors var ( + ErrBytesAtTheEnd = asn1.StructuralError{Msg: "invalid bytes at the end of the BER data"} ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} + ErrInvalidBerData = asn1.StructuralError{Msg: "invalid BER data"} + ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} + ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} ErrUnsupportedLen = asn1.StructuralError{Msg: "length method not supported"} ErrUnsupportedIndefinedLen = asn1.StructuralError{Msg: "indefinite length not supported"} - ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} - ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} ) // value represents an ASN.1 value. @@ -28,7 +30,7 @@ type value interface { // ConvertToDER converts BER-encoded ASN.1 data structures to DER-encoded. func ConvertToDER(ber []byte) ([]byte, error) { - v, _, err := decode(ber) + v, err := decode(ber) if err != nil { return nil, err } @@ -40,34 +42,110 @@ func ConvertToDER(ber []byte) ([]byte, error) { } // decode decodes BER-encoded ASN.1 data structures. -func decode(r []byte) (value, []byte, error) { - identifier, r, err := decodeIdentifier(r) +func decode(r []byte) (value, error) { + var ( + identifier []byte + contentLen int + berValueLen int + isPrimitive bool + err error + ) + // prepare the first value + identifier, contentLen, _, isPrimitive, r, err = decodeMetadata(r) if err != nil { - return nil, nil, err - } - contentLength, r, err := decodeLength(r) - if err != nil { - return nil, nil, err - } - - if contentLength > len(r) { - return nil, nil, ErrEarlyEOF + return nil, err } - content := r[:contentLength] - r = r[contentLength:] - isPrimitive := identifier[0]&0x20 == 0 // primitive value if isPrimitive { - return newPrimitiveValue(identifier, content), r, nil + if contentLen != len(r) { + return nil, ErrBytesAtTheEnd + } + return primitiveValue{ + identifier: identifier, + content: r[:contentLen], + }, nil } - // constructed value - v, err := newConstructedValue(identifier, content) + rootConstructed := constructedValue{ + identifier: identifier, + expectedLen: contentLen, + } + + // start deep-first decoding with stack + valueStack := []*constructedValue{&rootConstructed} + for len(valueStack) > 0 { + stackLen := len(valueStack) + // top + v := valueStack[stackLen-1] + + if v.expectedLen < 0 { + return nil, ErrInvalidBerData + } + + if v.expectedLen == 0 { + // calculate the length of the constructed value + for _, m := range v.members { + v.length += m.EncodedLen() + } + + // pop the constructued value + valueStack = valueStack[:stackLen-1] + break + } + + for v.expectedLen > 0 { + identifier, contentLen, berValueLen, isPrimitive, r, err = decodeMetadata(r) + if err != nil { + return nil, err + } + if isPrimitive { + // primitive value + pv := primitiveValue{ + identifier: identifier, + content: r[:contentLen], + } + r = r[contentLen:] + v.expectedLen -= berValueLen + v.members = append(v.members, &pv) + } else { + // constructed value + cv := constructedValue{ + identifier: identifier, + expectedLen: contentLen, + } + v.expectedLen -= berValueLen + v.members = append(v.members, &cv) + valueStack = append(valueStack, &cv) + // break to start decoding the new constructed value + break + } + } + } + if len(r) > 0 { + return nil, ErrBytesAtTheEnd + } + return rootConstructed, nil +} + +func decodeMetadata(r []byte) ([]byte, int, int, bool, []byte, error) { + length := len(r) + identifier, r, err := decodeIdentifier(r) + if err != nil { + return nil, 0, 0, false, nil, err + } + contentLen, r, err := decodeLen(r) if err != nil { - return nil, nil, err + return nil, 0, 0, false, nil, err + } + + if contentLen > len(r) { + return nil, 0, 0, false, nil, ErrEarlyEOF } - return v, r, nil + isPrimitive := identifier[0]&0x20 == 0 + metadataLen := length - len(r) + berValueLen := metadataLen + contentLen + return identifier, contentLen, berValueLen, isPrimitive, r, nil } // decodeIdentifier decodes decodeIdentifier octets. @@ -88,9 +166,9 @@ func decodeIdentifier(r []byte) ([]byte, []byte, error) { return r[:offset], r[offset:], nil } -// decodeLength decodes length octets. +// decodeLen decodes length octets. // Indefinite length is not supported -func decodeLength(r []byte) (int, []byte, error) { +func decodeLen(r []byte) (int, []byte, error) { offset := 0 if len(r) < 1 { return 0, nil, ErrEarlyEOF diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index 26a142f1..cfdb9de0 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -4,33 +4,10 @@ import "bytes" // constructedValue represents a value in constructed encoding. type constructedValue struct { - identifier []byte - length int - members []value -} - -// newConstructedValue builds the constructed value. -func newConstructedValue(identifier []byte, content []byte) (value, error) { - var ( - members []value - value value - err error - ) - encodedLength := 0 - for len(content) > 0 { - value, content, err = decode(content) - if err != nil { - return nil, err - } - members = append(members, value) - encodedLength += value.EncodedLen() - } - - return constructedValue{ - identifier: identifier, - length: encodedLength, - members: members, - }, nil + identifier []byte + expectedLen int + length int + members []value } // Encode encodes the constructed value to the value writer in DER. diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index f13f2d7b..afb8c8b4 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -8,14 +8,6 @@ type primitiveValue struct { content []byte } -// newPrimitiveValue builds the primitive value. -func newPrimitiveValue(identifier []byte, content []byte) value { - return primitiveValue{ - identifier: identifier, - content: content, - } -} - // Encode encodes the primitive value to the value writer in DER. func (v primitiveValue) Encode(w *bytes.Buffer) error { _, err := w.Write(v.identifier) From 234574046999798f70cdead8232446b71bb23ff1 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 2 Aug 2023 13:01:38 +0800 Subject: [PATCH 08/28] fix: added copyright Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 15 ++++++++++++++- internal/encoding/asn1/asn1_test.go | 13 +++++++++++++ internal/encoding/asn1/common.go | 13 +++++++++++++ internal/encoding/asn1/constructed.go | 13 +++++++++++++ internal/encoding/asn1/primitive.go | 13 +++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 007257ca..2e771890 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Package asn1 decodes BER-encoded ASN.1 data structures and encodes in DER. // Note: DER is a subset of BER. // Reference: http://luca.ntop.org/Teaching/Appunti/asn1.html @@ -72,7 +85,7 @@ func decode(r []byte) (value, error) { expectedLen: contentLen, } - // start deep-first decoding with stack + // start depth-first decoding with stack valueStack := []*constructedValue{&rootConstructed} for len(valueStack) > 0 { stackLen := len(valueStack) diff --git a/internal/encoding/asn1/asn1_test.go b/internal/encoding/asn1/asn1_test.go index c2fa90d1..90ca3aea 100644 --- a/internal/encoding/asn1/asn1_test.go +++ b/internal/encoding/asn1/asn1_test.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package asn1 import ( diff --git a/internal/encoding/asn1/common.go b/internal/encoding/asn1/common.go index 0c904cb1..b84f24dc 100644 --- a/internal/encoding/asn1/common.go +++ b/internal/encoding/asn1/common.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package asn1 import ( diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index cfdb9de0..440e2692 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -2,6 +2,19 @@ package asn1 import "bytes" +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // constructedValue represents a value in constructed encoding. type constructedValue struct { identifier []byte diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index afb8c8b4..c7390815 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -1,3 +1,16 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package asn1 import "bytes" From b5d5131b5ebf32b6901d41546b7dac75d09f9b5b Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 09:15:23 +0800 Subject: [PATCH 09/28] fix: expectedLen == 0 should continue Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 2e771890..eee8d746 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -104,7 +104,7 @@ func decode(r []byte) (value, error) { // pop the constructued value valueStack = valueStack[:stackLen-1] - break + continue } for v.expectedLen > 0 { From 643f3886ebfb1f75af4b3572d4dc93bbdc151f3c Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 09:17:39 +0800 Subject: [PATCH 10/28] fix: update comment Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index eee8d746..3fac5e3b 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -130,7 +130,8 @@ func decode(r []byte) (value, error) { v.expectedLen -= berValueLen v.members = append(v.members, &cv) valueStack = append(valueStack, &cv) - // break to start decoding the new constructed value + // break to start decoding the new constructed value in the next + // iteration break } } From bc18cae59daa3700b49503c46bd6555634c599e5 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:14:57 +0800 Subject: [PATCH 11/28] fix: resolve comments Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 77 +++++++++++++++------------ internal/encoding/asn1/common.go | 12 ++--- internal/encoding/asn1/constructed.go | 12 ++--- internal/encoding/asn1/primitive.go | 4 +- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 3fac5e3b..1d27cc5f 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -19,17 +19,16 @@ package asn1 import ( "bytes" "encoding/asn1" + "math" ) // Common errors var ( - ErrBytesAtTheEnd = asn1.StructuralError{Msg: "invalid bytes at the end of the BER data"} - ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} - ErrInvalidBerData = asn1.StructuralError{Msg: "invalid BER data"} - ErrInvalidOffset = asn1.StructuralError{Msg: "invalid offset"} - ErrInvalidSlice = asn1.StructuralError{Msg: "invalid slice"} - ErrUnsupportedLen = asn1.StructuralError{Msg: "length method not supported"} - ErrUnsupportedIndefinedLen = asn1.StructuralError{Msg: "indefinite length not supported"} + ErrBytesAtTheEnd = asn1.StructuralError{Msg: "invalid bytes at the end of the BER data"} + ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} + ErrInvalidBERData = asn1.StructuralError{Msg: "invalid BER data"} + ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} + ErrUnsupportedIndefinedLength = asn1.StructuralError{Msg: "indefinite length not supported"} ) // value represents an ASN.1 value. @@ -60,17 +59,16 @@ func decode(r []byte) (value, error) { identifier []byte contentLen int berValueLen int - isPrimitive bool err error ) // prepare the first value - identifier, contentLen, _, isPrimitive, r, err = decodeMetadata(r) + identifier, contentLen, _, r, err = decodeMetadata(r) if err != nil { return nil, err } // primitive value - if isPrimitive { + if isPrimitive(identifier[0]) { if contentLen != len(r) { return nil, ErrBytesAtTheEnd } @@ -93,7 +91,7 @@ func decode(r []byte) (value, error) { v := valueStack[stackLen-1] if v.expectedLen < 0 { - return nil, ErrInvalidBerData + return nil, ErrInvalidBERData } if v.expectedLen == 0 { @@ -108,11 +106,11 @@ func decode(r []byte) (value, error) { } for v.expectedLen > 0 { - identifier, contentLen, berValueLen, isPrimitive, r, err = decodeMetadata(r) + identifier, contentLen, berValueLen, r, err = decodeMetadata(r) if err != nil { return nil, err } - if isPrimitive { + if isPrimitive(identifier[0]) { // primitive value pv := primitiveValue{ identifier: identifier, @@ -142,32 +140,35 @@ func decode(r []byte) (value, error) { return rootConstructed, nil } -func decodeMetadata(r []byte) ([]byte, int, int, bool, []byte, error) { +func decodeMetadata(r []byte) ([]byte, int, int, []byte, error) { length := len(r) identifier, r, err := decodeIdentifier(r) if err != nil { - return nil, 0, 0, false, nil, err + return nil, 0, 0, nil, err } - contentLen, r, err := decodeLen(r) + contentLen, r, err := decodeLength(r) if err != nil { - return nil, 0, 0, false, nil, err + return nil, 0, 0, nil, err } if contentLen > len(r) { - return nil, 0, 0, false, nil, ErrEarlyEOF + return nil, 0, 0, nil, ErrEarlyEOF } - isPrimitive := identifier[0]&0x20 == 0 metadataLen := length - len(r) berValueLen := metadataLen + contentLen - return identifier, contentLen, berValueLen, isPrimitive, r, nil + return identifier, contentLen, berValueLen, r, nil } // decodeIdentifier decodes decodeIdentifier octets. +// +// r is the input byte slice. +// The first return value is the identifier octets. +// The second return value is the subsequent value after the identifiers octets. func decodeIdentifier(r []byte) ([]byte, []byte, error) { - offset := 0 if len(r) < 1 { return nil, nil, ErrEarlyEOF } + offset := 0 b := r[offset] offset++ @@ -180,41 +181,51 @@ func decodeIdentifier(r []byte) ([]byte, []byte, error) { return r[:offset], r[offset:], nil } -// decodeLen decodes length octets. +// decodeLength decodes length octets. // Indefinite length is not supported -func decodeLen(r []byte) (int, []byte, error) { +// +// r is the input byte slice. +// The first return value is the length. +// The second return value is the subsequent value after the length octets. +func decodeLength(r []byte) (int, []byte, error) { offset := 0 if len(r) < 1 { return 0, nil, ErrEarlyEOF } b := r[offset] offset++ - switch { - case b < 0x80: + + if b < 0x80 { // short form return int(b), r[offset:], nil - case b == 0x80: + } else if b == 0x80 { // Indefinite-length method is not supported. - return 0, nil, ErrUnsupportedIndefinedLen + return 0, nil, ErrUnsupportedIndefinedLength } // long form n := int(b & 0x7f) if n > 4 { // length must fit the memory space of the int type. - return 0, nil, ErrUnsupportedLen + return 0, nil, ErrUnsupportedLength } - var length int + var length uint64 for i := 0; i < n; i++ { if offset >= len(r) { return 0, nil, ErrEarlyEOF } - length = (length << 8) | int(r[offset]) + length = (length << 8) | uint64(r[offset]) offset++ } - if length < 0 { + if length > uint64(math.MaxInt64) { // double check in case that length is over 31 bits. - return 0, nil, ErrUnsupportedLen + return 0, nil, ErrUnsupportedLength } - return length, r[offset:], nil + return int(length), r[offset:], nil +} + +// isPrimitive returns true if the first identifier octet is marked +// as primitive. +func isPrimitive(b byte) bool { + return b&0x20 == 0 } diff --git a/internal/encoding/asn1/common.go b/internal/encoding/asn1/common.go index b84f24dc..eb93ba78 100644 --- a/internal/encoding/asn1/common.go +++ b/internal/encoding/asn1/common.go @@ -14,18 +14,18 @@ package asn1 import ( - "bytes" + "io" ) -// encodeLen encodes length octets in DER. -func encodeLen(w *bytes.Buffer, length int) error { +// encodeLength encodes length octets in DER. +func encodeLength(w io.ByteWriter, length int) error { // DER restriction: short form must be used for length less than 128 if length < 0x80 { return w.WriteByte(byte(length)) } // DER restriction: long form must be encoded in the minimum number of octets - lengthSize := encodedLenSize(length) + lengthSize := encodedLengthSize(length) err := w.WriteByte(0x80 | byte(lengthSize-1)) if err != nil { return err @@ -38,8 +38,8 @@ func encodeLen(w *bytes.Buffer, length int) error { return nil } -// encodedLenSize gives the number of octets used for encoding the length. -func encodedLenSize(length int) int { +// encodedLengthSize gives the number of octets used for encoding the length. +func encodedLengthSize(length int) int { if length < 0x80 { return 1 } diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index 440e2692..eeac2b93 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -1,7 +1,3 @@ -package asn1 - -import "bytes" - // Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +11,10 @@ import "bytes" // See the License for the specific language governing permissions and // limitations under the License. +package asn1 + +import "bytes" + // constructedValue represents a value in constructed encoding. type constructedValue struct { identifier []byte @@ -29,7 +29,7 @@ func (v constructedValue) Encode(w *bytes.Buffer) error { if err != nil { return err } - if err = encodeLen(w, v.length); err != nil { + if err = encodeLength(w, v.length); err != nil { return err } for _, value := range v.members { @@ -42,5 +42,5 @@ func (v constructedValue) Encode(w *bytes.Buffer) error { // EncodedLen returns the length in bytes of the encoded data. func (v constructedValue) EncodedLen() int { - return len(v.identifier) + encodedLenSize(v.length) + v.length + return len(v.identifier) + encodedLengthSize(v.length) + v.length } diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index c7390815..a2e9115b 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -27,7 +27,7 @@ func (v primitiveValue) Encode(w *bytes.Buffer) error { if err != nil { return err } - if err = encodeLen(w, len(v.content)); err != nil { + if err = encodeLength(w, len(v.content)); err != nil { return err } _, err = w.Write(v.content) @@ -36,5 +36,5 @@ func (v primitiveValue) Encode(w *bytes.Buffer) error { // EncodedLen returns the length in bytes of the encoded data. func (v primitiveValue) EncodedLen() int { - return len(v.identifier) + encodedLenSize(len(v.content)) + len(v.content) + return len(v.identifier) + encodedLengthSize(len(v.content)) + len(v.content) } From a8ba0ff99ce35a252af4e30c9b64927d27d11354 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:26:29 +0800 Subject: [PATCH 12/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 1d27cc5f..04052560 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -177,6 +177,10 @@ func decodeIdentifier(r []byte) ([]byte, []byte, error) { for offset < len(r) && r[offset]&0x80 == 0x80 { offset++ } + if offset >= len(r) { + return nil, nil, ErrEarlyEOF + } + offset++ } return r[:offset], r[offset:], nil } From edb729cb1628ba2c514abfeebd02cd002fa618a0 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:32:47 +0800 Subject: [PATCH 13/28] fix: resolve comment Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 04052560..532535db 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -213,11 +213,11 @@ func decodeLength(r []byte) (int, []byte, error) { // length must fit the memory space of the int type. return 0, nil, ErrUnsupportedLength } + if offset+n >= len(r) { + return 0, nil, ErrEarlyEOF + } var length uint64 for i := 0; i < n; i++ { - if offset >= len(r) { - return 0, nil, ErrEarlyEOF - } length = (length << 8) | uint64(r[offset]) offset++ } From 22afdf81b95eb45e4767d61b35e6fe1218d8d248 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:34:34 +0800 Subject: [PATCH 14/28] fix: resolve comment Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 532535db..5dd6fd7e 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -192,10 +192,10 @@ func decodeIdentifier(r []byte) ([]byte, []byte, error) { // The first return value is the length. // The second return value is the subsequent value after the length octets. func decodeLength(r []byte) (int, []byte, error) { - offset := 0 if len(r) < 1 { return 0, nil, ErrEarlyEOF } + offset := 0 b := r[offset] offset++ From f91631640843e1dd48a3ce13bb7b06e73a16429b Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:40:37 +0800 Subject: [PATCH 15/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 5dd6fd7e..f098d191 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -68,7 +68,7 @@ func decode(r []byte) (value, error) { } // primitive value - if isPrimitive(identifier[0]) { + if isPrimitive(identifier) { if contentLen != len(r) { return nil, ErrBytesAtTheEnd } @@ -110,7 +110,7 @@ func decode(r []byte) (value, error) { if err != nil { return nil, err } - if isPrimitive(identifier[0]) { + if isPrimitive(identifier) { // primitive value pv := primitiveValue{ identifier: identifier, @@ -230,6 +230,6 @@ func decodeLength(r []byte) (int, []byte, error) { // isPrimitive returns true if the first identifier octet is marked // as primitive. -func isPrimitive(b byte) bool { - return b&0x20 == 0 +func isPrimitive(identifier []byte) bool { + return identifier[0]&0x20 == 0 } From f4dc95f6a97ca31a2ebe963a7844a2c0b6ef87c8 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:41:57 +0800 Subject: [PATCH 16/28] fix: resolve comment Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index f098d191..b8eaebfe 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -24,9 +24,9 @@ import ( // Common errors var ( - ErrBytesAtTheEnd = asn1.StructuralError{Msg: "invalid bytes at the end of the BER data"} ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} ErrInvalidBERData = asn1.StructuralError{Msg: "invalid BER data"} + ErrTrailingData = asn1.SyntaxError{Msg: "trailing data"} ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} ErrUnsupportedIndefinedLength = asn1.StructuralError{Msg: "indefinite length not supported"} ) @@ -70,7 +70,7 @@ func decode(r []byte) (value, error) { // primitive value if isPrimitive(identifier) { if contentLen != len(r) { - return nil, ErrBytesAtTheEnd + return nil, ErrTrailingData } return primitiveValue{ identifier: identifier, @@ -135,7 +135,7 @@ func decode(r []byte) (value, error) { } } if len(r) > 0 { - return nil, ErrBytesAtTheEnd + return nil, ErrTrailingData } return rootConstructed, nil } From 3c994028abfb250a3eaa87b295548af7c21b902b Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 16:45:09 +0800 Subject: [PATCH 17/28] fix: update comment Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index b8eaebfe..d856a43e 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -140,6 +140,14 @@ func decode(r []byte) (value, error) { return rootConstructed, nil } +// decodeMetadata decodes the metadata of a BER-encoded ASN.1 value. +// +// r is the input byte slice. +// The first return value is the identifier octets. +// The second return value is the content length. +// The third return value is the BER value length. +// The fourth return value is the subsequent value after the identifier and +// length octets. func decodeMetadata(r []byte) ([]byte, int, int, []byte, error) { length := len(r) identifier, r, err := decodeIdentifier(r) From b9c73bd63f4237480a299056df168d3099fd04d0 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 17:56:52 +0800 Subject: [PATCH 18/28] fix: update to use rawContent instead of expectedLen Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 95 +++++++++++---------------- internal/encoding/asn1/constructed.go | 8 +-- 2 files changed, 44 insertions(+), 59 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index d856a43e..c8ef6abc 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -56,22 +56,21 @@ func ConvertToDER(ber []byte) ([]byte, error) { // decode decodes BER-encoded ASN.1 data structures. func decode(r []byte) (value, error) { var ( - identifier []byte - contentLen int - berValueLen int - err error + identifier []byte + contentLen int + err error ) // prepare the first value - identifier, contentLen, _, r, err = decodeMetadata(r) + identifier, contentLen, r, err = decodeMetadata(r) if err != nil { return nil, err } + if contentLen != len(r) { + return nil, ErrTrailingData + } // primitive value if isPrimitive(identifier) { - if contentLen != len(r) { - return nil, ErrTrailingData - } return primitiveValue{ identifier: identifier, content: r[:contentLen], @@ -79,8 +78,8 @@ func decode(r []byte) (value, error) { } // constructed value rootConstructed := constructedValue{ - identifier: identifier, - expectedLen: contentLen, + identifier: identifier, + rawContent: r[:contentLen], } // start depth-first decoding with stack @@ -90,52 +89,42 @@ func decode(r []byte) (value, error) { // top v := valueStack[stackLen-1] - if v.expectedLen < 0 { - return nil, ErrInvalidBERData - } - - if v.expectedLen == 0 { - // calculate the length of the constructed value + // the constructed value has been docoded + if len(v.rawContent) == 0 { for _, m := range v.members { v.length += m.EncodedLen() } - // pop the constructued value + // pop valueStack = valueStack[:stackLen-1] continue } - for v.expectedLen > 0 { - identifier, contentLen, berValueLen, r, err = decodeMetadata(r) - if err != nil { - return nil, err + // decode the next member of the constructed value + identifier, contentLen, v.rawContent, err = decodeMetadata(v.rawContent) + if err != nil { + return nil, err + } + if contentLen > len(v.rawContent) { + return nil, ErrEarlyEOF + } + if isPrimitive(identifier) { + // primitive value + pv := primitiveValue{ + identifier: identifier, + content: v.rawContent[:contentLen], } - if isPrimitive(identifier) { - // primitive value - pv := primitiveValue{ - identifier: identifier, - content: r[:contentLen], - } - r = r[contentLen:] - v.expectedLen -= berValueLen - v.members = append(v.members, &pv) - } else { - // constructed value - cv := constructedValue{ - identifier: identifier, - expectedLen: contentLen, - } - v.expectedLen -= berValueLen - v.members = append(v.members, &cv) - valueStack = append(valueStack, &cv) - // break to start decoding the new constructed value in the next - // iteration - break + v.members = append(v.members, &pv) + } else { + // constructed value + cv := constructedValue{ + identifier: identifier, + rawContent: v.rawContent[:contentLen], } + v.members = append(v.members, &cv) + valueStack = append(valueStack, &cv) } - } - if len(r) > 0 { - return nil, ErrTrailingData + v.rawContent = v.rawContent[contentLen:] } return rootConstructed, nil } @@ -145,26 +134,22 @@ func decode(r []byte) (value, error) { // r is the input byte slice. // The first return value is the identifier octets. // The second return value is the content length. -// The third return value is the BER value length. -// The fourth return value is the subsequent value after the identifier and +// The third return value is the subsequent value after the identifier and // length octets. -func decodeMetadata(r []byte) ([]byte, int, int, []byte, error) { - length := len(r) +func decodeMetadata(r []byte) ([]byte, int, []byte, error) { identifier, r, err := decodeIdentifier(r) if err != nil { - return nil, 0, 0, nil, err + return nil, 0, nil, err } contentLen, r, err := decodeLength(r) if err != nil { - return nil, 0, 0, nil, err + return nil, 0, nil, err } if contentLen > len(r) { - return nil, 0, 0, nil, ErrEarlyEOF + return nil, 0, nil, ErrEarlyEOF } - metadataLen := length - len(r) - berValueLen := metadataLen + contentLen - return identifier, contentLen, berValueLen, r, nil + return identifier, contentLen, r, nil } // decodeIdentifier decodes decodeIdentifier octets. diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index eeac2b93..c74e152c 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -17,10 +17,10 @@ import "bytes" // constructedValue represents a value in constructed encoding. type constructedValue struct { - identifier []byte - expectedLen int - length int - members []value + identifier []byte + length int + members []value + rawContent []byte // the raw content of BER. } // Encode encodes the constructed value to the value writer in DER. From 6cfbd9c03583d35fa8821f647d36c3d63f462d31 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 19:47:39 +0800 Subject: [PATCH 19/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index c8ef6abc..398912b0 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -89,12 +89,12 @@ func decode(r []byte) (value, error) { // top v := valueStack[stackLen-1] - // the constructed value has been docoded + // check that the constructed value is fully encoded if len(v.rawContent) == 0 { + // calculate the length of the members for _, m := range v.members { v.length += m.EncodedLen() } - // pop valueStack = valueStack[:stackLen-1] continue @@ -122,6 +122,8 @@ func decode(r []byte) (value, error) { rawContent: v.rawContent[:contentLen], } v.members = append(v.members, &cv) + + // add a new constructed node to the stack valueStack = append(valueStack, &cv) } v.rawContent = v.rawContent[contentLen:] From 6524a9ce6f7363edcb001322815dd2e760fd361e Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 19:53:27 +0800 Subject: [PATCH 20/28] fix: update variable naming Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 28 +++++++++++++-------------- internal/encoding/asn1/constructed.go | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 398912b0..e8bc66b4 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -87,13 +87,13 @@ func decode(r []byte) (value, error) { for len(valueStack) > 0 { stackLen := len(valueStack) // top - v := valueStack[stackLen-1] + node := valueStack[stackLen-1] // check that the constructed value is fully encoded - if len(v.rawContent) == 0 { + if len(node.rawContent) == 0 { // calculate the length of the members - for _, m := range v.members { - v.length += m.EncodedLen() + for _, m := range node.members { + node.length += m.EncodedLen() } // pop valueStack = valueStack[:stackLen-1] @@ -101,32 +101,32 @@ func decode(r []byte) (value, error) { } // decode the next member of the constructed value - identifier, contentLen, v.rawContent, err = decodeMetadata(v.rawContent) + identifier, contentLen, node.rawContent, err = decodeMetadata(node.rawContent) if err != nil { return nil, err } - if contentLen > len(v.rawContent) { + if contentLen > len(node.rawContent) { return nil, ErrEarlyEOF } if isPrimitive(identifier) { // primitive value - pv := primitiveValue{ + primitiveNode := primitiveValue{ identifier: identifier, - content: v.rawContent[:contentLen], + content: node.rawContent[:contentLen], } - v.members = append(v.members, &pv) + node.members = append(node.members, &primitiveNode) } else { // constructed value - cv := constructedValue{ + constructedNode := constructedValue{ identifier: identifier, - rawContent: v.rawContent[:contentLen], + rawContent: node.rawContent[:contentLen], } - v.members = append(v.members, &cv) + node.members = append(node.members, &constructedNode) // add a new constructed node to the stack - valueStack = append(valueStack, &cv) + valueStack = append(valueStack, &constructedNode) } - v.rawContent = v.rawContent[contentLen:] + node.rawContent = node.rawContent[contentLen:] } return rootConstructed, nil } diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index c74e152c..f662808f 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -20,7 +20,7 @@ type constructedValue struct { identifier []byte length int members []value - rawContent []byte // the raw content of BER. + rawContent []byte // the raw content of BER } // Encode encodes the constructed value to the value writer in DER. From 1465e3e1bb07a4de9f25c9b9fc89debdad34abe4 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 20:04:44 +0800 Subject: [PATCH 21/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index e8bc66b4..b61863c0 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -89,7 +89,7 @@ func decode(r []byte) (value, error) { // top node := valueStack[stackLen-1] - // check that the constructed value is fully encoded + // check that the constructed value is fully decoded if len(node.rawContent) == 0 { // calculate the length of the members for _, m := range node.members { From 91a369137df03d255cf25894839c787ce9ce7785 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 20:11:28 +0800 Subject: [PATCH 22/28] fix: create pointer instead of value to improve performance Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index b61863c0..617119b9 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -71,19 +71,19 @@ func decode(r []byte) (value, error) { // primitive value if isPrimitive(identifier) { - return primitiveValue{ + return &primitiveValue{ identifier: identifier, content: r[:contentLen], }, nil } // constructed value - rootConstructed := constructedValue{ + rootConstructed := &constructedValue{ identifier: identifier, rawContent: r[:contentLen], } // start depth-first decoding with stack - valueStack := []*constructedValue{&rootConstructed} + valueStack := []*constructedValue{rootConstructed} for len(valueStack) > 0 { stackLen := len(valueStack) // top @@ -110,21 +110,21 @@ func decode(r []byte) (value, error) { } if isPrimitive(identifier) { // primitive value - primitiveNode := primitiveValue{ + primitiveNode := &primitiveValue{ identifier: identifier, content: node.rawContent[:contentLen], } - node.members = append(node.members, &primitiveNode) + node.members = append(node.members, primitiveNode) } else { // constructed value - constructedNode := constructedValue{ + constructedNode := &constructedValue{ identifier: identifier, rawContent: node.rawContent[:contentLen], } - node.members = append(node.members, &constructedNode) + node.members = append(node.members, constructedNode) // add a new constructed node to the stack - valueStack = append(valueStack, &constructedNode) + valueStack = append(valueStack, constructedNode) } node.rawContent = node.rawContent[contentLen:] } From 9b6a0c526189ea04d42a42da07e9c2349ec4c33a Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Thu, 3 Aug 2023 20:25:22 +0800 Subject: [PATCH 23/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 38 +++++++++++++--------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 617119b9..fa24970d 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -55,17 +55,12 @@ func ConvertToDER(ber []byte) ([]byte, error) { // decode decodes BER-encoded ASN.1 data structures. func decode(r []byte) (value, error) { - var ( - identifier []byte - contentLen int - err error - ) // prepare the first value - identifier, contentLen, r, err = decodeMetadata(r) + identifier, content, r, err := decodeMetadata(r) if err != nil { return nil, err } - if contentLen != len(r) { + if len(r) != 0 { return nil, ErrTrailingData } @@ -73,13 +68,13 @@ func decode(r []byte) (value, error) { if isPrimitive(identifier) { return &primitiveValue{ identifier: identifier, - content: r[:contentLen], + content: content, }, nil } // constructed value rootConstructed := &constructedValue{ identifier: identifier, - rawContent: r[:contentLen], + rawContent: content, } // start depth-first decoding with stack @@ -101,32 +96,28 @@ func decode(r []byte) (value, error) { } // decode the next member of the constructed value - identifier, contentLen, node.rawContent, err = decodeMetadata(node.rawContent) + identifier, content, node.rawContent, err = decodeMetadata(node.rawContent) if err != nil { return nil, err } - if contentLen > len(node.rawContent) { - return nil, ErrEarlyEOF - } if isPrimitive(identifier) { // primitive value primitiveNode := &primitiveValue{ identifier: identifier, - content: node.rawContent[:contentLen], + content: content, } node.members = append(node.members, primitiveNode) } else { // constructed value constructedNode := &constructedValue{ identifier: identifier, - rawContent: node.rawContent[:contentLen], + rawContent: content, } node.members = append(node.members, constructedNode) // add a new constructed node to the stack valueStack = append(valueStack, constructedNode) } - node.rawContent = node.rawContent[contentLen:] } return rootConstructed, nil } @@ -135,23 +126,22 @@ func decode(r []byte) (value, error) { // // r is the input byte slice. // The first return value is the identifier octets. -// The second return value is the content length. -// The third return value is the subsequent value after the identifier and -// length octets. -func decodeMetadata(r []byte) ([]byte, int, []byte, error) { +// The second return value is the content octets. +// The third return value is the subsequent octets after the value. +func decodeMetadata(r []byte) ([]byte, []byte, []byte, error) { identifier, r, err := decodeIdentifier(r) if err != nil { - return nil, 0, nil, err + return nil, nil, nil, err } contentLen, r, err := decodeLength(r) if err != nil { - return nil, 0, nil, err + return nil, nil, nil, err } if contentLen > len(r) { - return nil, 0, nil, ErrEarlyEOF + return nil, nil, nil, ErrEarlyEOF } - return identifier, contentLen, r, nil + return identifier, r[:contentLen], r[contentLen:], nil } // decodeIdentifier decodes decodeIdentifier octets. From 8f1a2af3c061df99f10edba3625569d788638261 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Fri, 4 Aug 2023 13:40:09 +0800 Subject: [PATCH 24/28] fix: remove unused value Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index fa24970d..7743b514 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -25,7 +25,6 @@ import ( // Common errors var ( ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} - ErrInvalidBERData = asn1.StructuralError{Msg: "invalid BER data"} ErrTrailingData = asn1.SyntaxError{Msg: "trailing data"} ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} ErrUnsupportedIndefinedLength = asn1.StructuralError{Msg: "indefinite length not supported"} From 41ecec67b96772ff8db4e3378c953c64e83e8880 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Sun, 6 Aug 2023 17:33:19 +0800 Subject: [PATCH 25/28] fix: remove recusive call for encode() Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 46 +++++++++++++++++---------- internal/encoding/asn1/constructed.go | 5 --- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 7743b514..aabccca7 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -19,15 +19,15 @@ package asn1 import ( "bytes" "encoding/asn1" - "math" ) // Common errors var ( - ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} - ErrTrailingData = asn1.SyntaxError{Msg: "trailing data"} - ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} - ErrUnsupportedIndefinedLength = asn1.StructuralError{Msg: "indefinite length not supported"} + ErrEarlyEOF = asn1.SyntaxError{Msg: "early EOF"} + ErrTrailingData = asn1.SyntaxError{Msg: "trailing data"} + ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} + ErrUnsupportedIndefiniteLength = asn1.StructuralError{Msg: "indefinite length not supported"} + ErrUnsupportedValueType = asn1.StructuralError{Msg: "value type not supported"} ) // value represents an ASN.1 value. @@ -41,19 +41,27 @@ type value interface { // ConvertToDER converts BER-encoded ASN.1 data structures to DER-encoded. func ConvertToDER(ber []byte) ([]byte, error) { - v, err := decode(ber) + flatValues, err := decode(ber) if err != nil { return nil, err } - buf := bytes.NewBuffer(make([]byte, 0, v.EncodedLen())) - if err = v.Encode(buf); err != nil { - return nil, err + + // get the total length from the root value and allocate a buffer + buf := bytes.NewBuffer(make([]byte, 0, flatValues[0].EncodedLen())) + for _, v := range flatValues { + if err = v.Encode(buf); err != nil { + return nil, err + } } + return buf.Bytes(), nil } // decode decodes BER-encoded ASN.1 data structures. -func decode(r []byte) (value, error) { +// +// r is the input byte slice. +// The returned value is the flat ASN.1 values slice. +func decode(r []byte) ([]value, error) { // prepare the first value identifier, content, r, err := decodeMetadata(r) if err != nil { @@ -65,16 +73,19 @@ func decode(r []byte) (value, error) { // primitive value if isPrimitive(identifier) { - return &primitiveValue{ + return []value{&primitiveValue{ identifier: identifier, content: content, - }, nil + }}, nil } + + flatValues := []value{} // constructed value rootConstructed := &constructedValue{ identifier: identifier, rawContent: content, } + flatValues = append(flatValues, rootConstructed) // start depth-first decoding with stack valueStack := []*constructedValue{rootConstructed} @@ -106,6 +117,7 @@ func decode(r []byte) (value, error) { content: content, } node.members = append(node.members, primitiveNode) + flatValues = append(flatValues, primitiveNode) } else { // constructed value constructedNode := &constructedValue{ @@ -116,9 +128,10 @@ func decode(r []byte) (value, error) { // add a new constructed node to the stack valueStack = append(valueStack, constructedNode) + flatValues = append(flatValues, constructedNode) } } - return rootConstructed, nil + return flatValues, nil } // decodeMetadata decodes the metadata of a BER-encoded ASN.1 value. @@ -188,7 +201,7 @@ func decodeLength(r []byte) (int, []byte, error) { return int(b), r[offset:], nil } else if b == 0x80 { // Indefinite-length method is not supported. - return 0, nil, ErrUnsupportedIndefinedLength + return 0, nil, ErrUnsupportedIndefiniteLength } // long form @@ -205,8 +218,9 @@ func decodeLength(r []byte) (int, []byte, error) { length = (length << 8) | uint64(r[offset]) offset++ } - if length > uint64(math.MaxInt64) { - // double check in case that length is over 31 bits. + + // length must fit the memory space of the int32. + if (length >> 31) > 0 { return 0, nil, ErrUnsupportedLength } return int(length), r[offset:], nil diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index f662808f..ff50bd8a 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -32,11 +32,6 @@ func (v constructedValue) Encode(w *bytes.Buffer) error { if err = encodeLength(w, v.length); err != nil { return err } - for _, value := range v.members { - if err = value.Encode(w); err != nil { - return err - } - } return nil } From 7b823a9065a262ccc8c3226b139a1570f9d4cedf Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 7 Aug 2023 08:54:37 +0800 Subject: [PATCH 26/28] fix: update code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index aabccca7..b6069897 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -79,13 +79,12 @@ func decode(r []byte) ([]value, error) { }}, nil } - flatValues := []value{} // constructed value rootConstructed := &constructedValue{ identifier: identifier, rawContent: content, } - flatValues = append(flatValues, rootConstructed) + flatValues := []value{rootConstructed} // start depth-first decoding with stack valueStack := []*constructedValue{rootConstructed} From 75ce02d759d624645982c7fff493e672b7206ed5 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Mon, 7 Aug 2023 20:33:08 +0800 Subject: [PATCH 27/28] fix: added Conetent method for value interface Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 24 ++++++++++++++++++++---- internal/encoding/asn1/constructed.go | 11 ++++++++--- internal/encoding/asn1/primitive.go | 14 +++++++++----- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index b6069897..1309f720 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -32,11 +32,17 @@ var ( // value represents an ASN.1 value. type value interface { - // Encode encodes the value to the value writer in DER. - Encode(*bytes.Buffer) error + // EncodeMetadata encodes the identifier and length in DER to the buffer. + EncodeMetadata(*bytes.Buffer) error // EncodedLen returns the length in bytes of the encoded data. EncodedLen() int + + // Content returns the content of the value. + // For primitive values, it returns the content octets. + // For constructed values, it returns nil because the content is + // the data of all members. + Content() []byte } // ConvertToDER converts BER-encoded ASN.1 data structures to DER-encoded. @@ -49,9 +55,17 @@ func ConvertToDER(ber []byte) ([]byte, error) { // get the total length from the root value and allocate a buffer buf := bytes.NewBuffer(make([]byte, 0, flatValues[0].EncodedLen())) for _, v := range flatValues { - if err = v.Encode(buf); err != nil { + if err = v.EncodeMetadata(buf); err != nil { return nil, err } + + if v.Content() != nil { + // primitive value + _, err = buf.Write(v.Content()) + if err != nil { + return nil, err + } + } } return buf.Bytes(), nil @@ -60,7 +74,9 @@ func ConvertToDER(ber []byte) ([]byte, error) { // decode decodes BER-encoded ASN.1 data structures. // // r is the input byte slice. -// The returned value is the flat ASN.1 values slice. +// The returned value, which is the flat slice of ASN.1 values, contains the +// nodes from a depth-first traversal. To get the DER of `r`, encode the values +// in the returned slice in order. func decode(r []byte) ([]value, error) { // prepare the first value identifier, content, r, err := decodeMetadata(r) diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index ff50bd8a..747714e0 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -23,8 +23,8 @@ type constructedValue struct { rawContent []byte // the raw content of BER } -// Encode encodes the constructed value to the value writer in DER. -func (v constructedValue) Encode(w *bytes.Buffer) error { +// EncodeMetadata encodes the constructed value to the value writer in DER. +func (v *constructedValue) EncodeMetadata(w *bytes.Buffer) error { _, err := w.Write(v.identifier) if err != nil { return err @@ -36,6 +36,11 @@ func (v constructedValue) Encode(w *bytes.Buffer) error { } // EncodedLen returns the length in bytes of the encoded data. -func (v constructedValue) EncodedLen() int { +func (v *constructedValue) EncodedLen() int { return len(v.identifier) + encodedLengthSize(v.length) + v.length } + +// Content returns the content of the value. +func (v *constructedValue) Content() []byte { + return nil +} diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index a2e9115b..b24ac9dc 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -21,8 +21,8 @@ type primitiveValue struct { content []byte } -// Encode encodes the primitive value to the value writer in DER. -func (v primitiveValue) Encode(w *bytes.Buffer) error { +// EncodeMetadata encodes the primitive value to the value writer in DER. +func (v *primitiveValue) EncodeMetadata(w *bytes.Buffer) error { _, err := w.Write(v.identifier) if err != nil { return err @@ -30,11 +30,15 @@ func (v primitiveValue) Encode(w *bytes.Buffer) error { if err = encodeLength(w, len(v.content)); err != nil { return err } - _, err = w.Write(v.content) - return err + return nil } // EncodedLen returns the length in bytes of the encoded data. -func (v primitiveValue) EncodedLen() int { +func (v *primitiveValue) EncodedLen() int { return len(v.identifier) + encodedLengthSize(len(v.content)) + len(v.content) } + +// Content returns the content of the value. +func (v *primitiveValue) Content() []byte { + return v.content +} From 419869027fb86f673f7c6d1c3c0031756aab1c6a Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 9 Aug 2023 09:14:29 +0800 Subject: [PATCH 28/28] fix: simplify code Signed-off-by: Junjie Gao --- internal/encoding/asn1/asn1.go | 5 ++--- internal/encoding/asn1/constructed.go | 5 +---- internal/encoding/asn1/primitive.go | 5 +---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go index 1309f720..28d79049 100644 --- a/internal/encoding/asn1/asn1.go +++ b/internal/encoding/asn1/asn1.go @@ -27,7 +27,6 @@ var ( ErrTrailingData = asn1.SyntaxError{Msg: "trailing data"} ErrUnsupportedLength = asn1.StructuralError{Msg: "length method not supported"} ErrUnsupportedIndefiniteLength = asn1.StructuralError{Msg: "indefinite length not supported"} - ErrUnsupportedValueType = asn1.StructuralError{Msg: "value type not supported"} ) // value represents an ASN.1 value. @@ -59,9 +58,9 @@ func ConvertToDER(ber []byte) ([]byte, error) { return nil, err } - if v.Content() != nil { + if content := v.Content(); content != nil { // primitive value - _, err = buf.Write(v.Content()) + _, err = buf.Write(content) if err != nil { return nil, err } diff --git a/internal/encoding/asn1/constructed.go b/internal/encoding/asn1/constructed.go index 747714e0..409fd5a4 100644 --- a/internal/encoding/asn1/constructed.go +++ b/internal/encoding/asn1/constructed.go @@ -29,10 +29,7 @@ func (v *constructedValue) EncodeMetadata(w *bytes.Buffer) error { if err != nil { return err } - if err = encodeLength(w, v.length); err != nil { - return err - } - return nil + return encodeLength(w, v.length) } // EncodedLen returns the length in bytes of the encoded data. diff --git a/internal/encoding/asn1/primitive.go b/internal/encoding/asn1/primitive.go index b24ac9dc..0c2cdec6 100644 --- a/internal/encoding/asn1/primitive.go +++ b/internal/encoding/asn1/primitive.go @@ -27,10 +27,7 @@ func (v *primitiveValue) EncodeMetadata(w *bytes.Buffer) error { if err != nil { return err } - if err = encodeLength(w, len(v.content)); err != nil { - return err - } - return nil + return encodeLength(w, len(v.content)) } // EncodedLen returns the length in bytes of the encoded data.