package pkcs7 import ( "bytes" "errors" ) var encodeIndent = 0 type asn1Object interface { EncodeTo(writer *bytes.Buffer) error } type asn1Structured struct { tagBytes []byte content []asn1Object } func (s asn1Structured) EncodeTo(out *bytes.Buffer) error { // fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes) encodeIndent++ inner := new(bytes.Buffer) for _, obj := range s.content { err := obj.EncodeTo(inner) if err != nil { return err } } encodeIndent-- out.Write(s.tagBytes) encodeLength(out, inner.Len()) out.Write(inner.Bytes()) return nil } type asn1Primitive struct { tagBytes []byte length int content []byte } func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error { _, err := out.Write(p.tagBytes) if err != nil { return err } if err = encodeLength(out, p.length); err != nil { return err } // fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length) // fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content)) out.Write(p.content) return nil } func ber2der(ber []byte) ([]byte, error) { if len(ber) == 0 { return nil, errors.New("ber2der: input ber is empty") } // fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber)) out := new(bytes.Buffer) obj, _, err := readObject(ber, 0) if err != nil { return nil, err } obj.EncodeTo(out) // if offset < len(ber) { // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber)) //} return out.Bytes(), nil } // encodes lengths that are longer than 127 into string of bytes func marshalLongLength(out *bytes.Buffer, i int) (err error) { n := lengthLength(i) for ; n > 0; n-- { err = out.WriteByte(byte(i >> uint((n-1)*8))) if err != nil { return } } return nil } // computes the byte length of an encoded length value func lengthLength(i int) (numBytes int) { numBytes = 1 for i > 255 { numBytes++ i >>= 8 } return } // encodes the length in DER format // If the length fits in 7 bits, the value is encoded directly. // // Otherwise, the number of bytes to encode the length is first determined. // This number is likely to be 4 or less for a 32bit length. This number is // added to 0x80. The length is encoded in big endian encoding follow after // // Examples: // length | byte 1 | bytes n // 0 | 0x00 | - // 120 | 0x78 | - // 200 | 0x81 | 0xC8 // 500 | 0x82 | 0x01 0xF4 // func encodeLength(out *bytes.Buffer, length int) (err error) { if length >= 128 { l := lengthLength(length) err = out.WriteByte(0x80 | byte(l)) if err != nil { return } err = marshalLongLength(out, length) if err != nil { return } } else { err = out.WriteByte(byte(length)) if err != nil { return } } return } func readObject(ber []byte, offset int) (asn1Object, int, error) { berLen := len(ber) if offset >= berLen { return nil, 0, errors.New("ber2der: offset is after end of ber data") } tagStart := offset b := ber[offset] offset++ if offset >= berLen { return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") } tag := b & 0x1F // last 5 bits if tag == 0x1F { tag = 0 for ber[offset] >= 0x80 { tag = tag*128 + ber[offset] - 0x80 offset++ if offset > berLen { return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") } } // jvehent 20170227: this doesn't appear to be used anywhere... // tag = tag*128 + ber[offset] - 0x80 offset++ if offset > berLen { return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") } } tagEnd := offset kind := b & 0x20 if kind == 0 { debugprint("--> Primitive\n") } else { debugprint("--> Constructed\n") } // read length var length int l := ber[offset] offset++ if offset > berLen { return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") } indefinite := false if l > 0x80 { numberOfBytes := (int)(l & 0x7F) if numberOfBytes > 4 { // int is only guaranteed to be 32bit return nil, 0, errors.New("ber2der: BER tag length too long") } if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F { return nil, 0, errors.New("ber2der: BER tag length is negative") } if (int)(ber[offset]) == 0x0 { return nil, 0, errors.New("ber2der: BER tag length has leading zero") } debugprint("--> (compute length) indicator byte: %x\n", l) debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes]) for i := 0; i < numberOfBytes; i++ { length = length*256 + (int)(ber[offset]) offset++ if offset > berLen { return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") } } } else if l == 0x80 { indefinite = true } else { length = (int)(l) } if length < 0 { return nil, 0, errors.New("ber2der: invalid negative value found in BER tag length") } // fmt.Printf("--> length : %d\n", length) contentEnd := offset + length if contentEnd > len(ber) { return nil, 0, errors.New("ber2der: BER tag length is more than available data") } debugprint("--> content start : %d\n", offset) debugprint("--> content end : %d\n", contentEnd) debugprint("--> content : % X\n", ber[offset:contentEnd]) var obj asn1Object if indefinite && kind == 0 { return nil, 0, errors.New("ber2der: Indefinite form tag must have constructed encoding") } if kind == 0 { obj = asn1Primitive{ tagBytes: ber[tagStart:tagEnd], length: length, content: ber[offset:contentEnd], } } else { var subObjects []asn1Object for (offset < contentEnd) || indefinite { var subObj asn1Object var err error subObj, offset, err = readObject(ber, offset) if err != nil { return nil, 0, err } subObjects = append(subObjects, subObj) if indefinite { terminated, err := isIndefiniteTermination(ber, offset) if err != nil { return nil, 0, err } if terminated { break } } } obj = asn1Structured{ tagBytes: ber[tagStart:tagEnd], content: subObjects, } } // Apply indefinite form length with 0x0000 terminator. if indefinite { contentEnd = offset + 2 } return obj, contentEnd, nil } func isIndefiniteTermination(ber []byte, offset int) (bool, error) { if len(ber)-offset < 2 { return false, errors.New("ber2der: Invalid BER format") } return bytes.Index(ber[offset:], []byte{0x0, 0x0}) == 0, nil } func debugprint(format string, a ...interface{}) { // fmt.Printf(format, a) }