Skip to content

Commit 84188e4

Browse files
Read 64bit ASN1 ObjectIdentifier
See golang/go#58821
0 parents  commit 84188e4

File tree

7 files changed

+235
-0
lines changed

7 files changed

+235
-0
lines changed

LICENSE

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2023, Alexander Yastrebov
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
1. Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
2. Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
3. Neither the name of the copyright holder nor the names of its
17+
contributors may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Read 64bit ASN1 ObjectIdentifier
2+
3+
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.
4+
5+
See
6+
* [example_test.go](example_test.go)
7+
* https://github.com/golang/go/issues/58821
8+
9+
```sh
10+
# supported
11+
~$ echo '2^63-1' | bc
12+
9223372036854775807
13+
14+
~$ openssl asn1parse -genstr 'OID:2.5.2.9223372036854775807' -out - | hexdump -C
15+
00000000 06 0b 55 02 ff ff ff ff ff ff ff ff 7f 20 20 20 |..U.......... |
16+
00000010 20 30 3a 64 3d 30 20 20 68 6c 3d 32 20 6c 3d 20 | 0:d=0 hl=2 l= |
17+
00000020 20 31 31 20 70 72 69 6d 3a 20 4f 42 4a 45 43 54 | 11 prim: OBJECT|
18+
00000030 20 20 20 20 20 20 20 20 20 20 20 20 3a 32 2e 35 | :2.5|
19+
00000040 2e 32 2e 39 32 32 33 33 37 32 30 33 36 38 35 34 |.2.9223372036854|
20+
00000050 37 37 35 38 30 37 0a |775807.|
21+
00000057
22+
23+
# unsupported
24+
~$ echo '2^63' | bc
25+
9223372036854775808
26+
~$ openssl asn1parse -genstr 'OID:2.5.2.9223372036854775808' -out - | hexdump -C
27+
00000000 06 0c 55 02 81 80 80 80 80 80 80 80 80 00 20 20 |..U........... |
28+
00000010 20 20 30 3a 64 3d 30 20 20 68 6c 3d 32 20 6c 3d | 0:d=0 hl=2 l=|
29+
00000020 20 20 31 32 20 70 72 69 6d 3a 20 4f 42 4a 45 43 | 12 prim: OBJEC|
30+
00000030 54 20 20 20 20 20 20 20 20 20 20 20 20 3a 32 2e |T :2.|
31+
00000040 35 2e 32 2e 39 32 32 33 33 37 32 30 33 36 38 35 |5.2.922337203685|
32+
00000050 34 37 37 35 38 30 38 0a |4775808.|
33+
00000058
34+
```

example_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package asn1oid64_test
2+
3+
import (
4+
encoding_asn1 "encoding/asn1"
5+
"fmt"
6+
7+
"golang.org/x/crypto/cryptobyte"
8+
9+
"github.com/AlexanderYastrebov/asn1oid64"
10+
)
11+
12+
func ExampleReadASN1ObjectIdentifier() {
13+
encoded, err := encoding_asn1.Marshal(encoding_asn1.ObjectIdentifier([]int{2, 5, 2, 9223372036854775807}))
14+
if err != nil {
15+
panic(err)
16+
}
17+
18+
in := cryptobyte.String(encoded)
19+
20+
var out encoding_asn1.ObjectIdentifier
21+
ok := asn1oid64.ReadASN1ObjectIdentifier(&in, &out)
22+
23+
fmt.Printf("%s %t", out, ok)
24+
25+
// Output:
26+
// 2.5.2.9223372036854775807 true
27+
}

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/AlexanderYastrebov/asn1oid64
2+
3+
go 1.19
4+
5+
require golang.org/x/crypto v0.7.0

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
2+
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=

oid64.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package asn1oid64
2+
3+
import (
4+
encoding_asn1 "encoding/asn1"
5+
"math"
6+
7+
"golang.org/x/crypto/cryptobyte"
8+
"golang.org/x/crypto/cryptobyte/asn1"
9+
)
10+
11+
// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and
12+
// advances. It reports whether the read was successful.
13+
func ReadASN1ObjectIdentifier(s *cryptobyte.String, out *encoding_asn1.ObjectIdentifier) bool {
14+
var bytes cryptobyte.String
15+
if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 {
16+
return false
17+
}
18+
19+
// In the worst case, we get two elements from the first byte (which is
20+
// encoded differently) and then every varint is a single byte long.
21+
components := make([]int, len(bytes)+1)
22+
23+
// The first varint is 40*value1 + value2:
24+
// According to this packing, value1 can take the values 0, 1 and 2 only.
25+
// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
26+
// then there are no restrictions on value2.
27+
var v int
28+
if !readBase128Int(&bytes, &v) {
29+
return false
30+
}
31+
if v < 80 {
32+
components[0] = v / 40
33+
components[1] = v % 40
34+
} else {
35+
components[0] = 2
36+
components[1] = v - 80
37+
}
38+
39+
i := 2
40+
for ; len(bytes) > 0; i++ {
41+
if !readBase128Int(&bytes, &v) {
42+
return false
43+
}
44+
components[i] = v
45+
}
46+
*out = components[:i]
47+
return true
48+
}
49+
50+
func readBase128Int(s *cryptobyte.String, out *int) bool {
51+
ret := 0
52+
for i := 0; len(*s) > 0; i++ {
53+
if ret > math.MaxInt>>7 {
54+
return false
55+
}
56+
// if i == 5 {
57+
// return false
58+
// }
59+
// // Avoid overflowing int on a 32-bit platform.
60+
// // We don't want different behavior based on the architecture.
61+
// if ret >= 1<<(31-7) {
62+
// return false
63+
// }
64+
ret <<= 7
65+
b := read(s, 1)[0]
66+
ret |= int(b & 0x7f)
67+
if b&0x80 == 0 {
68+
*out = ret
69+
return true
70+
}
71+
}
72+
return false // truncated
73+
}
74+
75+
func read(s *cryptobyte.String, n int) []byte {
76+
if len(*s) < n || n < 0 {
77+
return nil
78+
}
79+
v := (*s)[:n]
80+
*s = (*s)[n:]
81+
return v
82+
}

oid64_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package asn1oid64_test
2+
3+
import (
4+
"bytes"
5+
encoding_asn1 "encoding/asn1"
6+
"testing"
7+
8+
"golang.org/x/crypto/cryptobyte"
9+
10+
"github.com/AlexanderYastrebov/asn1oid64"
11+
)
12+
13+
func TestASN1ObjectIdentifier(t *testing.T) {
14+
testData := []struct {
15+
in []byte
16+
ok bool
17+
out []int
18+
}{
19+
{[]byte{}, false, []int{}},
20+
{[]byte{6, 0}, false, []int{}},
21+
{[]byte{5, 1, 85}, false, []int{2, 5}},
22+
{[]byte{6, 1, 85}, true, []int{2, 5}},
23+
{[]byte{6, 2, 85, 0x02}, true, []int{2, 5, 2}},
24+
{[]byte{6, 4, 85, 0x02, 0xc0, 0x00}, true, []int{2, 5, 2, 0x2000}},
25+
{[]byte{6, 3, 0x81, 0x34, 0x03}, true, []int{2, 100, 3}},
26+
{[]byte{6, 7, 85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, false, []int{}},
27+
{[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
28+
{[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
29+
{[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, true, []int{2, 5, 2, 2147483648}}, // 2**31
30+
{[]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
31+
{[]byte{6, 11, 0x55, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 9223372036854775807}}, // 2**63-1
32+
{[]byte{0, 12, 0x55, 0x02, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**63
33+
}
34+
35+
for i, test := range testData {
36+
in := cryptobyte.String(test.in)
37+
var out encoding_asn1.ObjectIdentifier
38+
ok := asn1oid64.ReadASN1ObjectIdentifier(&in, &out)
39+
if ok != test.ok || ok && !out.Equal(test.out) {
40+
t.Errorf("#%d: in.ReadASN1ObjectIdentifier() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
41+
continue
42+
}
43+
44+
var b cryptobyte.Builder
45+
b.AddASN1ObjectIdentifier(out)
46+
result, err := b.Bytes()
47+
if builderOk := err == nil; test.ok != builderOk {
48+
t.Errorf("#%d: error from Builder.Bytes: %s", i, err)
49+
continue
50+
}
51+
if test.ok && !bytes.Equal(result, test.in) {
52+
t.Errorf("#%d: reserialisation didn't match, got %x, want %x", i, result, test.in)
53+
continue
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)