Skip to content

crypto/x509: add text and binary marshal methods to OID #66599

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

Closed
wants to merge 12 commits into from
5 changes: 5 additions & 0 deletions api/next/66249.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pkg crypto/x509, func ParseOID(string) (OID, error) #66249
pkg crypto/x509, method (*OID) UnmarshalBinary([]uint8) error #66249
pkg crypto/x509, method (*OID) UnmarshalText([]uint8) error #66249
pkg crypto/x509, method (OID) MarshalBinary() ([]uint8, error) #66249
pkg crypto/x509, method (OID) MarshalText() ([]uint8, error) #66249
3 changes: 3 additions & 0 deletions doc/next/6-stdlib/99-minor/crypto/x509/66249.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The new [`ParseOID`](/pkg/crypto/x509#ParseOID) function parses a dot-encoded ASN.1 Object Identifier string.
The [`OID`](/pkg/crypto/x509#OID) type now implements the [`BinaryMarshaler`](/pkg/encoding#BinaryMarshaler), [`BinaryUnmarshaler`](/pkg/encoding#BinaryUnmarshaler),
[`TextMarshaler`](/pkg/encoding#TextMarshaler), [`TextUnmarshaler`](/pkg/encoding#TextUnmarshaler) interfaces.
112 changes: 112 additions & 0 deletions src/crypto/x509/oid.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ type OID struct {
der []byte
}

// ParseOID parses a Object Identifier string, represented by ASCII numbers separated by dots.
func ParseOID(oid string) (OID, error) {
var o OID
return o, o.unmarshalOIDText(oid)
}

func newOIDFromDER(der []byte) (OID, bool) {
if len(der) == 0 || der[len(der)-1]&0x80 != 0 {
return OID{}, false
Expand Down Expand Up @@ -83,6 +89,112 @@ func appendBase128Int(dst []byte, n uint64) []byte {
return dst
}

func base128BigIntLength(n *big.Int) int {
if n.Cmp(big.NewInt(0)) == 0 {
return 1
}
return (n.BitLen() + 6) / 7
}

func appendBase128BigInt(dst []byte, n *big.Int) []byte {
if n.Cmp(big.NewInt(0)) == 0 {
return append(dst, 0)
}

for i := base128BigIntLength(n) - 1; i >= 0; i-- {
o := byte(big.NewInt(0).Rsh(n, uint(i)*7).Bits()[0])
o &= 0x7f
if i != 0 {
o |= 0x80
}
dst = append(dst, o)
}
return dst
}

// MarshalText implements [encoding.TextMarshaler]
func (o OID) MarshalText() ([]byte, error) {
return []byte(o.String()), nil
}

// UnmarshalText implements [encoding.TextUnmarshaler]
func (o *OID) UnmarshalText(text []byte) error {
return o.unmarshalOIDText(string(text))
}

func (o *OID) unmarshalOIDText(oid string) error {
// (*big.Int).SetString allows +/- signs, but we don't want
// to allow them in the string representation of Object Identifier, so
// reject such encodings.
for _, c := range oid {
isDigit := c >= '0' && c <= '9'
if !isDigit && c != '.' {
return errInvalidOID
}
}

var (
firstNum string
secondNum string
)

var nextComponentExists bool
firstNum, oid, nextComponentExists = strings.Cut(oid, ".")
if !nextComponentExists {
return errInvalidOID
}
secondNum, oid, nextComponentExists = strings.Cut(oid, ".")

var (
first = big.NewInt(0)
second = big.NewInt(0)
)

if _, ok := first.SetString(firstNum, 10); !ok {
return errInvalidOID
}
if _, ok := second.SetString(secondNum, 10); !ok {
return errInvalidOID
}

if first.Cmp(big.NewInt(2)) > 0 || (first.Cmp(big.NewInt(2)) < 0 && second.Cmp(big.NewInt(40)) >= 0) {
return errInvalidOID
}

firstComponent := first.Mul(first, big.NewInt(40))
firstComponent.Add(firstComponent, second)

der := appendBase128BigInt(make([]byte, 0, 32), firstComponent)

for nextComponentExists {
var strNum string
strNum, oid, nextComponentExists = strings.Cut(oid, ".")
b, ok := big.NewInt(0).SetString(strNum, 10)
if !ok {
return errInvalidOID
}
der = appendBase128BigInt(der, b)
}

o.der = der
return nil
}

// MarshalBinary implements [encoding.BinaryMarshaler]
func (o OID) MarshalBinary() ([]byte, error) {
return bytes.Clone(o.der), nil
}

// UnmarshalBinary implements [encoding.BinaryUnmarshaler]
func (o *OID) UnmarshalBinary(b []byte) error {
oid, ok := newOIDFromDER(bytes.Clone(b))
if !ok {
return errInvalidOID
}
*o = oid
return nil
}

// 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
Expand Down
Loading