Skip to content

Commit

Permalink
add new Uint*LengthPrefixed api
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusz834 committed Jul 26, 2022
1 parent 630584e commit 2e92e6f
Show file tree
Hide file tree
Showing 3 changed files with 383 additions and 37 deletions.
50 changes: 32 additions & 18 deletions cryptobyte/asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,30 @@ func (b *Builder) AddASN1Enum(v int64) {
}

func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) {
b.AddASN1(tag, func(c *Builder) {
b.ASN1(tag, func() {
length := 1
for i := v; i >= 0x80 || i < -0x80; i >>= 8 {
length++
}

for ; length > 0; length-- {
i := v >> uint((length-1)*8) & 0xff
c.AddUint8(uint8(i))
b.AddUint8(uint8(i))
}
})
}

// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER.
func (b *Builder) AddASN1Uint64(v uint64) {
b.AddASN1(asn1.INTEGER, func(c *Builder) {
b.ASN1(asn1.INTEGER, func() {
length := 1
for i := v; i >= 0x80; i >>= 8 {
length++
}

for ; length > 0; length-- {
i := v >> uint((length-1)*8) & 0xff
c.AddUint8(uint8(i))
b.AddUint8(uint8(i))
}
})
}
Expand All @@ -69,7 +69,7 @@ func (b *Builder) AddASN1BigInt(n *big.Int) {
return
}

b.AddASN1(asn1.INTEGER, func(c *Builder) {
b.ASN1(asn1.INTEGER, func() {
if n.Sign() < 0 {
// A negative number has to be converted to two's-complement form. So we
// invert and subtract 1. If the most-significant-bit isn't set then
Expand All @@ -82,25 +82,25 @@ func (b *Builder) AddASN1BigInt(n *big.Int) {
bytes[i] ^= 0xff
}
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
c.add(0xff)
b.add(0xff)
}
c.add(bytes...)
b.add(bytes...)
} else if n.Sign() == 0 {
c.add(0)
b.add(0)
} else {
bytes := n.Bytes()
if bytes[0]&0x80 != 0 {
c.add(0)
b.add(0)
}
c.add(bytes...)
b.add(bytes...)
}
})
}

// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING.
func (b *Builder) AddASN1OctetString(bytes []byte) {
b.AddASN1(asn1.OCTET_STRING, func(c *Builder) {
c.AddBytes(bytes)
b.ASN1(asn1.OCTET_STRING, func() {
b.AddBytes(bytes)
})
}

Expand All @@ -113,27 +113,27 @@ func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
return
}
b.AddASN1(asn1.GeneralizedTime, func(c *Builder) {
c.AddBytes([]byte(t.Format(generalizedTimeFormatStr)))
b.AddBytes([]byte(t.Format(generalizedTimeFormatStr)))
})
}

// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime.
func (b *Builder) AddASN1UTCTime(t time.Time) {
b.AddASN1(asn1.UTCTime, func(c *Builder) {
b.ASN1(asn1.UTCTime, func() {
// As utilized by the X.509 profile, UTCTime can only
// represent the years 1950 through 2049.
if t.Year() < 1950 || t.Year() >= 2050 {
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t)
return
}
c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
b.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
})
}

// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
// support BIT STRINGs that are not a whole number of bytes.
func (b *Builder) AddASN1BitString(data []byte) {
b.AddASN1(asn1.BIT_STRING, func(b *Builder) {
b.ASN1(asn1.BIT_STRING, func() {
b.AddUint8(0)
b.AddBytes(data)
})
Expand Down Expand Up @@ -179,7 +179,7 @@ func isValidOID(oid encoding_asn1.ObjectIdentifier) bool {
}

func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) {
b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) {
b.ASN1(asn1.OBJECT_IDENTIFIER, func() {
if !isValidOID(oid) {
b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid)
return
Expand All @@ -193,7 +193,7 @@ func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) {
}

func (b *Builder) AddASN1Boolean(v bool) {
b.AddASN1(asn1.BOOLEAN, func(b *Builder) {
b.ASN1(asn1.BOOLEAN, func() {
if v {
b.AddUint8(0xff)
} else {
Expand Down Expand Up @@ -241,6 +241,20 @@ func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
b.addLengthPrefixed(1, true, f)
}

func (b *Builder) ASN1(tag asn1.Tag, f func()) {
if b.err != nil {
return
}
// Identifiers with the low five bits set indicate high-tag-number format
// (two or more octets), which we don't support.
if tag&0x1f == 0x1f {
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag)
return
}
b.AddUint8(uint8(tag))
b.addLengthPrefixed2(1, true, f)
}

// String

// ReadASN1Boolean decodes an ASN.1 BOOLEAN and converts it to a boolean
Expand Down
170 changes: 151 additions & 19 deletions cryptobyte/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,18 @@ import (
// the value to a given Builder. See the documentation for BuilderContinuation
// for details.
type Builder struct {
err error
result []byte
fixedSize bool
child *Builder
err error
result []byte
child *Builder
lenghtPrefixedData
fixedSize bool
}

type lenghtPrefixedData struct {
offset int
pendingLenLen int
pendingIsASN1 bool
inContinuation *bool
inContinuation bool
}

// NewBuilder creates a Builder that appends its output to the given buffer.
Expand Down Expand Up @@ -151,12 +155,32 @@ func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) {
b.addLengthPrefixed(4, false, f)
}

// Uint8LengthPrefixed adds a 8-bit length-prefixed byte sequence.
func (b *Builder) Uint8LengthPrefixed(f func()) {
b.addLengthPrefixed2(1, false, f)
}

// Uint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence.
func (b *Builder) Uint16LengthPrefixed(f func()) {
b.addLengthPrefixed2(2, false, f)
}

// Uint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence.
func (b *Builder) Uint24LengthPrefixed(f func()) {
b.addLengthPrefixed2(3, false, f)
}

// Uint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence.
func (b *Builder) Uint32LengthPrefixed(f func()) {
b.addLengthPrefixed2(4, false, f)
}

func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) {
if !*b.inContinuation {
*b.inContinuation = true
if !b.inContinuation {
b.inContinuation = true

defer func() {
*b.inContinuation = false
b.inContinuation = false

r := recover()
if r == nil {
Expand All @@ -174,26 +198,47 @@ func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) {
f(arg)
}

func (b *Builder) callContinuation2(f func()) {
if !b.inContinuation {
b.inContinuation = true

defer func() {
b.inContinuation = false

r := recover()
if r == nil {
return
}

if buildError, ok := r.(BuildError); ok {
b.err = buildError.Err
} else {
panic(r)
}
}()
}

f()
}

func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) {
// Subsequent writes can be ignored if the builder has encountered an error.
if b.err != nil {
return
}

offset := len(b.result)
b.add(make([]byte, lenLen)...)

if b.inContinuation == nil {
b.inContinuation = new(bool)
}
b.alloc(lenLen)

b.child = &Builder{
result: b.result,
fixedSize: b.fixedSize,
offset: offset,
pendingLenLen: lenLen,
pendingIsASN1: isASN1,
inContinuation: b.inContinuation,
result: b.result,
fixedSize: b.fixedSize,
lenghtPrefixedData: lenghtPrefixedData{
offset: offset,
pendingLenLen: lenLen,
pendingIsASN1: isASN1,
inContinuation: b.inContinuation,
},
}

b.callContinuation(f, b.child)
Expand All @@ -203,6 +248,87 @@ func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuati
}
}

func (b *Builder) addLengthPrefixed2(lenLen int, isASN1 bool, f func()) {
// Subsequent writes can be ignored if the builder has encountered an error.
if b.err != nil {
return
}

offset := len(b.result)
b.alloc(lenLen)

before := b.lenghtPrefixedData

b.offset = offset
b.pendingLenLen = lenLen
b.pendingIsASN1 = isASN1

b.callContinuation2(f)

b.lenghtPrefixedData = before

if b.err != nil {
return
}

length := len(b.result) - lenLen - offset
if length < 0 {
panic("cryptobyte: internal error") // result unexpectedly shrunk
}

if isASN1 {
// For ASN.1, we reserved a single byte for the length. If that turned out
// to be incorrect, we have to move the contents along in order to make
// space.
if lenLen != 1 {
panic("cryptobyte: internal error")
}

var extraBytes = 0
var lenByte uint8
if int64(length) > 0xfffffffe {
b.err = errors.New("pending ASN.1 child too long")
return
} else if length > 0xffffff {
extraBytes = 4
lenByte = 0x80 | 4
} else if length > 0xffff {
extraBytes = 3
lenByte = 0x80 | 3
} else if length > 0xff {
extraBytes = 2
lenByte = 0x80 | 2
} else if length > 0x7f {
extraBytes = 1
lenByte = 0x80 | 1
} else {
lenByte = uint8(length)
length = 0
}

// Insert the initial length byte, make space for successive length bytes,
// and adjust the offset.
b.result[offset] = lenByte
if extraBytes != 0 {
b.alloc(extraBytes)
childStart := offset + lenLen
copy(b.result[childStart+extraBytes:], b.result[childStart:])
}
offset++
lenLen = extraBytes
}

l := length
for i := lenLen - 1; i >= 0; i-- {
b.result[offset+i] = uint8(l)
l >>= 8
}
if l != 0 {
b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, lenLen)
return
}
}

func (b *Builder) flushChild() {
if b.child == nil {
return
Expand Down Expand Up @@ -298,6 +424,12 @@ func (b *Builder) add(bytes ...byte) {
b.result = append(b.result, bytes...)
}

func (b *Builder) alloc(n int) {
for i := 0; i < n; i++ {
b.add(0)
}
}

// Unwrite rolls back n bytes written directly to the Builder. An attempt by a
// child builder passed to a continuation to unwrite bytes from its parent will
// panic.
Expand Down

0 comments on commit 2e92e6f

Please sign in to comment.