Skip to content

Commit

Permalink
Read 64bit ASN1 ObjectIdentifier
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderYastrebov committed Mar 9, 2023
0 parents commit 0cdf04d
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 0 deletions.
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2023, Alexander Yastrebov
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Read 64bit ASN1 ObjectIdentifier

This is a fork of `golang.org/x/crypto/cryptobyte` [String.ReadASN1ObjectIdentifier](https://pkg.go.dev/golang.org/x/crypto/cryptobyte#String.ReadASN1ObjectIdentifier) that supports 64 bit identifiers.

See
* [example_test.go](example_test.go)
* https://github.com/golang/go/issues/58821

```sh
# supported
~$ echo '2^63-1' | bc
9223372036854775807

~$ openssl asn1parse -genstr 'OID:2.5.2.9223372036854775807' -out - | hexdump -C
00000000 06 0b 55 02 ff ff ff ff ff ff ff ff 7f 20 20 20 |..U.......... |
00000010 20 30 3a 64 3d 30 20 20 68 6c 3d 32 20 6c 3d 20 | 0:d=0 hl=2 l= |
00000020 20 31 31 20 70 72 69 6d 3a 20 4f 42 4a 45 43 54 | 11 prim: OBJECT|
00000030 20 20 20 20 20 20 20 20 20 20 20 20 3a 32 2e 35 | :2.5|
00000040 2e 32 2e 39 32 32 33 33 37 32 30 33 36 38 35 34 |.2.9223372036854|
00000050 37 37 35 38 30 37 0a |775807.|
00000057

# unsupported
~$ echo '2^63' | bc
9223372036854775808

~$ openssl asn1parse -genstr 'OID:2.5.2.9223372036854775808' -out - | hexdump -C
00000000 06 0c 55 02 81 80 80 80 80 80 80 80 80 00 20 20 |..U........... |
00000010 20 20 30 3a 64 3d 30 20 20 68 6c 3d 32 20 6c 3d | 0:d=0 hl=2 l=|
00000020 20 20 31 32 20 70 72 69 6d 3a 20 4f 42 4a 45 43 | 12 prim: OBJEC|
00000030 54 20 20 20 20 20 20 20 20 20 20 20 20 3a 32 2e |T :2.|
00000040 35 2e 32 2e 39 32 32 33 33 37 32 30 33 36 38 35 |5.2.922337203685|
00000050 34 37 37 35 38 30 38 0a |4775808.|
00000058
```
27 changes: 27 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package asn1oid64_test

import (
encoding_asn1 "encoding/asn1"
"fmt"

"golang.org/x/crypto/cryptobyte"

"github.com/AlexanderYastrebov/asn1oid64"
)

func ExampleReadASN1ObjectIdentifier() {
encoded, err := encoding_asn1.Marshal(encoding_asn1.ObjectIdentifier([]int{2, 5, 2, 9223372036854775807}))
if err != nil {
panic(err)
}

in := cryptobyte.String(encoded)

var out encoding_asn1.ObjectIdentifier
ok := asn1oid64.ReadASN1ObjectIdentifier(&in, &out)

fmt.Printf("%s %t", out, ok)

// Output:
// 2.5.2.9223372036854775807 true
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/AlexanderYastrebov/asn1oid64

go 1.19

require golang.org/x/crypto v0.7.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
82 changes: 82 additions & 0 deletions oid64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package asn1oid64

import (
encoding_asn1 "encoding/asn1"
"math"

"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)

// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and
// advances. It reports whether the read was successful.
func ReadASN1ObjectIdentifier(s *cryptobyte.String, out *encoding_asn1.ObjectIdentifier) bool {
var bytes cryptobyte.String
if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 {
return false
}

// In the worst case, we get two elements from the first byte (which is
// encoded differently) and then every varint is a single byte long.
components := make([]int, len(bytes)+1)

// The first varint is 40*value1 + value2:
// According to this packing, value1 can take the values 0, 1 and 2 only.
// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
// then there are no restrictions on value2.
var v int
if !readBase128Int(&bytes, &v) {
return false
}
if v < 80 {
components[0] = v / 40
components[1] = v % 40
} else {
components[0] = 2
components[1] = v - 80
}

i := 2
for ; len(bytes) > 0; i++ {
if !readBase128Int(&bytes, &v) {
return false
}
components[i] = v
}
*out = components[:i]
return true
}

func readBase128Int(s *cryptobyte.String, out *int) bool {
ret := 0
for i := 0; len(*s) > 0; i++ {
if ret > math.MaxInt>>7 {
return false
}
// if i == 5 {
// return false
// }
// // Avoid overflowing int on a 32-bit platform.
// // We don't want different behavior based on the architecture.
// if ret >= 1<<(31-7) {
// return false
// }
ret <<= 7
b := read(s, 1)[0]
ret |= int(b & 0x7f)
if b&0x80 == 0 {
*out = ret
return true
}
}
return false // truncated
}

func read(s *cryptobyte.String, n int) []byte {
if len(*s) < n || n < 0 {
return nil
}
v := (*s)[:n]
*s = (*s)[n:]
return v
}
56 changes: 56 additions & 0 deletions oid64_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package asn1oid64_test

import (
"bytes"
encoding_asn1 "encoding/asn1"
"testing"

"golang.org/x/crypto/cryptobyte"

"github.com/AlexanderYastrebov/asn1oid64"
)

func TestASN1ObjectIdentifier(t *testing.T) {
testData := []struct {
in []byte
ok bool
out []int
}{
{[]byte{}, false, []int{}},
{[]byte{6, 0}, false, []int{}},
{[]byte{5, 1, 85}, false, []int{2, 5}},
{[]byte{6, 1, 85}, true, []int{2, 5}},
{[]byte{6, 2, 85, 0x02}, true, []int{2, 5, 2}},
{[]byte{6, 4, 85, 0x02, 0xc0, 0x00}, true, []int{2, 5, 2, 0x2000}},
{[]byte{6, 3, 0x81, 0x34, 0x03}, true, []int{2, 100, 3}},
{[]byte{6, 7, 85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, false, []int{}},
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, true, []int{2, 5, 2, 2147483648}}, // 2**31
{[]byte{6, 11, 0x2a, 0x24, 0xcb, 0x89, 0x90, 0x82, 0x1e, 0x03, 0x01, 0x01, 0x01}, true, []int{1, 2, 36, 20151795998, 3, 1, 1, 1}}, // https://github.com/golang/go/issues/58821
{[]byte{6, 11, 0x55, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 9223372036854775807}}, // 2**63-1
{[]byte{0, 12, 0x55, 0x02, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**63
}

for i, test := range testData {
in := cryptobyte.String(test.in)
var out encoding_asn1.ObjectIdentifier
ok := asn1oid64.ReadASN1ObjectIdentifier(&in, &out)
if ok != test.ok || ok && !out.Equal(test.out) {
t.Errorf("#%d: in.ReadASN1ObjectIdentifier() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
continue
}

var b cryptobyte.Builder
b.AddASN1ObjectIdentifier(out)
result, err := b.Bytes()
if builderOk := err == nil; test.ok != builderOk {
t.Errorf("#%d: error from Builder.Bytes: %s", i, err)
continue
}
if test.ok && !bytes.Equal(result, test.in) {
t.Errorf("#%d: reserialisation didn't match, got %x, want %x", i, result, test.in)
continue
}
}
}

0 comments on commit 0cdf04d

Please sign in to comment.