Skip to content

Commit

Permalink
resolved conflicts
Browse files Browse the repository at this point in the history
Signed-off-by: Patrick Zheng <[email protected]>
  • Loading branch information
Two-Hearts committed Jan 31, 2024
2 parents a7b87c6 + 2862807 commit aa9bc82
Show file tree
Hide file tree
Showing 10 changed files with 1,044 additions and 3 deletions.
12 changes: 9 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# 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
#
# Binaries for programs and plugins
*.exe
*.exe~
Expand All @@ -32,3 +29,12 @@

# Go workspace file
go.work

# Code Editors
.vscode
.idea
*.sublime-project
*.sublime-workspace

# Custom
coverage.txt
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/notaryproject/tspclient-go

go 1.20
308 changes: 308 additions & 0 deletions internal/encoding/asn1/ber/ber.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit aa9bc82

Please sign in to comment.