From 7f099cdc76a04fa93425f422ea844f88e4cb82f1 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Fri, 20 May 2011 10:20:08 -0700 Subject: [PATCH] asn1: add big support. Initially I wanted to minimise dependencies but it's become clear that big int support in ASN.1 is a common need and that it should be part of the core. R=bradfitz CC=golang-dev https://golang.org/cl/4550063 --- src/pkg/asn1/asn1.go | 27 ++++++++++++++++++++++++ src/pkg/asn1/asn1_test.go | 27 ++++++++++++++++++++++++ src/pkg/asn1/common.go | 2 ++ src/pkg/asn1/marshal.go | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) diff --git a/src/pkg/asn1/asn1.go b/src/pkg/asn1/asn1.go index e7a46196cf..2650ef2a26 100644 --- a/src/pkg/asn1/asn1.go +++ b/src/pkg/asn1/asn1.go @@ -20,6 +20,7 @@ package asn1 // everything by any means. import ( + "big" "fmt" "os" "reflect" @@ -88,6 +89,27 @@ func parseInt(bytes []byte) (int, os.Error) { return int(ret64), nil } +var bigOne = big.NewInt(1) + +// parseBigInt treats the given bytes as a big-endian, signed integer and returns +// the result. +func parseBigInt(bytes []byte) *big.Int { + ret := new(big.Int) + if len(bytes) > 0 && bytes[0]&0x80 == 0x80 { + // This is a negative number. + notBytes := make([]byte, len(bytes)) + for i := range notBytes { + notBytes[i] = ^bytes[i] + } + ret.SetBytes(notBytes) + ret.Add(ret, bigOne) + ret.Neg(ret) + return ret + } + ret.SetBytes(bytes) + return ret +} + // BIT STRING // BitString is the structure to use when you want an ASN.1 BIT STRING type. A @@ -425,6 +447,7 @@ var ( timeType = reflect.TypeOf(&time.Time{}) rawValueType = reflect.TypeOf(RawValue{}) rawContentsType = reflect.TypeOf(RawContent(nil)) + bigIntType = reflect.TypeOf(new(big.Int)) ) // invalidLength returns true iff offset + length > sliceLength, or if the @@ -639,6 +662,10 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam case flagType: v.SetBool(true) return + case bigIntType: + parsedInt := parseBigInt(innerBytes) + v.Set(reflect.ValueOf(parsedInt)) + return } switch val := v; val.Kind() { case reflect.Bool: diff --git a/src/pkg/asn1/asn1_test.go b/src/pkg/asn1/asn1_test.go index 78f5628052..463dbe0264 100644 --- a/src/pkg/asn1/asn1_test.go +++ b/src/pkg/asn1/asn1_test.go @@ -42,6 +42,33 @@ func TestParseInt64(t *testing.T) { } } +var bigIntTests = []struct { + in []byte + base10 string +}{ + {[]byte{0xff}, "-1"}, + {[]byte{0x00}, "0"}, + {[]byte{0x01}, "1"}, + {[]byte{0x00, 0xff}, "255"}, + {[]byte{0xff, 0x00}, "-256"}, + {[]byte{0x01, 0x00}, "256"}, +} + +func TestParseBigInt(t *testing.T) { + for i, test := range bigIntTests { + ret := parseBigInt(test.in) + if ret.String() != test.base10 { + t.Errorf("#%d: bad result from %x, got %s want %s", i, test.in, ret.String(), test.base10) + } + fw := newForkableWriter() + marshalBigInt(fw, ret) + result := fw.Bytes() + if !bytes.Equal(result, test.in) { + t.Errorf("#%d: got %x from marshaling %s, want %x", i, result, ret, test.in) + } + } +} + type bitStringTest struct { in []byte ok bool diff --git a/src/pkg/asn1/common.go b/src/pkg/asn1/common.go index 0e6abc46b8..9db887e251 100644 --- a/src/pkg/asn1/common.go +++ b/src/pkg/asn1/common.go @@ -132,6 +132,8 @@ func getUniversalType(t reflect.Type) (tagNumber int, isCompound, ok bool) { return tagUTCTime, false, true case enumeratedType: return tagEnum, false, true + case bigIntType: + return tagInteger, false, true } switch t.Kind() { case reflect.Bool: diff --git a/src/pkg/asn1/marshal.go b/src/pkg/asn1/marshal.go index fc7c337f1a..771ac28243 100644 --- a/src/pkg/asn1/marshal.go +++ b/src/pkg/asn1/marshal.go @@ -5,6 +5,7 @@ package asn1 import ( + "big" "bytes" "fmt" "io" @@ -125,6 +126,43 @@ func int64Length(i int64) (numBytes int) { return } +func marshalBigInt(out *forkableWriter, n *big.Int) (err os.Error) { + if n.Sign() < 0 { + // A negative number has to be converted to two's-complement + // form. So we'll subtract 1 and invert. If the + // most-significant-bit isn't set then we'll need to pad the + // beginning with 0xff in order to keep the number negative. + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bytes := nMinus1.Bytes() + for i := range bytes { + bytes[i] ^= 0xff + } + if len(bytes) == 0 || bytes[0]&0x80 == 0 { + err = out.WriteByte(0xff) + if err != nil { + return + } + } + _, err = out.Write(bytes) + } else if n.Sign() == 0 { + // Zero is written as a single 0 zero rather than no bytes. + err = out.WriteByte(0x00) + } else { + bytes := n.Bytes() + if len(bytes) > 0 && bytes[0]&0x80 != 0 { + // We'll have to pad this with 0x00 in order to stop it + // looking like a negative number. + err = out.WriteByte(0) + if err != nil { + return + } + } + _, err = out.Write(bytes) + } + return +} + func marshalLength(out *forkableWriter, i int) (err os.Error) { n := lengthLength(i) @@ -334,6 +372,8 @@ func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameter return marshalBitString(out, value.Interface().(BitString)) case objectIdentifierType: return marshalObjectIdentifier(out, value.Interface().(ObjectIdentifier)) + case bigIntType: + return marshalBigInt(out, value.Interface().(*big.Int)) } switch v := value; v.Kind() { @@ -420,6 +460,9 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) if v.Type() == rawValueType { rv := v.Interface().(RawValue) + if rv.Class == 0 && rv.Tag == 0 && len(rv.Bytes) == 0 && params.optional { + return + } err = marshalTagAndLength(out, tagAndLength{rv.Class, rv.Tag, len(rv.Bytes), rv.IsCompound}) if err != nil { return