diff --git a/internal/encoding/asn1/asn1.go b/internal/encoding/asn1/asn1.go new file mode 100644 index 00000000..28d79049 --- /dev/null +++ b/internal/encoding/asn1/asn1.go @@ -0,0 +1,247 @@ +// 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 +package asn1 + +import ( + "bytes" + "encoding/asn1" +) + +// Common errors +var ( + 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"} +) + +// value represents an ASN.1 value. +type value interface { + // 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. +func ConvertToDER(ber []byte) ([]byte, error) { + 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. +// +// r is the input byte 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) + if err != nil { + return nil, err + } + if len(r) != 0 { + return nil, ErrTrailingData + } + + // primitive value + if isPrimitive(identifier) { + return []value{&primitiveValue{ + identifier: identifier, + content: content, + }}, nil + } + + // constructed value + rootConstructed := &constructedValue{ + identifier: identifier, + rawContent: content, + } + flatValues := []value{rootConstructed} + + // start depth-first decoding with stack + valueStack := []*constructedValue{rootConstructed} + for len(valueStack) > 0 { + stackLen := len(valueStack) + // top + node := valueStack[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 + valueStack = valueStack[:stackLen-1] + continue + } + + // decode the next member of the constructed value + identifier, content, node.rawContent, err = decodeMetadata(node.rawContent) + if err != nil { + return nil, err + } + if isPrimitive(identifier) { + // primitive value + primitiveNode := &primitiveValue{ + identifier: identifier, + content: content, + } + node.members = append(node.members, primitiveNode) + flatValues = append(flatValues, primitiveNode) + } else { + // constructed value + constructedNode := &constructedValue{ + identifier: identifier, + rawContent: content, + } + node.members = append(node.members, constructedNode) + + // add a new constructed node to the stack + valueStack = append(valueStack, constructedNode) + flatValues = append(flatValues, constructedNode) + } + } + return flatValues, 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 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, nil, nil, err + } + contentLen, r, err := decodeLength(r) + if err != nil { + return nil, nil, nil, err + } + + if contentLen > len(r) { + return nil, nil, nil, ErrEarlyEOF + } + return identifier, r[:contentLen], r[contentLen:], 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) { + if len(r) < 1 { + return nil, nil, ErrEarlyEOF + } + offset := 0 + b := r[offset] + offset++ + + // high-tag-number form + if b&0x1f == 0x1f { + for offset < len(r) && r[offset]&0x80 == 0x80 { + offset++ + } + if offset >= len(r) { + return nil, nil, ErrEarlyEOF + } + offset++ + } + return r[:offset], r[offset:], nil +} + +// decodeLength decodes length octets. +// Indefinite length is not supported +// +// 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) { + if len(r) < 1 { + return 0, nil, ErrEarlyEOF + } + offset := 0 + b := r[offset] + offset++ + + if b < 0x80 { + // short form + return int(b), r[offset:], nil + } else if b == 0x80 { + // Indefinite-length method is not supported. + return 0, nil, ErrUnsupportedIndefiniteLength + } + + // long form + n := int(b & 0x7f) + if n > 4 { + // 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++ { + length = (length << 8) | uint64(r[offset]) + offset++ + } + + // length must fit the memory space of the int32. + if (length >> 31) > 0 { + return 0, nil, ErrUnsupportedLength + } + return int(length), r[offset:], nil +} + +// isPrimitive returns true if the first identifier octet is marked +// as primitive. +func isPrimitive(identifier []byte) bool { + return identifier[0]&0x20 == 0 +} diff --git a/internal/encoding/asn1/asn1_test.go b/internal/encoding/asn1/asn1_test.go new file mode 100644 index 00000000..90ca3aea --- /dev/null +++ b/internal/encoding/asn1/asn1_test.go @@ -0,0 +1,81 @@ +// 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 ( + "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..eb93ba78 --- /dev/null +++ b/internal/encoding/asn1/common.go @@ -0,0 +1,52 @@ +// 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 ( + "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..409fd5a4 --- /dev/null +++ b/internal/encoding/asn1/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 asn1 + +import "bytes" + +// constructedValue represents a value in constructed encoding. +type constructedValue struct { + identifier []byte + length int + members []value + rawContent []byte // the raw content of BER +} + +// 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 + } + return encodeLength(w, v.length) +} + +// EncodedLen returns the length in bytes of the encoded data. +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 new file mode 100644 index 00000000..0c2cdec6 --- /dev/null +++ b/internal/encoding/asn1/primitive.go @@ -0,0 +1,41 @@ +// 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" + +// primitiveValue represents a value in primitive encoding. +type primitiveValue struct { + identifier []byte + content []byte +} + +// 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 + } + return encodeLength(w, len(v.content)) +} + +// EncodedLen returns the length in bytes of the encoded data. +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 +}