Skip to content

Commit dd84bb6

Browse files
mateusz834gopherbot
authored andcommitted
crypto/x509: add new OID type and use it in Certificate
Fixes #60665 Change-Id: I814b7d4b26b964f74443584fb2048b3e27e3b675 GitHub-Last-Rev: 693c741 GitHub-Pull-Request: #62096 Reviewed-on: https://go-review.googlesource.com/c/go/+/520535 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Mateusz Poliwczak <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 68e52bc commit dd84bb6

File tree

6 files changed

+479
-14
lines changed

6 files changed

+479
-14
lines changed

api/next/60665.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pkg crypto/x509, type Certificate struct, Policies []OID #60665
2+
pkg crypto/x509, type OID struct #60665
3+
pkg crypto/x509, method (OID) Equal(OID) bool #60665
4+
pkg crypto/x509, method (OID) EqualASN1OID(asn1.ObjectIdentifier) bool #60665
5+
pkg crypto/x509, method (OID) String() string #60665
6+
pkg crypto/x509, func OIDFromInts([]uint64) (OID, error) #60665

src/crypto/x509/oid.go

+273
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package x509
6+
7+
import (
8+
"bytes"
9+
"encoding/asn1"
10+
"errors"
11+
"math"
12+
"math/big"
13+
"math/bits"
14+
"strconv"
15+
"strings"
16+
)
17+
18+
var (
19+
errInvalidOID = errors.New("invalid oid")
20+
)
21+
22+
// An OID represents an ASN.1 OBJECT IDENTIFIER.
23+
type OID struct {
24+
der []byte
25+
}
26+
27+
func newOIDFromDER(der []byte) (OID, bool) {
28+
if len(der) == 0 || der[len(der)-1]&0x80 != 0 {
29+
return OID{}, false
30+
}
31+
32+
start := 0
33+
for i, v := range der {
34+
// ITU-T X.690, section 8.19.2:
35+
// The subidentifier shall be encoded in the fewest possible octets,
36+
// that is, the leading octet of the subidentifier shall not have the value 0x80.
37+
if i == start && v == 0x80 {
38+
return OID{}, false
39+
}
40+
if v&0x80 == 0 {
41+
start = i + 1
42+
}
43+
}
44+
45+
return OID{der}, true
46+
}
47+
48+
// OIDFromInts creates a new OID using ints, each integer is a separate component.
49+
func OIDFromInts(oid []uint64) (OID, error) {
50+
if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) {
51+
return OID{}, errInvalidOID
52+
}
53+
54+
length := base128IntLength(oid[0]*40 + oid[1])
55+
for _, v := range oid[2:] {
56+
length += base128IntLength(v)
57+
}
58+
59+
der := make([]byte, 0, length)
60+
der = appendBase128Int(der, oid[0]*40+oid[1])
61+
for _, v := range oid[2:] {
62+
der = appendBase128Int(der, v)
63+
}
64+
return OID{der}, nil
65+
}
66+
67+
func base128IntLength(n uint64) int {
68+
if n == 0 {
69+
return 1
70+
}
71+
return (bits.Len64(n) + 6) / 7
72+
}
73+
74+
func appendBase128Int(dst []byte, n uint64) []byte {
75+
for i := base128IntLength(n) - 1; i >= 0; i-- {
76+
o := byte(n >> uint(i*7))
77+
o &= 0x7f
78+
if i != 0 {
79+
o |= 0x80
80+
}
81+
dst = append(dst, o)
82+
}
83+
return dst
84+
}
85+
86+
// Equal returns true when oid and other represents the same Object Identifier.
87+
func (oid OID) Equal(other OID) bool {
88+
// There is only one possible DER encoding of
89+
// each unique Object Identifier.
90+
return bytes.Equal(oid.der, other.der)
91+
}
92+
93+
func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, failed bool) {
94+
offset = initOffset
95+
var ret64 int64
96+
for shifted := 0; offset < len(bytes); shifted++ {
97+
// 5 * 7 bits per byte == 35 bits of data
98+
// Thus the representation is either non-minimal or too large for an int32
99+
if shifted == 5 {
100+
failed = true
101+
return
102+
}
103+
ret64 <<= 7
104+
b := bytes[offset]
105+
// integers should be minimally encoded, so the leading octet should
106+
// never be 0x80
107+
if shifted == 0 && b == 0x80 {
108+
failed = true
109+
return
110+
}
111+
ret64 |= int64(b & 0x7f)
112+
offset++
113+
if b&0x80 == 0 {
114+
ret = int(ret64)
115+
// Ensure that the returned value fits in an int on all platforms
116+
if ret64 > math.MaxInt32 {
117+
failed = true
118+
}
119+
return
120+
}
121+
}
122+
failed = true
123+
return
124+
}
125+
126+
// EqualASN1OID returns whether an OID equals an asn1.ObjectIdentifier. If
127+
// asn1.ObjectIdentifier cannot represent the OID specified by oid, because
128+
// a component of OID requires more than 31 bits, it returns false.
129+
func (oid OID) EqualASN1OID(other asn1.ObjectIdentifier) bool {
130+
if len(other) < 2 {
131+
return false
132+
}
133+
v, offset, failed := parseBase128Int(oid.der, 0)
134+
if failed {
135+
// This should never happen, since we've already parsed the OID,
136+
// but just in case.
137+
return false
138+
}
139+
if v < 80 {
140+
a, b := v/40, v%40
141+
if other[0] != a || other[1] != b {
142+
return false
143+
}
144+
} else {
145+
a, b := 2, v-80
146+
if other[0] != a || other[1] != b {
147+
return false
148+
}
149+
}
150+
151+
i := 2
152+
for ; offset < len(oid.der); i++ {
153+
v, offset, failed = parseBase128Int(oid.der, offset)
154+
if failed {
155+
// Again, shouldn't happen, since we've already parsed
156+
// the OID, but better safe than sorry.
157+
return false
158+
}
159+
if v != other[i] {
160+
return false
161+
}
162+
}
163+
164+
return i == len(other)
165+
}
166+
167+
// Strings returns the string representation of the Object Identifier.
168+
func (oid OID) String() string {
169+
var b strings.Builder
170+
b.Grow(32)
171+
const (
172+
valSize = 64 // size in bits of val.
173+
bitsPerByte = 7
174+
maxValSafeShift = (1 << (valSize - bitsPerByte)) - 1
175+
)
176+
var (
177+
start = 0
178+
val = uint64(0)
179+
numBuf = make([]byte, 0, 21)
180+
bigVal *big.Int
181+
overflow bool
182+
)
183+
for i, v := range oid.der {
184+
curVal := v & 0x7F
185+
valEnd := v&0x80 == 0
186+
if valEnd {
187+
if start != 0 {
188+
b.WriteByte('.')
189+
}
190+
}
191+
if !overflow && val > maxValSafeShift {
192+
if bigVal == nil {
193+
bigVal = new(big.Int)
194+
}
195+
bigVal = bigVal.SetUint64(val)
196+
overflow = true
197+
}
198+
if overflow {
199+
bigVal = bigVal.Lsh(bigVal, bitsPerByte).Or(bigVal, big.NewInt(int64(curVal)))
200+
if valEnd {
201+
if start == 0 {
202+
b.WriteString("2.")
203+
bigVal = bigVal.Sub(bigVal, big.NewInt(80))
204+
}
205+
numBuf = bigVal.Append(numBuf, 10)
206+
b.Write(numBuf)
207+
numBuf = numBuf[:0]
208+
val = 0
209+
start = i + 1
210+
overflow = false
211+
}
212+
continue
213+
}
214+
val <<= bitsPerByte
215+
val |= uint64(curVal)
216+
if valEnd {
217+
if start == 0 {
218+
if val < 80 {
219+
b.Write(strconv.AppendUint(numBuf, val/40, 10))
220+
b.WriteByte('.')
221+
b.Write(strconv.AppendUint(numBuf, val%40, 10))
222+
} else {
223+
b.WriteString("2.")
224+
b.Write(strconv.AppendUint(numBuf, val-80, 10))
225+
}
226+
} else {
227+
b.Write(strconv.AppendUint(numBuf, val, 10))
228+
}
229+
val = 0
230+
start = i + 1
231+
}
232+
}
233+
return b.String()
234+
}
235+
236+
func (oid OID) toASN1OID() (asn1.ObjectIdentifier, bool) {
237+
out := make([]int, 0, len(oid.der)+1)
238+
239+
const (
240+
valSize = 31 // amount of usable bits of val for OIDs.
241+
bitsPerByte = 7
242+
maxValSafeShift = (1 << (valSize - bitsPerByte)) - 1
243+
)
244+
245+
val := 0
246+
247+
for _, v := range oid.der {
248+
if val > maxValSafeShift {
249+
return nil, false
250+
}
251+
252+
val <<= bitsPerByte
253+
val |= int(v & 0x7F)
254+
255+
if v&0x80 == 0 {
256+
if len(out) == 0 {
257+
if val < 80 {
258+
out = append(out, val/40)
259+
out = append(out, val%40)
260+
} else {
261+
out = append(out, 2)
262+
out = append(out, val-80)
263+
}
264+
val = 0
265+
continue
266+
}
267+
out = append(out, val)
268+
val = 0
269+
}
270+
}
271+
272+
return out, true
273+
}

src/crypto/x509/oid_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package x509
6+
7+
import (
8+
"encoding/asn1"
9+
"math"
10+
"testing"
11+
)
12+
13+
func TestOID(t *testing.T) {
14+
var tests = []struct {
15+
raw []byte
16+
valid bool
17+
str string
18+
ints []uint64
19+
}{
20+
{[]byte{}, false, "", nil},
21+
{[]byte{0x80, 0x01}, false, "", nil},
22+
{[]byte{0x01, 0x80, 0x01}, false, "", nil},
23+
24+
{[]byte{1, 2, 3}, true, "0.1.2.3", []uint64{0, 1, 2, 3}},
25+
{[]byte{41, 2, 3}, true, "1.1.2.3", []uint64{1, 1, 2, 3}},
26+
{[]byte{86, 2, 3}, true, "2.6.2.3", []uint64{2, 6, 2, 3}},
27+
28+
{[]byte{41, 255, 255, 255, 127}, true, "1.1.268435455", []uint64{1, 1, 268435455}},
29+
{[]byte{41, 0x87, 255, 255, 255, 127}, true, "1.1.2147483647", []uint64{1, 1, 2147483647}},
30+
{[]byte{41, 255, 255, 255, 255, 127}, true, "1.1.34359738367", []uint64{1, 1, 34359738367}},
31+
{[]byte{42, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.2.9223372036854775807", []uint64{1, 2, 9223372036854775807}},
32+
{[]byte{43, 0x81, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.3.18446744073709551615", []uint64{1, 3, 18446744073709551615}},
33+
{[]byte{44, 0x83, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.4.36893488147419103231", nil},
34+
{[]byte{85, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.5.1180591620717411303423", nil},
35+
{[]byte{85, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.5.19342813113834066795298815", nil},
36+
37+
{[]byte{255, 255, 255, 127}, true, "2.268435375", []uint64{2, 268435375}},
38+
{[]byte{0x87, 255, 255, 255, 127}, true, "2.2147483567", []uint64{2, 2147483567}},
39+
{[]byte{255, 127}, true, "2.16303", []uint64{2, 16303}},
40+
{[]byte{255, 255, 255, 255, 127}, true, "2.34359738287", []uint64{2, 34359738287}},
41+
{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.9223372036854775727", []uint64{2, 9223372036854775727}},
42+
{[]byte{0x81, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.18446744073709551535", []uint64{2, 18446744073709551535}},
43+
{[]byte{0x83, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.36893488147419103151", nil},
44+
{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.1180591620717411303343", nil},
45+
{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.19342813113834066795298735", nil},
46+
}
47+
48+
for _, v := range tests {
49+
oid, ok := newOIDFromDER(v.raw)
50+
if ok != v.valid {
51+
if ok {
52+
t.Errorf("%v: unexpected success while parsing: %v", v.raw, oid)
53+
} else {
54+
t.Errorf("%v: unexpected failure while parsing", v.raw)
55+
}
56+
continue
57+
}
58+
59+
if !ok {
60+
continue
61+
}
62+
63+
if str := oid.String(); str != v.str {
64+
t.Errorf("%v: oid.String() = %v, want; %v", v.raw, str, v.str)
65+
}
66+
67+
var asn1OID asn1.ObjectIdentifier
68+
for _, v := range v.ints {
69+
if v > math.MaxInt32 {
70+
asn1OID = nil
71+
break
72+
}
73+
asn1OID = append(asn1OID, int(v))
74+
}
75+
76+
o, ok := oid.toASN1OID()
77+
if shouldOk := asn1OID != nil; shouldOk != ok {
78+
if ok {
79+
t.Errorf("%v: oid.toASN1OID() unexpected success", v.raw)
80+
} else {
81+
t.Errorf("%v: oid.toASN1OID() unexpected fauilure", v.raw)
82+
}
83+
continue
84+
}
85+
86+
if asn1OID != nil {
87+
if !o.Equal(asn1OID) {
88+
t.Errorf("%v: oid.toASN1OID(asn1OID).Equal(oid) = false, want: true", v.raw)
89+
}
90+
}
91+
92+
if v.ints != nil {
93+
oid2, err := OIDFromInts(v.ints)
94+
if err != nil {
95+
t.Errorf("%v: OIDFromInts() unexpected error: %v", v.raw, err)
96+
}
97+
if !oid2.Equal(oid) {
98+
t.Errorf("%v: %#v.Equal(%#v) = false, want: true", v.raw, oid2, oid)
99+
}
100+
}
101+
}
102+
}
103+
104+
func mustNewOIDFromInts(t *testing.T, ints []uint64) OID {
105+
oid, err := OIDFromInts(ints)
106+
if err != nil {
107+
t.Fatalf("OIDFromInts(%v) unexpected error: %v", ints, err)
108+
}
109+
return oid
110+
}

0 commit comments

Comments
 (0)