Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

encoding/asn1: add new OID type #60840

Closed
wants to merge 16 commits into from
7 changes: 7 additions & 0 deletions api/next/60665
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pkg encoding/asn1, func FromObjectIdentifier(ObjectIdentifier) (OID, error) #60665
pkg encoding/asn1, func ParseOID([]uint8) (OID, error) #60665
pkg encoding/asn1, method (OID) Equal(OID) bool #60665
pkg encoding/asn1, method (OID) EqualObjectIdentifier(ObjectIdentifier) bool #60665
pkg encoding/asn1, method (OID) String() string #60665
pkg encoding/asn1, method (OID) ToObjectIdentifier() (ObjectIdentifier, bool) #60665
pkg encoding/asn1, type OID struct #60665
249 changes: 249 additions & 0 deletions src/encoding/asn1/asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ package asn1
// everything by any means.

import (
"bytes"
"errors"
"fmt"
"math"
"math/big"
"math/bits"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -292,6 +294,253 @@ func parseObjectIdentifier(bytes []byte) (s ObjectIdentifier, err error) {
return
}

var (
errInvalidDEROid = errors.New("invalid DER Object Identifier encoding")
errInvalidOID = errors.New("invalid oid")
)

// An OID represents an ASN.1 OBJECT IDENTIFIER.
type OID struct {
der []byte
}

// FromObjectIdentifier creates a new OID from ObjectIdentifier.
func FromObjectIdentifier(oid ObjectIdentifier) (OID, error) {
enc, err := makeObjectIdentifier(oid)
if err != nil {
return OID{}, errInvalidOID
}
der := make([]byte, enc.Len())
enc.Encode(der)
return OID{der}, nil
}

// ParseOID parsed DER-encoded Object Identifier.
// On success, der is referenced in the OID struct.
// der must not be modified after parsing.
func ParseOID(der []byte) (OID, error) {
if !isDEROIDValid(der) {
return OID{}, errInvalidDEROid
}
return OID{der}, nil
}

func isDEROIDValid(der []byte) bool {
if len(der) == 0 || der[len(der)-1]&0x80 != 0 {
return false
}

start := 0
for i, v := range der {
// ITU-T X.690, section 8.19.2:
// The subidentifier shall be encoded in the fewest possible octets,
// that is, the leading octet of the subidentifier shall not have the value 0x80.
if i == start && v == 0x80 {
return false
}
if v&0x80 == 0 {
start = i + 1
}
}

return true
}

// Equal returns true when oid and other represents the same Object Identifier.
func (oid OID) Equal(other OID) bool {
// There is only one possible DER encoding of
// each unique Object Identifier.
return bytes.Equal(oid.der, other.der)
}

// ToObjectIdentifier converts oid to an ObjectIdentifier
// Reports whether the conversion succeeded, it fails when the
// int in ObjectIdentifier is too small. to represent the oid.
func (oid OID) ToObjectIdentifier() (ObjectIdentifier, bool) {
out := make([]int, 0, len(oid.der)+1)

const (
valSize = bits.UintSize - 1 // amount of usable bits of val for OIDs.
bitsPerByte = 7
maxValSafeShift = (1 << (valSize - bitsPerByte)) - 1
)

val := 0

for _, v := range oid.der {
if val > maxValSafeShift {
return nil, false
}

val <<= bitsPerByte
val |= int(v & 0x7F)

if v&0x80 == 0 {
if len(out) == 0 {
if val < 80 {
out = append(out, val/40)
out = append(out, val%40)
} else {
out = append(out, 2)
out = append(out, val-80)
}
val = 0
continue
}
out = append(out, val)
val = 0
}
}

return out, true
}

// Equal returns true when oid and other represents the same Object Identifier
func (oid OID) EqualObjectIdentifier(other ObjectIdentifier) bool {
const (
valSize = bits.UintSize - 1 // amount of usable bits of val for OIDs.
bitsPerByte = 7
maxValSafeShift = (1 << (valSize - bitsPerByte)) - 1
)

var (
val = 0
first = true
)

for _, v := range oid.der {
if val > maxValSafeShift {
return false
}

val <<= bitsPerByte
val |= int(v & 0x7F)

if v&0x80 == 0 {
if first {
if len(other) < 2 {
return false
}

var val1, val2 int
if val < 80 {
val1 = val / 40
val2 = val % 40
} else {
val1 = 2
val2 = val - 80
}

if val1 != other[0] || val2 != other[1] {
return false
}

val = 0
first = false
other = other[2:]
continue
}

if len(other) == 0 {
return false
}

if val != other[0] {
return false
}

val = 0
other = other[1:]

}
}

return true
}

// Strings returns the string representation of the Object Identifier.
func (oid OID) String() string {
var b strings.Builder
b.Grow(32)

const (
valSize = 64 // size in bits of val.
bitsPerByte = 7
maxValSafeShift = (1 << (valSize - bitsPerByte)) - 1
)

var (
start = 0
val = uint64(0)
numBuf = make([]byte, 0, 21)
bigVal *big.Int
overflow bool
)

for i, v := range oid.der {
curVal := v & 0x7F
valEnd := v&0x80 == 0

if valEnd {
if start != 0 {
b.WriteByte('.')
}
}

if !overflow && val > maxValSafeShift {
if bigVal == nil {
bigVal = new(big.Int)
}
bigVal = bigVal.SetUint64(val)
overflow = true
}

if overflow {
bigVal = bigVal.Lsh(bigVal, bitsPerByte).Or(bigVal, big.NewInt(int64(curVal)))
if valEnd {
if start == 0 {
b.WriteString("2.")
bigVal = bigVal.Sub(bigVal, big.NewInt(80))
}

numBuf = bigVal.Append(numBuf, 10)
b.Write(numBuf)
numBuf = numBuf[:0]

val = 0
start = i + 1
overflow = false
}
continue
}

val <<= bitsPerByte
val |= uint64(curVal)

if valEnd {
if start == 0 {
var val1, val2 uint64
if val < 80 {
val1 = val / 40
val2 = val % 40
} else {
val1 = 2
val2 = val - 80
}
b.Write(strconv.AppendUint(numBuf, val1, 10))
b.WriteByte('.')
b.Write(strconv.AppendUint(numBuf, val2, 10))
} else {
b.Write(strconv.AppendUint(numBuf, val, 10))
}
val = 0
start = i + 1
}
}

return b.String()
}

// ENUMERATED

// An Enumerated is represented as a plain int.
Expand Down
96 changes: 96 additions & 0 deletions src/encoding/asn1/asn1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"math"
"math/big"
"math/bits"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -1175,3 +1176,98 @@ func BenchmarkObjectIdentifierString(b *testing.B) {
_ = oidPublicKeyRSA.String()
}
}

func on64Bit(ints []int64) []int {
if bits.UintSize == 64 {
out := make([]int, len(ints))
for i := range ints {
out[i] = int(ints[i])
}
return out
}
return nil
}

var testOIDs = []struct {
raw []byte
valid bool
str string
oid ObjectIdentifier
}{
{[]byte{}, false, "", nil},
{[]byte{0x80, 0x01}, false, "", nil},
{[]byte{0x01, 0x80, 0x01}, false, "", nil},

{[]byte{1, 2, 3}, true, "0.1.2.3", []int{0, 1, 2, 3}},
{[]byte{41, 2, 3}, true, "1.1.2.3", []int{1, 1, 2, 3}},
{[]byte{86, 2, 3}, true, "2.6.2.3", []int{2, 6, 2, 3}},

{[]byte{41, 255, 255, 255, 127}, true, "1.1.268435455", []int{1, 1, 268435455}},
{[]byte{41, 0x87, 255, 255, 255, 127}, true, "1.1.2147483647", []int{1, 1, 2147483647}},
{[]byte{41, 255, 255, 255, 255, 127}, true, "1.1.34359738367", on64Bit([]int64{1, 1, 34359738367})},
{[]byte{42, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.2.9223372036854775807", on64Bit([]int64{1, 2, 9223372036854775807})},
{[]byte{43, 0x81, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.3.18446744073709551615", nil},
{[]byte{44, 0x83, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.4.36893488147419103231", nil},
{[]byte{85, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.5.1180591620717411303423", nil},
{[]byte{85, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.5.19342813113834066795298815", nil},

{[]byte{255, 255, 255, 127}, true, "2.268435375", []int{2, 268435375}},
{[]byte{0x87, 255, 255, 255, 127}, true, "2.2147483567", []int{2, 2147483567}},
{[]byte{255, 127}, true, "2.16303", []int{2, 16303}},
{[]byte{255, 255, 255, 255, 127}, true, "2.34359738287", on64Bit([]int64{2, 34359738287})},
{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.9223372036854775727", on64Bit([]int64{2, 9223372036854775727})},
{[]byte{0x81, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.18446744073709551535", nil},
{[]byte{0x83, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.36893488147419103151", nil},
{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.1180591620717411303343", nil},
{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.19342813113834066795298735", nil},
}

func TestOID(t *testing.T) {
for _, v := range testOIDs {
oid, err := ParseOID(v.raw)
if valid := err == nil; valid != v.valid {
if valid {
t.Errorf("%v: unexpected success while parsing: %v", v.raw, oid)
} else {
t.Errorf("%v: unexpected failure while parsing: %v", v.raw, err)
}
continue
}

if err != nil {
continue
}

if str := oid.String(); str != v.str {
t.Errorf("%v: unexpected string, got: %q, expected: %q", v.raw, str, v.str)
}

if v.oid != nil && !oid.EqualObjectIdentifier(v.oid) {
t.Errorf("%v: is not equal to %v", v.raw, v.oid)
}

o, ok := oid.ToObjectIdentifier()
if shouldOk := v.oid != nil; shouldOk != ok {
if ok {
t.Errorf("%v: unexpected success while converting to ObjectIdentifier", v.raw)
} else {
t.Errorf("%v: unexpected failure while converting to ObjectIdentifier", v.raw)
}
continue
}

if ok && !o.Equal(v.oid) {
t.Errorf("%v: after ToObjectIdentifer, is not equal to %v", v.raw, v.oid)
}

if v.oid != nil {
oid2, err := FromObjectIdentifier(v.oid)
if err != nil {
t.Errorf("%v: failed while creating OID from ObjectIdentifier: %v", v.raw, err)
}
if !oid2.Equal(oid) {
t.Errorf("%v: OID from ObjectIdentifier is not equal to oid", v.raw)
}
}
}
}