diff --git a/.gitignore b/.gitignore index 3b735ec..bb4e59d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,16 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# 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. + # Binaries for programs and plugins *.exe *.exe~ @@ -19,3 +29,12 @@ # Go workspace file go.work + +# Code Editors +.vscode +.idea +*.sublime-project +*.sublime-workspace + +# Custom +coverage.txt \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bdfcfeb --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/notaryproject/tspclient-go + +go 1.20 diff --git a/internal/encoding/asn1/ber/ber.go b/internal/encoding/asn1/ber/ber.go new file mode 100644 index 0000000..2794ded --- /dev/null +++ b/internal/encoding/asn1/ber/ber.go @@ -0,0 +1,308 @@ +// 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 ber decodes BER-encoded ASN.1 data structures and encodes in DER. +// Note: +// - DER is a subset of BER. +// - Indefinite length is not supported. +// - The length of the encoded data must fit the memory space of the int type +// (4 bytes). +// +// Reference: +// - http://luca.ntop.org/Teaching/Appunti/asn1.html +// - ISO/IEC 8825-1:2021 +// - https://learn.microsoft.com/windows/win32/seccertenroll/about-introduction-to-asn-1-syntax-and-encoding +package ber + +import ( + "bytes" + "encoding/asn1" + "fmt" +) + +// value is the interface for an ASN.1 value node. +type value interface { + // EncodeMetadata encodes the identifier and length in DER to the buffer. + EncodeMetadata(writer) error + + // EncodedLen returns the length in bytes of the data when encoding in DER. + 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. +func ConvertToDER(ber []byte) ([]byte, error) { + if len(ber) == 0 { + return nil, asn1.SyntaxError{Msg: "BER-encoded ASN.1 data structures is empty"} + } + + flatValues, err := decode(ber) + if 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.EncodeMetadata(buf); err != nil { + return nil, err + } + + if content := v.Content(); content != nil { + // primitive value + _, err = buf.Write(content) + if err != nil { + return nil, err + } + } + } + + return buf.Bytes(), nil +} + +// decode decodes BER-encoded ASN.1 data structures. +// To get the DER of `r`, encode the values +// in the returned slice in order. +// +// Parameters: +// r - The input byte slice. +// +// Return: +// []value - The returned value, which is the flat slice of ASN.1 values, +// contains the nodes from a depth-first traversal. +// error - An error that can occur during the decoding process. +// +// Reference: ISO/IEC 8825-1: 8.1.1.3 +func decode(r []byte) ([]value, error) { + // prepare the first value + identifier, contentLen, r, err := decodeMetadata(r) + if err != nil { + return nil, err + } + if len(r) != contentLen { + return nil, asn1.SyntaxError{Msg: fmt.Sprintf("decoding BER: length octets value %d does not match with content length %d", contentLen, len(r))} + } + + // primitive value + if isPrimitive(identifier) { + return []value{&primitive{ + identifier: identifier, + content: r, + }}, nil + } + + // constructed value + rootConstructed := &constructed{ + identifier: identifier, + rawContent: r, + } + flatValues := []value{rootConstructed} + + // start depth-first decoding with stack + contructedStack := []*constructed{rootConstructed} + for { + stackLen := len(contructedStack) + if stackLen == 0 { + break + } + + // top + node := contructedStack[stackLen-1] + + // check that the constructed value is fully decoded + if len(node.rawContent) == 0 { + // calculate the length of the members + for _, m := range node.members { + node.length += m.EncodedLen() + } + // pop + contructedStack = contructedStack[:stackLen-1] + continue + } + + // decode the next member of the constructed value + nextNodeIdentifier, nextNodeContentLen, remainingContent, err := decodeMetadata(node.rawContent) + if err != nil { + return nil, err + } + nextNodeContent := remainingContent[:nextNodeContentLen] + node.rawContent = remainingContent[nextNodeContentLen:] + + if isPrimitive(nextNodeIdentifier) { + // primitive value + primitiveNode := &primitive{ + identifier: nextNodeIdentifier, + content: nextNodeContent, + } + node.members = append(node.members, primitiveNode) + flatValues = append(flatValues, primitiveNode) + } else { + // constructed value + constructedNode := &constructed{ + identifier: nextNodeIdentifier, + rawContent: nextNodeContent, + } + node.members = append(node.members, constructedNode) + + // add a new constructed node to the stack + contructedStack = append(contructedStack, constructedNode) + flatValues = append(flatValues, constructedNode) + } + } + return flatValues, nil +} + +// decodeMetadata decodes the metadata of a BER-encoded ASN.1 value. +// +// Parameters: +// r - The input byte slice. +// +// Return: +// []byte - The identifier octets. +// int - The length octets value. +// []byte - The subsequent octets after the length octets. +// error - An error that can occur during the decoding process. +// +// Reference: ISO/IEC 8825-1: 8.1.1.3 +func decodeMetadata(r []byte) ([]byte, int, []byte, error) { + // structure of an encoding (primitive or constructed) + // +----------------+----------------+----------------+ + // | identifier | length | content | + // +----------------+----------------+----------------+ + identifier, r, err := decodeIdentifier(r) + if err != nil { + return nil, 0, nil, err + } + + contentLen, r, err := decodeLength(r) + if err != nil { + return nil, 0, nil, err + } + + return identifier, contentLen, r, nil +} + +// decodeIdentifier decodes decodeIdentifier octets. +// +// Parameters: +// r - The input byte slice from which the identifier octets are to be decoded. +// +// Returns: +// []byte - The identifier octets decoded from the input byte slice. +// []byte - The remaining part of the input byte slice after the identifier octets. +// error - An error that can occur during the decoding process. +// +// Reference: ISO/IEC 8825-1: 8.1.2 +func decodeIdentifier(r []byte) ([]byte, []byte, error) { + if len(r) < 1 { + return nil, nil, asn1.SyntaxError{Msg: "decoding BER identifier octets: identifier octets is empty"} + } + offset := 0 + b := r[offset] + offset++ + + // high-tag-number form + // Reference: ISO/IEC 8825-1: 8.1.2.4 + if b&0x1f == 0x1f { + for offset < len(r) && r[offset]&0x80 == 0x80 { + offset++ + } + if offset >= len(r) { + return nil, nil, asn1.SyntaxError{Msg: "decoding BER identifier octets: high-tag-number form with early EOF"} + } + offset++ + } + + if offset >= len(r) { + return nil, nil, asn1.SyntaxError{Msg: "decoding BER identifier octets: early EOF due to missing length and content octets"} + } + return r[:offset], r[offset:], nil +} + +// decodeLength decodes length octets. +// Indefinite length is not supported +// +// Parameters: +// r - The input byte slice from which the length octets are to be decoded. +// +// Returns: +// int - The length decoded from the input byte slice. +// []byte - The remaining part of the input byte slice after the length octets. +// error - An error that can occur during the decoding process. +// +// Reference: ISO/IEC 8825-1: 8.1.3 +func decodeLength(r []byte) (int, []byte, error) { + if len(r) < 1 { + return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: length octets is empty"} + } + offset := 0 + b := r[offset] + offset++ + + if b < 0x80 { + // short form + // Reference: ISO/IEC 8825-1: 8.1.3.4 + contentLen := int(b) + subsequentOctets := r[offset:] + if contentLen > len(subsequentOctets) { + return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: short form length octets value should be less or equal to the subsequent octets length"} + } + return contentLen, subsequentOctets, nil + } + + if b == 0x80 { + // Indefinite-length method is not supported. + // Reference: ISO/IEC 8825-1: 8.1.3.6.1 + return 0, nil, asn1.StructuralError{Msg: "decoding BER length octets: indefinite length not supported"} + } + + // long form + // Reference: ISO/IEC 8825-1: 8.1.3.5 + n := int(b & 0x7f) + if n > 4 { + // length must fit the memory space of the int type (4 bytes). + return 0, nil, asn1.StructuralError{Msg: fmt.Sprintf("decoding BER length octets: length of encoded data (%d bytes) cannot exceed 4 bytes", n)} + } + if offset+n >= len(r) { + return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: long form length octets with early EOF"} + } + var length uint64 + for i := 0; i < n; i++ { + length = (length << 8) | uint64(r[offset]) + offset++ + } + + // length must fit the memory space of the int32. + if (length >> 31) > 0 { + return 0, nil, asn1.StructuralError{Msg: fmt.Sprintf("decoding BER length octets: length %d does not fit the memory space of int32", length)} + } + + contentLen := int(length) + subsequentOctets := r[offset:] + if contentLen > len(subsequentOctets) { + return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: long form length octets value should be less or equal to the subsequent octets length"} + } + return contentLen, subsequentOctets, nil +} + +// isPrimitive returns true if the first identifier octet is marked +// as primitive. +// Reference: ISO/IEC 8825-1: 8.1.2.5 +func isPrimitive(identifier []byte) bool { + return identifier[0]&0x20 == 0 +} diff --git a/internal/encoding/asn1/ber/ber_test.go b/internal/encoding/asn1/ber/ber_test.go new file mode 100644 index 0000000..0004c36 --- /dev/null +++ b/internal/encoding/asn1/ber/ber_test.go @@ -0,0 +1,351 @@ +// 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 ber + +import ( + "reflect" + "testing" +) + +func TestConvertToDER(t *testing.T) { + var testBytes = make([]byte, 0xFFFFFFFF+8) + // primitive identifier + testBytes[0] = 0x1f + testBytes[1] = 0xa0 + testBytes[2] = 0x20 + // length + testBytes[3] = 0x84 + testBytes[4] = 0xFF + testBytes[5] = 0xFF + testBytes[6] = 0xFF + testBytes[7] = 0xFF + + type data struct { + name string + ber []byte + der []byte + expectError bool + } + testData := []data{ + { + name: "Constructed value", + 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: []byte{ + // Constructed value + 0x30, + // Constructed value length + 0x2d, + + // Type identifier + 0x06, + // Type length + 0x09, + // Type content + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + + // Value identifier + 0x04, + // Value length in BER + 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, + }, + expectError: false, + }, + { + name: "Primitive value", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0x20, + // length + 0x81, 0x01, + // content + 0x01, + }, + der: []byte{ + // Primitive value + // identifier + 0x1f, 0x20, + // length + 0x01, + // content + 0x01, + }, + expectError: false, + }, + { + name: "Constructed value in constructed value", + ber: []byte{ + // Constructed value + 0x30, + // Constructed value length + 0x2d, + + // Constructed value identifier + 0x26, + // Type length + 0x2b, + + // Value identifier + 0x04, + // Value length in BER + 0x81, 0x28, + // Value content + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, + 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: []byte{ + // Constructed value + 0x30, + // Constructed value length + 0x2c, + + // Constructed value identifier + 0x26, + // Type length + 0x2a, + + // Value identifier + 0x04, + // Value length in BER + 0x28, + // Value content + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, + 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, + }, + expectError: false, + }, + { + name: "empty", + ber: []byte{}, + der: []byte{}, + expectError: true, + }, + { + name: "identifier high tag number form", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x81, 0x01, + // content + 0x01, + }, + der: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x01, + // content + 0x01, + }, + expectError: false, + }, + { + name: "EarlyEOF for identifier high tag number form", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, + }, + der: []byte{}, + expectError: true, + }, + { + name: "EarlyEOF for length", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + }, + der: []byte{}, + expectError: true, + }, + { + name: "Unsupport indefinite-length", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x80, + }, + der: []byte{}, + expectError: true, + }, + { + name: "length greater than 4 bytes", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x85, + }, + der: []byte{}, + expectError: true, + }, + { + name: "long form length EarlyEOF ", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x84, + }, + der: []byte{}, + expectError: true, + }, + { + name: "length greater than content", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x02, + }, + der: []byte{}, + expectError: true, + }, + { + name: "trailing data", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x02, + // content + 0x01, 0x02, 0x03, + }, + der: []byte{}, + expectError: true, + }, + { + name: "EarlyEOF in constructed value", + ber: []byte{ + // Constructed value + 0x30, + // Constructed value length + 0x2c, + + // Constructed value identifier + 0x26, + // Type length + 0x2b, + + // Value identifier + 0x04, + // Value length in BER + 0x81, 0x28, + // Value content + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, + 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, + }, + expectError: true, + }, + { + name: "length greater > int32", + ber: testBytes[:], + der: []byte{}, + expectError: true, + }, + { + name: "long form length greather than subsequent octets length ", + ber: []byte{ + // Primitive value + // identifier + 0x1f, 0xa0, 0x20, + // length + 0x81, 0x09, + // content + 0x01, + }, + der: []byte{}, + expectError: true, + }, + } + + for _, tt := range testData { + der, err := ConvertToDER(tt.ber) + if !tt.expectError && err != nil { + t.Errorf("ConvertToDER() error = %v, but expect no error", err) + return + } + if tt.expectError && err == nil { + t.Errorf("ConvertToDER() error = nil, but expect error") + } + + if !tt.expectError && !reflect.DeepEqual(der, tt.der) { + t.Errorf("got = %v, want %v", der, tt.der) + } + } +} + +func TestDecodeIdentifier(t *testing.T) { + t.Run("identifier is empty", func(t *testing.T) { + _, _, err := decodeIdentifier([]byte{}) + if err == nil { + t.Errorf("decodeIdentifier() error = nil, but expect error") + } + }) +} + +func TestDecodeLength(t *testing.T) { + t.Run("length is empty", func(t *testing.T) { + _, _, err := decodeLength([]byte{}) + if err == nil { + t.Errorf("decodeLength() error = nil, but expect error") + } + }) +} diff --git a/internal/encoding/asn1/ber/common.go b/internal/encoding/asn1/ber/common.go new file mode 100644 index 0000000..4e6fe19 --- /dev/null +++ b/internal/encoding/asn1/ber/common.go @@ -0,0 +1,64 @@ +// 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 ber + +import "io" + +// writer is the interface that wraps the basic Write and WriteByte methods. +type writer interface { + io.Writer + io.ByteWriter +} + +// encodeLength encodes length octets in DER. +// Reference: +// - ISO/IEC 8825-1: 10.1 +// - https://learn.microsoft.com/windows/win32/seccertenroll/about-encoded-length-and-value-bytes +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 +// in DER. +// Reference: +// - ISO/IEC 8825-1: 10.1 +// - https://learn.microsoft.com/windows/win32/seccertenroll/about-encoded-length-and-value-bytes +func encodedLengthSize(length int) int { + if length < 0x80 { + return 1 + } + + lengthSize := 1 + for length > 0 { + length >>= 8 + lengthSize++ + } + return lengthSize +} diff --git a/internal/encoding/asn1/ber/common_test.go b/internal/encoding/asn1/ber/common_test.go new file mode 100644 index 0000000..7331a10 --- /dev/null +++ b/internal/encoding/asn1/ber/common_test.go @@ -0,0 +1,126 @@ +// 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 ber + +import ( + "bytes" + "errors" + "testing" +) + +func TestEncodeLength(t *testing.T) { + tests := []struct { + name string + length int + want []byte + wantErr bool + }{ + { + name: "Length less than 128", + length: 127, + want: []byte{127}, + wantErr: false, + }, + { + name: "Length equal to 128", + length: 128, + want: []byte{0x81, 128}, + wantErr: false, + }, + { + name: "Length greater than 128", + length: 300, + want: []byte{0x82, 0x01, 0x2C}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := &bytes.Buffer{} + err := encodeLength(buf, tt.length) + if (err != nil) != tt.wantErr { + t.Errorf("encodeLength() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got := buf.Bytes(); !bytes.Equal(got, tt.want) { + t.Errorf("encodeLength() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEncodedLengthSize(t *testing.T) { + tests := []struct { + name string + length int + want int + }{ + { + name: "Length less than 128", + length: 127, + want: 1, + }, + { + name: "Length equal to 128", + length: 128, + want: 2, + }, + { + name: "Length greater than 128", + length: 300, + want: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := encodedLengthSize(tt.length); got != tt.want { + t.Errorf("encodedLengthSize() = %v, want %v", got, tt.want) + } + }) + } +} + +type secondErrorWriter struct { + count int +} + +func (ew *secondErrorWriter) WriteByte(p byte) (err error) { + ew.count += 1 + if ew.count == 2 { + return errors.New("write error") + } + return nil +} + +func TestEncodeLengthFailed(t *testing.T) { + t.Run("byte write error 1", func(t *testing.T) { + buf := &errorWriter{} + err := encodeLength(buf, 128) + if err == nil { + t.Error("encodeLength() error = nil, want Error") + return + } + }) + + t.Run("byte write error 2", func(t *testing.T) { + buf := &secondErrorWriter{} + err := encodeLength(buf, 128) + if err == nil { + t.Error("encodeLength() error = nil, want Error") + return + } + }) +} diff --git a/internal/encoding/asn1/ber/constructed.go b/internal/encoding/asn1/ber/constructed.go new file mode 100644 index 0000000..9188339 --- /dev/null +++ b/internal/encoding/asn1/ber/constructed.go @@ -0,0 +1,43 @@ +// 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 ber + +// constructed represents a value in constructed. +type constructed struct { + identifier []byte + length int // length of this constructed value's memebers in bytes when encoded in DER + members []value + rawContent []byte // the raw content of BER +} + +// EncodeMetadata encodes the identifier and length octets of constructed +// to the value writer in DER. +func (v *constructed) EncodeMetadata(w writer) error { + _, err := w.Write(v.identifier) + if err != nil { + return err + } + return encodeLength(w, v.length) +} + +// EncodedLen returns the length in bytes of the constructed when encoded +// in DER. +func (v *constructed) EncodedLen() int { + return len(v.identifier) + encodedLengthSize(v.length) + v.length +} + +// Content returns the content of the value. +func (v *constructed) Content() []byte { + return nil +} diff --git a/internal/encoding/asn1/ber/constructed_test.go b/internal/encoding/asn1/ber/constructed_test.go new file mode 100644 index 0000000..c98fa5e --- /dev/null +++ b/internal/encoding/asn1/ber/constructed_test.go @@ -0,0 +1,56 @@ +// 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 ber + +import ( + "errors" + "testing" +) + +type errorWriter struct{} + +func (ew *errorWriter) Write(p []byte) (n int, err error) { + return 0, errors.New("write error") +} + +func (ew *errorWriter) WriteByte(c byte) error { + return errors.New("write error") +} + +func TestConstructedEncodeMetadata(t *testing.T) { + tests := []struct { + name string + v constructed + wantError bool + }{ + { + name: "Error case", + v: constructed{ + identifier: []byte{0x30}, + length: 5, + }, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &errorWriter{} + err := tt.v.EncodeMetadata(w) + if (err != nil) != tt.wantError { + t.Errorf("EncodeMetadata() error = %v, wantError %v", err, tt.wantError) + } + }) + } +} diff --git a/internal/encoding/asn1/ber/primitive.go b/internal/encoding/asn1/ber/primitive.go new file mode 100644 index 0000000..25eab41 --- /dev/null +++ b/internal/encoding/asn1/ber/primitive.go @@ -0,0 +1,40 @@ +// 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 ber + +// primitive represents a value in primitive encoding. +type primitive struct { + identifier []byte + content []byte +} + +// EncodeMetadata encodes the identifier and length octets of primitive to +// the value writer in DER. +func (v *primitive) EncodeMetadata(w writer) error { + _, err := w.Write(v.identifier) + if err != nil { + return err + } + return encodeLength(w, len(v.content)) +} + +// EncodedLen returns the length in bytes of the primitive when encoded in DER. +func (v *primitive) EncodedLen() int { + return len(v.identifier) + encodedLengthSize(len(v.content)) + len(v.content) +} + +// Content returns the content of the value. +func (v *primitive) Content() []byte { + return v.content +} diff --git a/internal/encoding/asn1/ber/primitive_test.go b/internal/encoding/asn1/ber/primitive_test.go new file mode 100644 index 0000000..5cd5526 --- /dev/null +++ b/internal/encoding/asn1/ber/primitive_test.go @@ -0,0 +1,44 @@ +// 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 ber + +import ( + "testing" +) + +func TestPrimitiveEncodeMetadata(t *testing.T) { + tests := []struct { + name string + v primitive + wantError bool + }{ + { + name: "Error case", + v: primitive{ + identifier: []byte{0x30}, + }, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &errorWriter{} + err := tt.v.EncodeMetadata(w) + if (err != nil) != tt.wantError { + t.Errorf("EncodeMetadata() error = %v, wantError %v", err, tt.wantError) + } + }) + } +}