diff options
author | Adam Langley <agl@golang.org> | 2009-10-13 14:37:48 -0700 |
---|---|---|
committer | Adam Langley <agl@golang.org> | 2009-10-13 14:37:48 -0700 |
commit | bfb3b4f49a1c37cb1413d1358fb7e21c62591cba (patch) | |
tree | 94aee50ef4e421fc869de93d3d64c35003302964 /src/pkg/asn1 | |
parent | 262cf1c92d6235f013280463e283f1cfd132d4fb (diff) | |
download | golang-bfb3b4f49a1c37cb1413d1358fb7e21c62591cba.tar.gz |
Add ASN.1 parser.
R=rsc
APPROVED=rsc
DELTA=1459 (1459 added, 0 deleted, 0 changed)
OCL=35389
CL=35681
Diffstat (limited to 'src/pkg/asn1')
-rw-r--r-- | src/pkg/asn1/Makefile | 11 | ||||
-rw-r--r-- | src/pkg/asn1/asn1.go | 884 | ||||
-rw-r--r-- | src/pkg/asn1/asn1_test.go | 562 |
3 files changed, 1457 insertions, 0 deletions
diff --git a/src/pkg/asn1/Makefile b/src/pkg/asn1/Makefile new file mode 100644 index 000000000..8ad3fb78d --- /dev/null +++ b/src/pkg/asn1/Makefile @@ -0,0 +1,11 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=asn1 +GOFILES=\ + asn1.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/src/pkg/asn1/asn1.go b/src/pkg/asn1/asn1.go new file mode 100644 index 000000000..f3de79612 --- /dev/null +++ b/src/pkg/asn1/asn1.go @@ -0,0 +1,884 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This package implements parsing of DER-encoded ASN.1 data structures, +// as defined in ITU-T Rec. X.690. +// +// See also ``A Layman's Guide to a Subset of ASN.1, BER, and DER,'' +// http://luca.ntop.org/Teaching/Appunti/asn1.html. +package asn1 + +// ASN.1 is a syntax for specifying abstract objects and BER, DER, PER, XER etc +// are different encoding formats for those objects. Here, we'll be dealing +// with DER, the Distinguished Encoding Rules. DER is used in X.509 because +// it's fast to parse and, unlike BER, has a unique encoding for every object. +// When calculating hashes over objects, it's important that the resulting +// bytes be the same at both ends and DER removes this margin of error. +// +// ASN.1 is very complex and this package doesn't attempt to implement +// everything by any means. + +import ( + "fmt"; + "os"; + "reflect"; + "strconv"; + "strings"; + "time"; +) + +// A StructuralError suggests that the ASN.1 data is valid, but the Go type +// which is receiving it doesn't match. +type StructuralError struct { + Msg string; +} + +func (e StructuralError) String() string { + return "ASN.1 structure error: " + e.Msg; +} + +// A SyntaxError suggests that the ASN.1 data is invalid. +type SyntaxError struct { + Msg string; +} + +func (e SyntaxError) String() string { + return "ASN.1 syntax error: " + e.Msg; +} + +// We start by dealing with each of the primitive types in turn. + +// BOOLEAN + +func parseBool(bytes []byte) (ret bool, err os.Error) { + if len(bytes) != 1 { + err = SyntaxError{"invalid boolean"}; + return; + } + + return bytes[0] != 0, nil; +} + +// INTEGER + +// parseInt64 treats the given bytes as a big-endian, signed integer and +// returns the result. +func parseInt64(bytes []byte) (ret int64, err os.Error) { + if len(bytes) > 8 { + // We'll overflow an int64 in this case. + err = StructuralError{"integer too large"}; + return; + } + for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { + ret <<= 8; + ret |= int64(bytes[bytesRead]); + } + + // Shift up and down in order to sign extend the result. + ret <<= 64 - uint8(len(bytes))*8; + ret >>= 64 - uint8(len(bytes))*8; + return; +} + +// parseInt treats the given bytes as a big-endian, signed integer and returns +// the result. +func parseInt(bytes []byte) (int, os.Error) { + ret64, err := parseInt64(bytes); + if err != nil { + return 0, err; + } + if ret64 != int64(int(ret64)) { + return 0, StructuralError{"integer too large"}; + } + return int(ret64), nil; +} + +// BIT STRING + +// BitString is the structure to use when you want an ASN.1 BIT STRING type. A +// bit string is padded up to the nearest byte in memory and the number of +// valid bits is recorded. Padding bits will be zero. +type BitString struct { + Bytes []byte; // bits packed into bytes. + BitLength int; // length in bits. +} + +// At returns the bit at the given index. If the index is out of range it +// returns false. +func (b BitString) At(i int) int { + if i < 0 || i >= b.BitLength { + return 0; + } + x := i / 8; + y := 7 - uint(i % 8); + return int(b.Bytes[x] >> y) & 1; +} + +// parseBitString parses an ASN.1 bit string from the given byte array and returns it. +func parseBitString(bytes []byte) (ret BitString, err os.Error) { + if len(bytes) == 0 { + err = SyntaxError{"zero length BIT STRING"}; + return; + } + paddingBits := int(bytes[0]); + if paddingBits > 7 || + len(bytes) == 1 && paddingBits > 0 || + bytes[len(bytes)-1] & ((1 << bytes[0])-1) != 0 { + err = SyntaxError{"invalid padding bits in BIT STRING"}; + return; + } + ret.BitLength = (len(bytes)-1)*8 - paddingBits; + ret.Bytes = bytes[1:len(bytes)]; + return; +} + +// OBJECT IDENTIFIER + +// An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER. +type ObjectIdentifier []int + +// parseObjectIdentifier parses an OBJECT IDENTIFER from the given bytes and +// returns it. An object identifer is a sequence of variable length integers +// that are assigned in a hierarachy. +func parseObjectIdentifier(bytes []byte) (s []int, err os.Error) { + if len(bytes) == 0 { + err = SyntaxError{"zero length OBJECT IDENTIFIER"}; + return; + } + + // In the worst case, we get two elements from the first byte (which is + // encoded differently) and then every varint is a single byte long. + s = make([]int, len(bytes)+1); + + // The first byte is 40*value1 + value2: + s[0] = int(bytes[0]) / 40; + s[1] = int(bytes[0]) % 40; + i := 2; + for offset := 1; offset < len(bytes); i++ { + var v int; + v, offset, err = parseBase128Int(bytes, offset); + if err != nil { + return; + } + s[i] = v; + } + s = s[0:i]; + return; +} + +// parseBase128Int parses a base-128 encoded int from the given offset in the +// given byte array. It returns the value and the new offset. +func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err os.Error) { + offset = initOffset; + for shifted := 0; offset < len(bytes); shifted++ { + if shifted > 4 { + err = StructuralError{"base 128 integer too large"}; + return; + } + ret <<= 7; + b := bytes[offset]; + ret |= int(b&0x7f); + offset++; + if b&0x80 == 0 { + return; + } + } + err = SyntaxError{"truncated base 128 integer"}; + return; +} + +// UTCTime + +func isDigit(b byte) bool { + return '0' <= b && b <= '9'; +} + +// twoDigits returns the value of two, base 10 digits. +func twoDigits(bytes []byte, max int) (int, bool) { + for i := 0; i < 2; i++ { + if !isDigit(bytes[i]) { + return 0, false; + } + } + value := (int(bytes[0]) - '0')*10 + int(bytes[1] - '0'); + if value > max { + return 0, false; + } + return value, true; +} + +// parseUTCTime parses the UTCTime from the given byte array and returns the +// resulting time. +func parseUTCTime(bytes []byte) (ret time.Time, err os.Error) { + // A UTCTime can take the following formats: + // + // 1111111 + // 01234567890123456 + // + // YYMMDDhhmmZ + // YYMMDDhhmm+hhmm + // YYMMDDhhmm-hhmm + // YYMMDDhhmmssZ + // YYMMDDhhmmss+hhmm + // YYMMDDhhmmss-hhmm + if len(bytes) < 11 { + err = SyntaxError{"UTCTime too short"}; + return; + } + var ok1, ok2, ok3, ok4, ok5 bool; + year, ok1 := twoDigits(bytes[0:2], 99); + // RFC 5280, section 5.1.2.4 says that years 2050 or later use another date + // scheme. + if year > 50 { + ret.Year = 1900+int64(year); + } else { + ret.Year = 2000+int64(year); + } + ret.Month, ok2 = twoDigits(bytes[2:4], 12); + ret.Day, ok3 = twoDigits(bytes[4:6], 31); + ret.Hour, ok4 = twoDigits(bytes[6:8], 23); + ret.Minute, ok5 = twoDigits(bytes[8:10], 59); + if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 { + goto Error; + } + bytes = bytes[10:len(bytes)]; + switch bytes[0] { + case '0', '1', '2', '3', '4', '5', '6': + if len(bytes) < 3 { + goto Error; + } + ret.Second, ok1 = twoDigits(bytes[0:2], 60); // 60, not 59, because of leap seconds. + if !ok1 { + goto Error; + } + bytes = bytes[2:len(bytes)]; + } + if len(bytes) == 0 { + goto Error; + } + switch bytes[0] { + case 'Z': + if len(bytes) != 1 { + goto Error; + } + return; + case '-', '+': + if len(bytes) != 5 { + goto Error; + } + hours, ok1 := twoDigits(bytes[1:3], 12); + minutes, ok2 := twoDigits(bytes[3:5], 59); + if !ok1 || !ok2 { + goto Error; + } + sign := 1; + if bytes[0] == '-' { + sign = -1; + } + ret.ZoneOffset = sign*(60*(hours*60 + minutes)); + default: + goto Error; + } + return; + +Error: + err = SyntaxError{"invalid UTCTime"}; + return; +} + +// PrintableString + +// parsePrintableString parses a ASN.1 PrintableString from the given byte +// array and returns it. +func parsePrintableString(bytes []byte) (ret string, err os.Error) { + for _, b := range bytes { + if !isPrintable(b) { + err = SyntaxError{"PrintableString contains invalid character"}; + return; + } + } + ret = string(bytes); + return; +} + +// isPrintable returns true iff the given b is in the ASN.1 PrintableString set. +func isPrintable(b byte) bool { + return 'a' <= b && b <= 'z' || + 'A' <= b && b <= 'Z' || + '0' <= b && b <= '9' || + '\'' <= b && b <= ')' || + '+' <= b && b <= '/' || + b == ' ' || + b == ':' || + b == '=' || + b == '?'; +} + +// IA5String + +// parseIA5String parses a ASN.1 IA5String (ASCII string) from the given +// byte array and returns it. +func parseIA5String(bytes []byte) (ret string, err os.Error) { + for _, b := range bytes { + if b >= 0x80 { + err = SyntaxError{"IA5String contains invalid character"}; + return; + } + } + ret = string(bytes); + return; +} + +// A RawValue represents an undecoded ASN.1 object. +type RawValue struct { + Class, Tag int; + IsCompound bool; + Bytes []byte; +} + +// Tagging + +// ASN.1 objects have metadata preceeding them: +// the tag: the type of the object +// a flag denoting if this object is compound or not +// the class type: the namespace of the tag +// the length of the object, in bytes + +// Here are some standard tags and classes + +const ( + tagBoolean = 1; + tagInteger = 2; + tagBitString = 3; + tagOctetString = 4; + tagOID = 6; + tagSequence = 16; + tagSet = 17; + tagPrintableString = 19; + tagIA5String = 22; + tagUTCTime = 23; +) + +const ( + classUniversal = 0; + classApplication = 1; + classContextSpecific = 2; + classPrivate = 3; +) + +type tagAndLength struct { + class, tag, length int; + isCompound bool; +} + +// parseTagAndLength parses an ASN.1 tag and length pair from the given offset +// into a byte array. It returns the parsed data and the new offset. SET and +// SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we +// don't distinguish between ordered and unordered objects in this code. +func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset int, err os.Error) { + offset = initOffset; + b := bytes[offset]; + offset++; + ret.class = int(b>>6); + ret.isCompound = b&0x20 == 0x20; + ret.tag = int(b&0x1f); + + // If the bottom five bits are set, then the tag number is actually base 128 + // encoded afterwards + if ret.tag == 0x1f { + ret.tag, offset, err = parseBase128Int(bytes, offset); + if err != nil { + return; + } + } + if offset >= len(bytes) { + err = SyntaxError{"truncated tag or length"}; + return; + } + b = bytes[offset]; + offset++; + if b&0x80 == 0 { + // The length is encoded in the bottom 7 bits. + ret.length = int(b&0x7f); + } else { + // Bottom 7 bits give the number of length bytes to follow. + numBytes := int(b&0x7f); + // We risk overflowing a signed 32-bit number if we accept more than 3 bytes. + if numBytes > 3 { + err = StructuralError{"length too large"}; + return; + } + if numBytes == 0 { + err = SyntaxError{"indefinite length found (not DER)"}; + return; + } + ret.length = 0; + for i := 0; i < numBytes; i++ { + if offset >= len(bytes) { + err = SyntaxError{"truncated tag or length"}; + return; + } + b = bytes[offset]; + offset++; + ret.length <<= 8; + ret.length |= int(b); + } + } + + // We magically map SET and SET OF to SEQUENCE and SEQUENCE OF + // because we treat everything as ordered. + if ret.tag == tagSet { + ret.tag = tagSequence; + } + return; +} + +// ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead +// of" and "in addition to". When not specified, every primitive type has a +// default tag in the UNIVERSAL class. +// +// For example: a BIT STRING is tagged [UNIVERSAL 3] by default (although ASN.1 +// doesn't actually have a UNIVERSAL keyword). However, by saying [IMPLICIT +// CONTEXT-SPECIFIC 42], that means that the tag is replaced by another. +// +// On the other hand, if it said [EXPLICIT CONTEXT-SPECIFIC 10], then an +// /additional/ tag would wrap the default tag. This explicit tag will have the +// compound flag set. +// +// (This is used in order to remove ambiguity with optional elements.) +// +// You can layer EXPLICIT and IMPLICIT tags to an arbitary depth, however we +// don't support that here. We support a single layer of EXPLICIT or IMPLICIT +// tagging with tag strings on the fields of a structure. + +// fieldParameters is the parsed representation of tag string from a structure field. +type fieldParameters struct { + optional bool; // true iff the field is OPTIONAL + explicit bool; // true iff and EXPLICIT tag is in use. + defaultValue *int64; // a default value for INTEGER typed fields (maybe nil). + tag *int; // the EXPLICIT or IMPLICIT tag (maybe nil). + +// Invariants: +// if explicit is set, tag is non-nil. +} + +// Given a tag string with the format specified in the package comment, +// parseFieldParameters will parse it into a fieldParameters structure, +// ignoring unknown parts of the string. +func parseFieldParameters(str string) (ret fieldParameters) { + for _, part := range strings.Split(str, ",", 0) { + switch { + case part == "optional": + ret.optional = true; + case part == "explicit": + ret.explicit = true; + if ret.tag == nil { + ret.tag = new(int); + *ret.tag = 0; + } + case strings.HasPrefix(part, "default:"): + i, err := strconv.Atoi64(part[8:len(part)]); + if err == nil { + ret.defaultValue = new(int64); + *ret.defaultValue = i; + } + case strings.HasPrefix(part, "tag:"): + i, err := strconv.Atoi(part[4:len(part)]); + if err == nil { + ret.tag = new(int); + *ret.tag = i; + } + } + } + return; +} + +// Given a reflected Go type, getUniversalType returns the default tag number +// and expected compound flag. +func getUniversalType(t reflect.Type) (tagNumber int, isCompound, ok bool) { + switch t { + case objectIdentifierType: + return tagOID, false, true; + case bitStringType: + return tagBitString, false, true; + case timeType: + return tagUTCTime, false, true; + } + switch i := t.(type) { + case *reflect.BoolType: + return tagBoolean, false, true; + case *reflect.IntType: + return tagInteger, false, true; + case *reflect.Int64Type: + return tagInteger, false, true; + case *reflect.StructType: + return tagSequence, true, true; + case *reflect.SliceType: + if _, ok := t.(*reflect.SliceType).Elem().(*reflect.Uint8Type); ok { + return tagOctetString, false, true; + } + return tagSequence, true, true; + case *reflect.StringType: + return tagPrintableString, false, true; + } + return 0, false, false; +} + +// parseSequenceOf is used for SEQUENCE OF and SET OF values. It tries to parse +// a number of ASN.1 values from the given byte array and returns them as a +// slice of Go values of the given type. +func parseSequenceOf(bytes []byte, sliceType *reflect.SliceType, elemType reflect.Type) (ret *reflect.SliceValue, err os.Error) { + expectedTag, compoundType, ok := getUniversalType(elemType); + if !ok { + err = StructuralError{"unknown Go type for slice"}; + return; + } + + // First we iterate over the input and count the number of elements, + // checking that the types are correct in each case. + numElements := 0; + for offset := 0; offset < len(bytes); { + var t tagAndLength; + t, offset, err = parseTagAndLength(bytes, offset); + if err != nil { + return; + } + if t.class != classUniversal || t.isCompound != compoundType || t.tag != expectedTag { + err = StructuralError{"sequence tag mismatch"}; + return; + } + if invalidLength(offset, t.length, len(bytes)) { + err = SyntaxError{"truncated sequence"}; + return; + } + offset += t.length; + numElements++; + } + ret = reflect.MakeSlice(sliceType, numElements, numElements); + params := fieldParameters{}; + offset := 0; + for i := 0; i < numElements; i++ { + offset, err = parseField(ret.Elem(i), bytes, offset, params); + if err != nil { + return; + } + } + return; +} + +var ( + bitStringType = reflect.Typeof(BitString{}); + objectIdentifierType = reflect.Typeof(ObjectIdentifier{}); + timeType = reflect.Typeof(time.Time{}); + rawValueType = reflect.Typeof(RawValue{}); +) + +// invalidLength returns true iff offset + length > sliceLength, or if the +// addition would overflow. +func invalidLength(offset, length, sliceLength int) bool { + return offset+length < offset || offset+length > sliceLength; +} + +// parseField is the main parsing function. Given a byte array and an offset +// into the array, it will try to parse a suitable ASN.1 value out and store it +// in the given Value. +func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParameters) (offset int, err os.Error) { + offset = initOffset; + fieldType := v.Type(); + + // If we have run out of data, it may be that there are optional elements at the end. + if offset == len(bytes) { + if !setDefaultValue(v, params) { + err = SyntaxError{"sequence truncated"}; + } + return; + } + + // Deal with raw values. + if fieldType == rawValueType { + var t tagAndLength; + t, offset, err = parseTagAndLength(bytes, offset); + if err != nil { + return; + } + if invalidLength(offset, t.length, len(bytes)) { + err = SyntaxError{"data truncated"}; + return; + } + result := RawValue{t.class, t.tag, t.isCompound, bytes[offset : offset + t.length]}; + offset += t.length; + v.(*reflect.StructValue).Set(reflect.NewValue(result).(*reflect.StructValue)); + return; + } + + // Deal with the ANY type. + if ifaceType, ok := fieldType.(*reflect.InterfaceType); ok && ifaceType.NumMethod() == 0 { + ifaceValue := v.(*reflect.InterfaceValue); + var t tagAndLength; + t, offset, err = parseTagAndLength(bytes, offset); + if err != nil { + return; + } + if invalidLength(offset, t.length, len(bytes)) { + err = SyntaxError{"data truncated"}; + return; + } + var result interface{} + if !t.isCompound && t.class == classUniversal { + innerBytes := bytes[offset : offset + t.length]; + switch t.tag { + case tagPrintableString: + result, err = parsePrintableString(innerBytes); + case tagIA5String: + result, err = parseIA5String(innerBytes); + case tagInteger: + result, err = parseInt64(innerBytes); + case tagBitString: + result, err = parseBitString(innerBytes); + case tagOID: + result, err = parseObjectIdentifier(innerBytes); + case tagUTCTime: + result, err = parseUTCTime(innerBytes); + case tagOctetString: + result = innerBytes; + default: + // If we don't know how to handle the type, we just leave Value as nil. + } + } + offset += t.length; + if err != nil { + return; + } + if result != nil { + ifaceValue.Set(reflect.NewValue(result)); + } + return; + } + universalTag, compoundType, ok1 := getUniversalType(fieldType); + if !ok1 { + err = StructuralError{fmt.Sprintf("unknown Go type: %v", fieldType)}; + return; + } + + t, offset, err := parseTagAndLength(bytes, offset); + if err != nil { + return; + } + if params.explicit { + if t.class == classContextSpecific && t.tag == *params.tag && t.isCompound { + t, offset, err = parseTagAndLength(bytes, offset); + if err != nil { + return; + } + } else { + // The tags didn't match, it might be an optional element. + ok := setDefaultValue(v, params); + if ok { + offset = initOffset; + } else { + err = StructuralError{"explicitly tagged member didn't match"}; + } + return; + } + } + + // Special case for strings: PrintableString and IA5String both map to + // the Go type string. getUniversalType returns the tag for + // PrintableString when it sees a string so, if we see an IA5String on + // the wire, we change the universal type to match. + if universalTag == tagPrintableString && t.tag == tagIA5String { + universalTag = tagIA5String; + } + + expectedClass := classUniversal; + expectedTag := universalTag; + + if !params.explicit && params.tag != nil { + expectedClass = classContextSpecific; + expectedTag = *params.tag; + } + + // We have unwrapped any explicit tagging at this point. + if t.class != expectedClass || t.tag != expectedTag || t.isCompound != compoundType { + // Tags don't match. Again, it could be an optional element. + ok := setDefaultValue(v, params); + if ok { + offset = initOffset; + } else { + err = StructuralError{fmt.Sprintf("tags don't match (%d vs %+v) %+v %s %#v", expectedTag, t, params, fieldType.Name(), bytes[offset:len(bytes)])}; + } + return; + } + if invalidLength(offset, t.length, len(bytes)) { + err = SyntaxError{"data truncated"}; + return; + } + innerBytes := bytes[offset : offset + t.length]; + + // We deal with the structures defined in this package first. + switch fieldType { + case objectIdentifierType: + newSlice, err1 := parseObjectIdentifier(innerBytes); + sliceValue := v.(*reflect.SliceValue); + sliceValue.Set(reflect.MakeSlice(sliceValue.Type().(*reflect.SliceType), len(newSlice), len(newSlice))); + if err1 == nil { + reflect.ArrayCopy(sliceValue, reflect.NewValue(newSlice).(reflect.ArrayOrSliceValue)); + } + offset += t.length; + err = err1; + return; + case bitStringType: + structValue := v.(*reflect.StructValue); + bs, err1 := parseBitString(innerBytes); + offset += t.length; + if err1 == nil { + structValue.Set(reflect.NewValue(bs).(*reflect.StructValue)); + } + err = err1; + return; + case timeType: + structValue := v.(*reflect.StructValue); + time, err1 := parseUTCTime(innerBytes); + offset += t.length; + if err1 == nil { + structValue.Set(reflect.NewValue(time).(*reflect.StructValue)); + } + err = err1; + return; + } + switch val := v.(type) { + case *reflect.BoolValue: + parsedBool, err1 := parseBool(innerBytes); + offset += t.length; + if err1 == nil { + val.Set(parsedBool); + } + err = err1; + return; + case *reflect.IntValue: + parsedInt, err1 := parseInt(innerBytes); + offset += t.length; + if err1 == nil { + val.Set(parsedInt); + } + err = err1; + return; + case *reflect.Int64Value: + parsedInt, err1 := parseInt64(innerBytes); + offset += t.length; + if err1 == nil { + val.Set(parsedInt); + } + err = err1; + return; + case *reflect.StructValue: + structType := fieldType.(*reflect.StructType); + innerOffset := 0; + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i); + innerOffset, err = parseField(val.Field(i), innerBytes, innerOffset, parseFieldParameters(field.Tag)); + if err != nil { + return; + } + } + offset += t.length; + // We allow extra bytes at the end of the SEQUENCE because + // adding elements to the end has been used in X.509 as the + // version numbers have increased. + return; + case *reflect.SliceValue: + sliceType := fieldType.(*reflect.SliceType); + if _, ok := sliceType.Elem().(*reflect.Uint8Type); ok { + val.Set(reflect.MakeSlice(sliceType, len(innerBytes), len(innerBytes))); + reflect.ArrayCopy(val, reflect.NewValue(innerBytes).(reflect.ArrayOrSliceValue)); + return; + } + newSlice, err1 := parseSequenceOf(innerBytes, sliceType, sliceType.Elem()); + offset += t.length; + if err1 == nil { + val.Set(newSlice); + } + err = err1; + return; + case *reflect.StringValue: + var v string; + switch universalTag { + case tagPrintableString: + v, err = parsePrintableString(innerBytes); + case tagIA5String: + v, err = parseIA5String(innerBytes); + default: + err = SyntaxError{fmt.Sprintf("internal error: unknown string type %d", universalTag)}; + } + if err == nil { + val.Set(v); + } + return; + } + err = StructuralError{"unknown Go type"}; + return; +} + +// setDefaultValue is used to install a default value, from a tag string, into +// a Value. It is successful is the field was optional, even if a default value +// wasn't provided or it failed to install it into the Value. +func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { + if !params.optional { + return; + } + ok = true; + if params.defaultValue == nil { + return; + } + switch val := v.(type) { + case *reflect.IntValue: + val.Set(int(*params.defaultValue)); + case *reflect.Int64Value: + val.Set(int64(*params.defaultValue)); + } + return; +} + +// Unmarshal parses the DER-encoded ASN.1 data structure b +// and uses the reflect package to fill in an arbitrary value pointed at by val. +// Because Unmarshal uses the reflect package, the structs +// being written to must use upper case field names. +// +// An ASN.1 INTEGER can be written to an int or int64. +// If the encoded value does not fit in the Go type, +// Unmarshal returns a parse error. +// +// An ASN.1 BIT STRING can be written to a BitString. +// +// An ASN.1 OCTET STRING can be written to a []byte. +// +// An ASN.1 OBJECT IDENTIFIER can be written to an +// ObjectIdentifier. +// +// An ASN.1 PrintableString or IA5String can be written to a string. +// +// Any of the above ASN.1 values can be written to an interface{}. +// The value stored in the interface has the corresponding Go type. +// For integers, that type is int64. +// +// An ASN.1 SEQUENCE OF x or SET OF x can be written +// to a slice if an x can be written to the slice's element type. +// +// An ASN.1 SEQUENCE or SET can be written to a struct +// if each of the elements in the sequence can be +// written to the corresponding element in the struct. +// +// The following tags on struct fields have special meaning to Unmarshal: +// +// optional marks the field as ASN.1 OPTIONAL +// [explicit] tag:x specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC +// default:x sets the default value for optional integer fields +// +// Other ASN.1 types are not supported; if it encounters them, +// Unmarshal returns a parse error. +func Unmarshal(val interface{}, b []byte) os.Error { + v := reflect.NewValue(val).(*reflect.PtrValue).Elem(); + _, err := parseField(v, b, 0, fieldParameters{}); + return err; +} diff --git a/src/pkg/asn1/asn1_test.go b/src/pkg/asn1/asn1_test.go new file mode 100644 index 000000000..c3e1a13c9 --- /dev/null +++ b/src/pkg/asn1/asn1_test.go @@ -0,0 +1,562 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package asn1 + +import ( + "bytes"; + "reflect"; + "strings"; + "testing"; + "time"; +) + +type int64Test struct { + in []byte; + ok bool; + out int64; +} + +var int64TestData = []int64Test{ + int64Test{[]byte{0x00}, true, 0}, + int64Test{[]byte{0x7f}, true, 127}, + int64Test{[]byte{0x00, 0x80}, true, 128}, + int64Test{[]byte{0x01, 0x00}, true, 256}, + int64Test{[]byte{0x80}, true, -128}, + int64Test{[]byte{0xff, 0x7f}, true, -129}, + int64Test{[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, true, -1}, + int64Test{[]byte{0xff}, true, -1}, + int64Test{[]byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, true, -9223372036854775808}, + int64Test{[]byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, false, 0}, +} + +func TestParseInt64(t *testing.T) { + for i, test := range int64TestData { + ret, err := parseInt64(test.in); + if (err == nil) != test.ok { + t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok); + } + if test.ok && ret != test.out { + t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out); + } + } +} + +type bitStringTest struct { + in []byte; + ok bool; + out []byte; + bitLength int; +} + +var bitStringTestData = []bitStringTest{ + bitStringTest{[]byte{}, false, []byte{}, 0}, + bitStringTest{[]byte{0x00}, true, []byte{}, 0}, + bitStringTest{[]byte{0x07, 0x00}, true, []byte{0x00}, 1}, + bitStringTest{[]byte{0x07, 0x01}, false, []byte{}, 0}, + bitStringTest{[]byte{0x07, 0x40}, false, []byte{}, 0}, + bitStringTest{[]byte{0x08, 0x00}, false, []byte{}, 0}, +} + +func TestBitString(t *testing.T) { + for i, test := range bitStringTestData { + ret, err := parseBitString(test.in); + if (err == nil) != test.ok { + t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok); + } + if err == nil { + if test.bitLength != ret.BitLength || bytes.Compare(ret.Bytes, test.out) != 0 { + t.Errorf("#%d: Bad result: %v (expected %v %v)", i, ret, test.out, test.bitLength); + } + } + } +} + +func TestBitStringAt(t *testing.T) { + bs := BitString{[]byte{0x82, 0x40}, 16}; + if bs.At(0) != 1 { + t.Error("#1: Failed"); + } + if bs.At(1) != 0 { + t.Error("#2: Failed"); + } + if bs.At(6) != 1 { + t.Error("#3: Failed"); + } + if bs.At(9) != 1 { + t.Error("#4: Failed"); + } +} + +type objectIdentifierTest struct { + in []byte; + ok bool; + out []int; +} + +var objectIdentifierTestData = []objectIdentifierTest{ + objectIdentifierTest{[]byte{}, false, []int{}}, + objectIdentifierTest{[]byte{85}, true, []int{2, 5}}, + objectIdentifierTest{[]byte{85, 0x02}, true, []int{2, 5, 2}}, + objectIdentifierTest{[]byte{85, 0x02, 0xc0, 0x00}, true, []int{2, 5, 2, 0x2000}}, + objectIdentifierTest{[]byte{85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, false, []int{}}, +} + +func TestObjectIdentifier(t *testing.T) { + for i, test := range objectIdentifierTestData { + ret, err := parseObjectIdentifier(test.in); + if (err == nil) != test.ok { + t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok); + } + if err == nil { + if !reflect.DeepEqual(test.out, ret) { + t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out); + } + } + } +} + +type timeTest struct { + in string; + ok bool; + out time.Time; +} + +var timeTestData = []timeTest{ + timeTest{"910506164540-0700", true, time.Time{1991, 05, 06, 16, 45, 40, 0, -7 * 60 * 60, ""}}, + timeTest{"910506164540+0730", true, time.Time{1991, 05, 06, 16, 45, 40, 0, 7*60*60 + 30*60, ""}}, + timeTest{"910506234540Z", true, time.Time{1991, 05, 06, 23, 45, 40, 0, 0, ""}}, + timeTest{"9105062345Z", true, time.Time{1991, 05, 06, 23, 45, 0, 0, 0, ""}}, + timeTest{"a10506234540Z", false, time.Time{}}, + timeTest{"91a506234540Z", false, time.Time{}}, + timeTest{"9105a6234540Z", false, time.Time{}}, + timeTest{"910506a34540Z", false, time.Time{}}, + timeTest{"910506334a40Z", false, time.Time{}}, + timeTest{"91050633444aZ", false, time.Time{}}, + timeTest{"910506334461Z", false, time.Time{}}, + timeTest{"910506334400Za", false, time.Time{}}, +} + +func TestTime(t *testing.T) { + for i, test := range timeTestData { + ret, err := parseUTCTime(strings.Bytes(test.in)); + if (err == nil) != test.ok { + t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok); + } + if err == nil { + if !reflect.DeepEqual(test.out, ret) { + t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out); + } + } + } +} + +type tagAndLengthTest struct { + in []byte; + ok bool; + out tagAndLength; +} + +var tagAndLengthData = []tagAndLengthTest{ + tagAndLengthTest{[]byte{0x80, 0x01}, true, tagAndLength{2, 0, 1, false}}, + tagAndLengthTest{[]byte{0xa0, 0x01}, true, tagAndLength{2, 0, 1, true}}, + tagAndLengthTest{[]byte{0x02, 0x00}, true, tagAndLength{0, 2, 0, false}}, + tagAndLengthTest{[]byte{0xfe, 0x00}, true, tagAndLength{3, 30, 0, true}}, + tagAndLengthTest{[]byte{0x1f, 0x01, 0x00}, true, tagAndLength{0, 1, 0, false}}, + tagAndLengthTest{[]byte{0x1f, 0x81, 0x00, 0x00}, true, tagAndLength{0, 128, 0, false}}, + tagAndLengthTest{[]byte{0x1f, 0x81, 0x80, 0x01, 0x00}, true, tagAndLength{0, 0x4001, 0, false}}, + tagAndLengthTest{[]byte{0x00, 0x81, 0x01}, true, tagAndLength{0, 0, 1, false}}, + tagAndLengthTest{[]byte{0x00, 0x82, 0x01, 0x00}, true, tagAndLength{0, 0, 256, false}}, + tagAndLengthTest{[]byte{0x00, 0x83, 0x01, 0x00}, false, tagAndLength{}}, + tagAndLengthTest{[]byte{0x1f, 0x85}, false, tagAndLength{}}, + tagAndLengthTest{[]byte{0x30, 0x80}, false, tagAndLength{}}, +} + +func TestParseTagAndLength(t *testing.T) { + for i, test := range tagAndLengthData { + tagAndLength, _, err := parseTagAndLength(test.in, 0); + if (err == nil) != test.ok { + t.Errorf("#%d: Incorrect error result (did pass? %v, expected: %v)", i, err == nil, test.ok); + } + if err == nil && !reflect.DeepEqual(test.out, tagAndLength) { + t.Errorf("#%d: Bad result: %v (expected %v)", i, tagAndLength, test.out); + } + } +} + +type parseFieldParametersTest struct { + in string; + out fieldParameters; +} + +func newInt(n int) *int { + return &n; +} + +func newInt64(n int64) *int64 { + return &n; +} + +func newString(s string) *string { + return &s; +} + +func newBool(b bool) *bool { + return &b; +} + +var parseFieldParametersTestData []parseFieldParametersTest = []parseFieldParametersTest{ + parseFieldParametersTest{"", fieldParameters{false, false, nil, nil}}, + parseFieldParametersTest{"optional", fieldParameters{true, false, nil, nil}}, + parseFieldParametersTest{"explicit", fieldParameters{false, true, nil, new(int)}}, + parseFieldParametersTest{"optional,explicit", fieldParameters{true, true, nil, new(int)}}, + parseFieldParametersTest{"default:42", fieldParameters{false, false, newInt64(42), nil}}, + parseFieldParametersTest{"tag:17", fieldParameters{false, false, nil, newInt(17)}}, + parseFieldParametersTest{"optional,explicit,default:42,tag:17", fieldParameters{true, true, newInt64(42), newInt(17)}}, + parseFieldParametersTest{"optional,explicit,default:42,tag:17,rubbish1", fieldParameters{true, true, newInt64(42), newInt(17)}}, +} + +func TestParseFieldParameters(t *testing.T) { + for i, test := range parseFieldParametersTestData { + f := parseFieldParameters(test.in); + if !reflect.DeepEqual(f, test.out) { + t.Errorf("#%d: Bad result: %v (expected %v)", i, f, test.out); + } + } +} + +type unmarshalTest struct { + in []byte; + out interface{}; +} + +type TestObjectIdentifierStruct struct { + OID ObjectIdentifier; +} + +type TestContextSpecificTags struct { + A int "tag:1"; +} + +type TestContextSpecificTags2 struct { + A int "explicit,tag:1"; + B int; +} + +var unmarshalTestData []unmarshalTest = []unmarshalTest{ + unmarshalTest{[]byte{0x02, 0x01, 0x42}, newInt(0x42)}, + unmarshalTest{[]byte{0x30, 0x08, 0x06, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d}, &TestObjectIdentifierStruct{[]int{1, 2, 840, 113549}}}, + unmarshalTest{[]byte{0x03, 0x04, 0x06, 0x6e, 0x5d, 0xc0}, &BitString{[]byte{110, 93, 192}, 18}}, + unmarshalTest{[]byte{0x30, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, &[]int{1, 2, 3}}, + unmarshalTest{[]byte{0x02, 0x01, 0x10}, newInt(16)}, + unmarshalTest{[]byte{0x13, 0x04, 't', 'e', 's', 't'}, newString("test")}, + unmarshalTest{[]byte{0x16, 0x04, 't', 'e', 's', 't'}, newString("test")}, + unmarshalTest{[]byte{0x16, 0x04, 't', 'e', 's', 't'}, &RawValue{0, 22, false, []byte{'t', 'e', 's', 't'}}}, + unmarshalTest{[]byte{0x04, 0x04, 1, 2, 3, 4}, &RawValue{0, 4, false, []byte{1, 2, 3, 4}}}, + unmarshalTest{[]byte{0x30, 0x03, 0x81, 0x01, 0x01}, &TestContextSpecificTags{1}}, + unmarshalTest{[]byte{0x30, 0x08, 0xa1, 0x03, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02}, &TestContextSpecificTags2{1,2}}, + unmarshalTest{[]byte{0x01, 0x01, 0x00}, newBool(false)}, + unmarshalTest{[]byte{0x01, 0x01, 0x01}, newBool(true)}, +} + +func TestUnmarshal(t *testing.T) { + for i, test := range unmarshalTestData { + pv := reflect.MakeZero(reflect.NewValue(test.out).Type()); + zv := reflect.MakeZero(pv.Type().(*reflect.PtrType).Elem()); + pv.(*reflect.PtrValue).PointTo(zv); + val := pv.Interface(); + err := Unmarshal(val, test.in); + if err != nil { + t.Errorf("Unmarshal failed at index %d %v", i, err); + } + if !reflect.DeepEqual(val, test.out) { + t.Errorf("#%d:\nhave %#v\nwant %#v", i, val, test.out); + } + } +} + +type Certificate struct { + TBSCertificate TBSCertificate; + SignatureAlgorithm AlgorithmIdentifier; + SignatureValue BitString; +} + +type TBSCertificate struct { + Version int "optional,explicit,default:0,tag:0"; + SerialNumber RawValue; + SignatureAlgorithm AlgorithmIdentifier; + Issuer RDNSequence; + Validity Validity; + Subject RDNSequence; + PublicKey PublicKeyInfo; +} + +type AlgorithmIdentifier struct { + Algorithm ObjectIdentifier; +} + +type RDNSequence []RelativeDistinguishedName + +type RelativeDistinguishedName []AttributeTypeAndValue + +type AttributeTypeAndValue struct { + Type ObjectIdentifier; + Value interface{}; +} + +type Validity struct { + NotBefore, NotAfter time.Time; +} + +type PublicKeyInfo struct { + Algorithm AlgorithmIdentifier; + PublicKey BitString; +} + +func TestCertificate(t *testing.T) { + // This is a minimal, self-signed certificate that should parse correctly. + var cert Certificate; + if err := Unmarshal(&cert, derEncodedSelfSignedCertBytes); err != nil { + t.Errorf("Unmarshal failed: %v", err); + } + if !reflect.DeepEqual(cert, derEncodedSelfSignedCert) { + t.Errorf("Bad result:\ngot: %+v\nwant: %+v\n", cert, derEncodedSelfSignedCert); + } +} + +func TestCertificateWithNUL(t *testing.T) { + // This is the paypal NUL-hack certificate. It should fail to parse because + // NUL isn't a permitted character in a PrintableString. + + var cert Certificate; + if err := Unmarshal(&cert, derEncodedPaypalNULCertBytes); err == nil { + t.Error("Unmarshal succeeded, should not have"); + } +} + +var derEncodedSelfSignedCert = Certificate{ + TBSCertificate: TBSCertificate{ + Version: 0, + SerialNumber: RawValue{Class: 0, Tag: 2, IsCompound: false, Bytes: []uint8{0x0, 0x8c, 0xc3, 0x37, 0x92, 0x10, 0xec, 0x2c, 0x98}}, + SignatureAlgorithm: AlgorithmIdentifier{Algorithm: ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}}, + Issuer: RDNSequence{ + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 6}, Value: "XX"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 8}, Value: "Some-State"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 7}, Value: "City"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 10}, Value: "Internet Widgits Pty Ltd"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 3}, Value: "false.example.com"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "false@example.com"}}, + }, + Validity: Validity{NotBefore: time.Time{Year: 2009, Month: 10, Day: 8, Hour: 0, Minute: 25, Second: 53, Weekday: 0, ZoneOffset: 0, Zone: ""}, NotAfter: time.Time{Year: 2010, Month: 10, Day: 8, Hour: 0, Minute: 25, Second: 53, Weekday: 0, ZoneOffset: 0, Zone: ""}}, + Subject: RDNSequence{ + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 6}, Value: "XX"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 8}, Value: "Some-State"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 7}, Value: "City"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 10}, Value: "Internet Widgits Pty Ltd"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{2, 5, 4, 3}, Value: "false.example.com"}}, + RelativeDistinguishedName{AttributeTypeAndValue{Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "false@example.com"}}, + }, + PublicKey: PublicKeyInfo{ + Algorithm: AlgorithmIdentifier{Algorithm: ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}}, + PublicKey: BitString{ + Bytes: []uint8{ + 0x30, 0x48, 0x2, 0x41, 0x0, 0xcd, 0xb7, + 0x63, 0x9c, 0x32, 0x78, 0xf0, 0x6, 0xaa, 0x27, 0x7f, 0x6e, 0xaf, 0x42, + 0x90, 0x2b, 0x59, 0x2d, 0x8c, 0xbc, 0xbe, 0x38, 0xa1, 0xc9, 0x2b, 0xa4, + 0x69, 0x5a, 0x33, 0x1b, 0x1d, 0xea, 0xde, 0xad, 0xd8, 0xe9, 0xa5, 0xc2, + 0x7e, 0x8c, 0x4c, 0x2f, 0xd0, 0xa8, 0x88, 0x96, 0x57, 0x72, 0x2a, 0x4f, + 0x2a, 0xf7, 0x58, 0x9c, 0xf2, 0xc7, 0x70, 0x45, 0xdc, 0x8f, 0xde, 0xec, + 0x35, 0x7d, 0x2, 0x3, 0x1, 0x0, 0x1, + }, + BitLength: 592, + }, + }, + }, + SignatureAlgorithm: AlgorithmIdentifier{Algorithm: ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}}, + SignatureValue: BitString{ + Bytes: []uint8{ + 0xa6, 0x7b, 0x6, 0xec, 0x5e, 0xce, + 0x92, 0x77, 0x2c, 0xa4, 0x13, 0xcb, 0xa3, 0xca, 0x12, 0x56, 0x8f, 0xdc, 0x6c, + 0x7b, 0x45, 0x11, 0xcd, 0x40, 0xa7, 0xf6, 0x59, 0x98, 0x4, 0x2, 0xdf, 0x2b, + 0x99, 0x8b, 0xb9, 0xa4, 0xa8, 0xcb, 0xeb, 0x34, 0xc0, 0xf0, 0xa7, 0x8c, 0xf8, + 0xd9, 0x1e, 0xde, 0x14, 0xa5, 0xed, 0x76, 0xbf, 0x11, 0x6f, 0xe3, 0x60, 0xaa, + 0xfa, 0x88, 0x21, 0x49, 0x4, 0x35, + }, + BitLength: 512, + }, +} + +var derEncodedSelfSignedCertBytes = []byte{ + 0x30, 0x82, 0x02, 0x18, 0x30, + 0x82, 0x01, 0xc2, 0x02, 0x09, 0x00, 0x8c, 0xc3, 0x37, 0x92, 0x10, 0xec, 0x2c, + 0x98, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x05, 0x05, 0x00, 0x30, 0x81, 0x92, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x58, 0x58, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x04, 0x43, + 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, + 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, + 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x31, + 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, + 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x01, 0x16, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x40, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, + 0x30, 0x39, 0x31, 0x30, 0x30, 0x38, 0x30, 0x30, 0x32, 0x35, 0x35, 0x33, 0x5a, + 0x17, 0x0d, 0x31, 0x30, 0x31, 0x30, 0x30, 0x38, 0x30, 0x30, 0x32, 0x35, 0x35, + 0x33, 0x5a, 0x30, 0x81, 0x92, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x58, 0x58, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, + 0x08, 0x13, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x04, 0x43, 0x69, + 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67, + 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x31, 0x1a, + 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x66, 0x61, 0x6c, 0x73, + 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x01, 0x16, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x40, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x5c, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, + 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0xcd, 0xb7, 0x63, 0x9c, 0x32, 0x78, + 0xf0, 0x06, 0xaa, 0x27, 0x7f, 0x6e, 0xaf, 0x42, 0x90, 0x2b, 0x59, 0x2d, 0x8c, + 0xbc, 0xbe, 0x38, 0xa1, 0xc9, 0x2b, 0xa4, 0x69, 0x5a, 0x33, 0x1b, 0x1d, 0xea, + 0xde, 0xad, 0xd8, 0xe9, 0xa5, 0xc2, 0x7e, 0x8c, 0x4c, 0x2f, 0xd0, 0xa8, 0x88, + 0x96, 0x57, 0x72, 0x2a, 0x4f, 0x2a, 0xf7, 0x58, 0x9c, 0xf2, 0xc7, 0x70, 0x45, + 0xdc, 0x8f, 0xde, 0xec, 0x35, 0x7d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, + 0x03, 0x41, 0x00, 0xa6, 0x7b, 0x06, 0xec, 0x5e, 0xce, 0x92, 0x77, 0x2c, 0xa4, + 0x13, 0xcb, 0xa3, 0xca, 0x12, 0x56, 0x8f, 0xdc, 0x6c, 0x7b, 0x45, 0x11, 0xcd, + 0x40, 0xa7, 0xf6, 0x59, 0x98, 0x04, 0x02, 0xdf, 0x2b, 0x99, 0x8b, 0xb9, 0xa4, + 0xa8, 0xcb, 0xeb, 0x34, 0xc0, 0xf0, 0xa7, 0x8c, 0xf8, 0xd9, 0x1e, 0xde, 0x14, + 0xa5, 0xed, 0x76, 0xbf, 0x11, 0x6f, 0xe3, 0x60, 0xaa, 0xfa, 0x88, 0x21, 0x49, + 0x04, 0x35, +} + +var derEncodedPaypalNULCertBytes = []byte{ + 0x30, 0x82, 0x06, 0x44, 0x30, + 0x82, 0x05, 0xad, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x03, 0x00, 0xf0, 0x9b, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, + 0x05, 0x00, 0x30, 0x82, 0x01, 0x12, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x45, 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x13, 0x09, 0x42, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x6f, 0x6e, 0x61, + 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x42, 0x61, + 0x72, 0x63, 0x65, 0x6c, 0x6f, 0x6e, 0x61, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, + 0x55, 0x04, 0x0a, 0x13, 0x20, 0x49, 0x50, 0x53, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x73, 0x2e, 0x6c, 0x2e, 0x31, 0x2e, + 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x14, 0x25, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, + 0x20, 0x43, 0x2e, 0x49, 0x2e, 0x46, 0x2e, 0x20, 0x20, 0x42, 0x2d, 0x42, 0x36, + 0x32, 0x32, 0x31, 0x30, 0x36, 0x39, 0x35, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x13, 0x25, 0x69, 0x70, 0x73, 0x43, 0x41, 0x20, 0x43, 0x4c, + 0x41, 0x53, 0x45, 0x41, 0x31, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x25, 0x69, 0x70, 0x73, 0x43, 0x41, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, + 0x31, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, + 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x01, 0x16, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, + 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, + 0x30, 0x32, 0x32, 0x34, 0x32, 0x33, 0x30, 0x34, 0x31, 0x37, 0x5a, 0x17, 0x0d, + 0x31, 0x31, 0x30, 0x32, 0x32, 0x34, 0x32, 0x33, 0x30, 0x34, 0x31, 0x37, 0x5a, + 0x30, 0x81, 0x94, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, + 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, + 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x53, 0x61, 0x6e, 0x20, + 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x11, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0b, + 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x55, 0x6e, 0x69, 0x74, 0x31, 0x2f, + 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, 0x77, 0x77, 0x77, 0x2e, + 0x70, 0x61, 0x79, 0x70, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x73, 0x73, + 0x6c, 0x2e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x63, 0x63, 0x30, 0x81, 0x9f, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, + 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xd2, 0x69, + 0xfa, 0x6f, 0x3a, 0x00, 0xb4, 0x21, 0x1b, 0xc8, 0xb1, 0x02, 0xd7, 0x3f, 0x19, + 0xb2, 0xc4, 0x6d, 0xb4, 0x54, 0xf8, 0x8b, 0x8a, 0xcc, 0xdb, 0x72, 0xc2, 0x9e, + 0x3c, 0x60, 0xb9, 0xc6, 0x91, 0x3d, 0x82, 0xb7, 0x7d, 0x99, 0xff, 0xd1, 0x29, + 0x84, 0xc1, 0x73, 0x53, 0x9c, 0x82, 0xdd, 0xfc, 0x24, 0x8c, 0x77, 0xd5, 0x41, + 0xf3, 0xe8, 0x1e, 0x42, 0xa1, 0xad, 0x2d, 0x9e, 0xff, 0x5b, 0x10, 0x26, 0xce, + 0x9d, 0x57, 0x17, 0x73, 0x16, 0x23, 0x38, 0xc8, 0xd6, 0xf1, 0xba, 0xa3, 0x96, + 0x5b, 0x16, 0x67, 0x4a, 0x4f, 0x73, 0x97, 0x3a, 0x4d, 0x14, 0xa4, 0xf4, 0xe2, + 0x3f, 0x8b, 0x05, 0x83, 0x42, 0xd1, 0xd0, 0xdc, 0x2f, 0x7a, 0xe5, 0xb6, 0x10, + 0xb2, 0x11, 0xc0, 0xdc, 0x21, 0x2a, 0x90, 0xff, 0xae, 0x97, 0x71, 0x5a, 0x49, + 0x81, 0xac, 0x40, 0xf3, 0x3b, 0xb8, 0x59, 0xb2, 0x4f, 0x02, 0x03, 0x01, 0x00, + 0x01, 0xa3, 0x82, 0x03, 0x21, 0x30, 0x82, 0x03, 0x1d, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x06, 0x40, + 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x03, 0xf8, + 0x30, 0x13, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a, 0x06, 0x08, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x61, 0x8f, 0x61, 0x34, 0x43, 0x55, 0x14, + 0x7f, 0x27, 0x09, 0xce, 0x4c, 0x8b, 0xea, 0x9b, 0x7b, 0x19, 0x25, 0xbc, 0x6e, + 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, + 0x0e, 0x07, 0x60, 0xd4, 0x39, 0xc9, 0x1b, 0x5b, 0x5d, 0x90, 0x7b, 0x23, 0xc8, + 0xd2, 0x34, 0x9d, 0x4a, 0x9a, 0x46, 0x39, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, + 0x11, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x1d, 0x12, 0x04, + 0x15, 0x30, 0x13, 0x81, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, + 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x72, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d, 0x04, 0x65, 0x16, 0x63, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, + 0x4f, 0x54, 0x20, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x45, 0x44, 0x2e, + 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, + 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x2f, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x02, 0x04, 0x22, 0x16, 0x20, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, + 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, + 0x32, 0x30, 0x30, 0x32, 0x2f, 0x30, 0x43, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x86, 0xf8, 0x42, 0x01, 0x04, 0x04, 0x36, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, + 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, + 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x43, 0x4c, + 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x03, 0x04, 0x39, 0x16, 0x37, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, + 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, + 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x72, 0x65, 0x76, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68, 0x74, + 0x6d, 0x6c, 0x3f, 0x30, 0x43, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, + 0x42, 0x01, 0x07, 0x04, 0x36, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, + 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, + 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, + 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3f, 0x30, 0x41, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x08, 0x04, 0x34, 0x16, 0x32, 0x68, 0x74, + 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, + 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, + 0x30, 0x30, 0x32, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x4c, 0x41, + 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x30, 0x81, 0x83, 0x06, + 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x7c, 0x30, 0x7a, 0x30, 0x39, 0xa0, 0x37, 0xa0, + 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, + 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, + 0x32, 0x30, 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, + 0x72, 0x6c, 0x30, 0x3d, 0xa0, 0x3b, 0xa0, 0x39, 0x86, 0x37, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x2e, 0x69, + 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, + 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, + 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, 0x72, 0x6c, + 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, + 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, + 0x73, 0x70, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, + 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x68, 0xee, 0x79, 0x97, 0x97, 0xdd, 0x3b, + 0xef, 0x16, 0x6a, 0x06, 0xf2, 0x14, 0x9a, 0x6e, 0xcd, 0x9e, 0x12, 0xf7, 0xaa, + 0x83, 0x10, 0xbd, 0xd1, 0x7c, 0x98, 0xfa, 0xc7, 0xae, 0xd4, 0x0e, 0x2c, 0x9e, + 0x38, 0x05, 0x9d, 0x52, 0x60, 0xa9, 0x99, 0x0a, 0x81, 0xb4, 0x98, 0x90, 0x1d, + 0xae, 0xbb, 0x4a, 0xd7, 0xb9, 0xdc, 0x88, 0x9e, 0x37, 0x78, 0x41, 0x5b, 0xf7, + 0x82, 0xa5, 0xf2, 0xba, 0x41, 0x25, 0x5a, 0x90, 0x1a, 0x1e, 0x45, 0x38, 0xa1, + 0x52, 0x58, 0x75, 0x94, 0x26, 0x44, 0xfb, 0x20, 0x07, 0xba, 0x44, 0xcc, 0xe5, + 0x4a, 0x2d, 0x72, 0x3f, 0x98, 0x47, 0xf6, 0x26, 0xdc, 0x05, 0x46, 0x05, 0x07, + 0x63, 0x21, 0xab, 0x46, 0x9b, 0x9c, 0x78, 0xd5, 0x54, 0x5b, 0x3d, 0x0c, 0x1e, + 0xc8, 0x64, 0x8c, 0xb5, 0x50, 0x23, 0x82, 0x6f, 0xdb, 0xb8, 0x22, 0x1c, 0x43, + 0x96, 0x07, 0xa8, 0xbb, +} |