package rardecode import ( "bufio" "bytes" "crypto/hmac" "crypto/sha256" "errors" "hash" "hash/crc32" "io" "io/ioutil" "time" ) const ( // block types block5Arc = 1 block5File = 2 block5Service = 3 block5Encrypt = 4 block5End = 5 // block flags block5HasExtra = 0x0001 block5HasData = 0x0002 block5DataNotFirst = 0x0008 block5DataNotLast = 0x0010 // end block flags endArc5NotLast = 0x0001 // archive encryption block flags enc5CheckPresent = 0x0001 // password check data is present // main archive block flags arc5MultiVol = 0x0001 arc5Solid = 0x0004 // file block flags file5IsDir = 0x0001 file5HasUnixMtime = 0x0002 file5HasCRC32 = 0x0004 file5UnpSizeUnknown = 0x0008 // file encryption record flags file5EncCheckPresent = 0x0001 // password check data is present file5EncUseMac = 0x0002 // use MAC instead of plain checksum cacheSize50 = 4 maxPbkdf2Salt = 64 pwCheckSize = 8 maxKdfCount = 24 minHeaderSize = 7 ) var ( errBadPassword = errors.New("rardecode: incorrect password") errCorruptEncrypt = errors.New("rardecode: corrupt encryption data") errUnknownEncMethod = errors.New("rardecode: unknown encryption method") ) type extra struct { ftype uint64 // field type data readBuf // field data } type blockHeader50 struct { htype uint64 // block type flags uint64 data readBuf // block header data extra []extra // extra fields dataSize int64 // size of block data } // leHash32 wraps a hash.Hash32 to return the result of Sum in little // endian format. type leHash32 struct { hash.Hash32 } func (h leHash32) Sum(b []byte) []byte { s := h.Sum32() return append(b, byte(s), byte(s>>8), byte(s>>16), byte(s>>24)) } func newLittleEndianCRC32() hash.Hash32 { return leHash32{crc32.NewIEEE()} } // hash50 implements fileChecksum for RAR 5 archives type hash50 struct { hash.Hash // hash file data is written to sum []byte // file checksum key []byte // if present used with hmac in calculating checksum from hash } func (h *hash50) valid() bool { sum := h.Sum(nil) if len(h.key) > 0 { mac := hmac.New(sha256.New, h.key) mac.Write(sum) sum = mac.Sum(sum[:0]) if len(h.sum) == 4 { // CRC32 for i, v := range sum[4:] { sum[i&3] ^= v } sum = sum[:4] } } return bytes.Equal(sum, h.sum) } // archive50 implements fileBlockReader for RAR 5 file format archives type archive50 struct { byteReader // reader for current block data v *bufio.Reader // reader for current archive volume pass []byte blockKey []byte // key used to encrypt blocks multi bool // archive is multi-volume solid bool // is a solid archive checksum hash50 // file checksum dec decoder // optional decoder used to unpack file buf readBuf // temporary buffer keyCache [cacheSize50]struct { // encryption key cache kdfCount int salt []byte keys [][]byte } } // calcKeys50 calculates the keys used in RAR 5 archive processing. // The returned slice of byte slices contains 3 keys. // Key 0 is used for block or file decryption. // Key 1 is optionally used for file checksum calculation. // Key 2 is optionally used for password checking. func calcKeys50(pass, salt []byte, kdfCount int) [][]byte { if len(salt) > maxPbkdf2Salt { salt = salt[:maxPbkdf2Salt] } keys := make([][]byte, 3) if len(keys) == 0 { return keys } prf := hmac.New(sha256.New, pass) prf.Write(salt) prf.Write([]byte{0, 0, 0, 1}) t := prf.Sum(nil) u := append([]byte(nil), t...) kdfCount-- for i, iter := range []int{kdfCount, 16, 16} { for iter > 0 { prf.Reset() prf.Write(u) u = prf.Sum(u[:0]) for j := range u { t[j] ^= u[j] } iter-- } keys[i] = append([]byte(nil), t...) } pwcheck := keys[2] for i, v := range pwcheck[pwCheckSize:] { pwcheck[i&(pwCheckSize-1)] ^= v } keys[2] = pwcheck[:pwCheckSize] return keys } // getKeys reads kdfcount and salt from b and returns the corresponding encryption keys. func (a *archive50) getKeys(b *readBuf) (keys [][]byte, err error) { if len(*b) < 17 { return nil, errCorruptEncrypt } // read kdf count and salt kdfCount := int(b.byte()) if kdfCount > maxKdfCount { return nil, errCorruptEncrypt } kdfCount = 1 << uint(kdfCount) salt := b.bytes(16) // check cache of keys for match for _, v := range a.keyCache { if kdfCount == v.kdfCount && bytes.Equal(salt, v.salt) { return v.keys, nil } } // not found, calculate keys keys = calcKeys50(a.pass, salt, kdfCount) // store in cache copy(a.keyCache[1:], a.keyCache[:]) a.keyCache[0].kdfCount = kdfCount a.keyCache[0].salt = append([]byte(nil), salt...) a.keyCache[0].keys = keys return keys, nil } // checkPassword calculates if a password is correct given password check data and keys. func checkPassword(b *readBuf, keys [][]byte) error { if len(*b) < 12 { return nil // not enough bytes, ignore for the moment } pwcheck := b.bytes(8) sum := b.bytes(4) csum := sha256.Sum256(pwcheck) if bytes.Equal(sum, csum[:len(sum)]) && !bytes.Equal(pwcheck, keys[2]) { return errBadPassword } return nil } // parseFileEncryptionRecord processes the optional file encryption record from a file header. func (a *archive50) parseFileEncryptionRecord(b readBuf, f *fileBlockHeader) error { if ver := b.uvarint(); ver != 0 { return errUnknownEncMethod } flags := b.uvarint() keys, err := a.getKeys(&b) if err != nil { return err } f.key = keys[0] if len(b) < 16 { return errCorruptEncrypt } f.iv = b.bytes(16) if flags&file5EncCheckPresent > 0 { if err := checkPassword(&b, keys); err != nil { return err } } if flags&file5EncUseMac > 0 { a.checksum.key = keys[1] } return nil } func (a *archive50) parseFileHeader(h *blockHeader50) (*fileBlockHeader, error) { a.checksum.sum = nil a.checksum.key = nil f := new(fileBlockHeader) f.first = h.flags&block5DataNotFirst == 0 f.last = h.flags&block5DataNotLast == 0 flags := h.data.uvarint() // file flags f.IsDir = flags&file5IsDir > 0 f.UnKnownSize = flags&file5UnpSizeUnknown > 0 f.UnPackedSize = int64(h.data.uvarint()) f.PackedSize = h.dataSize f.Attributes = int64(h.data.uvarint()) if flags&file5HasUnixMtime > 0 { if len(h.data) < 4 { return nil, errCorruptFileHeader } f.ModificationTime = time.Unix(int64(h.data.uint32()), 0) } if flags&file5HasCRC32 > 0 { if len(h.data) < 4 { return nil, errCorruptFileHeader } a.checksum.sum = append([]byte(nil), h.data.bytes(4)...) if f.first { a.checksum.Hash = newLittleEndianCRC32() f.cksum = &a.checksum } } flags = h.data.uvarint() // compression flags f.solid = flags&0x0040 > 0 f.winSize = uint(flags&0x3C00)>>10 + 17 method := (flags >> 7) & 7 // compression method (0 == none) if f.first && method != 0 { unpackver := flags & 0x003f if unpackver != 0 { return nil, errUnknownDecoder } if a.dec == nil { a.dec = new(decoder50) } f.decoder = a.dec } switch h.data.uvarint() { case 0: f.HostOS = HostOSWindows case 1: f.HostOS = HostOSUnix default: f.HostOS = HostOSUnknown } nlen := int(h.data.uvarint()) if len(h.data) < nlen { return nil, errCorruptFileHeader } f.Name = string(h.data.bytes(nlen)) // parse optional extra records for _, e := range h.extra { var err error switch e.ftype { case 1: // encryption err = a.parseFileEncryptionRecord(e.data, f) case 2: // TODO: hash case 3: // TODO: time case 4: // version _ = e.data.uvarint() // ignore flags field f.Version = int(e.data.uvarint()) case 5: // TODO: redirection case 6: // TODO: owner } if err != nil { return nil, err } } return f, nil } // parseEncryptionBlock calculates the key for block encryption. func (a *archive50) parseEncryptionBlock(b readBuf) error { if ver := b.uvarint(); ver != 0 { return errUnknownEncMethod } flags := b.uvarint() keys, err := a.getKeys(&b) if err != nil { return err } if flags&enc5CheckPresent > 0 { if err := checkPassword(&b, keys); err != nil { return err } } a.blockKey = keys[0] return nil } func (a *archive50) readBlockHeader() (*blockHeader50, error) { r := io.Reader(a.v) if a.blockKey != nil { // block is encrypted iv := a.buf[:16] if err := readFull(r, iv); err != nil { return nil, err } r = newAesDecryptReader(r, a.blockKey, iv) } b := a.buf[:minHeaderSize] if err := readFull(r, b); err != nil { return nil, err } crc := b.uint32() hash := crc32.NewIEEE() hash.Write(b) size := int(b.uvarint()) // header size if size > cap(a.buf) { a.buf = readBuf(make([]byte, size)) } else { a.buf = a.buf[:size] } n := copy(a.buf, b) // copy left over bytes if err := readFull(r, a.buf[n:]); err != nil { // read rest of header return nil, err } // check header crc hash.Write(a.buf[n:]) if crc != hash.Sum32() { return nil, errBadHeaderCrc } b = a.buf h := new(blockHeader50) h.htype = b.uvarint() h.flags = b.uvarint() var extraSize int if h.flags&block5HasExtra > 0 { extraSize = int(b.uvarint()) } if h.flags&block5HasData > 0 { h.dataSize = int64(b.uvarint()) } if len(b) < extraSize { return nil, errCorruptHeader } h.data = b.bytes(len(b) - extraSize) // read header extra records for len(b) > 0 { size = int(b.uvarint()) if len(b) < size { return nil, errCorruptHeader } data := readBuf(b.bytes(size)) ftype := data.uvarint() h.extra = append(h.extra, extra{ftype, data}) } return h, nil } // next advances to the next file block in the archive func (a *archive50) next() (*fileBlockHeader, error) { for { h, err := a.readBlockHeader() if err != nil { return nil, err } a.byteReader = limitByteReader(a.v, h.dataSize) switch h.htype { case block5File: return a.parseFileHeader(h) case block5Arc: flags := h.data.uvarint() a.multi = flags&arc5MultiVol > 0 a.solid = flags&arc5Solid > 0 case block5Encrypt: err = a.parseEncryptionBlock(h.data) case block5End: flags := h.data.uvarint() if flags&endArc5NotLast == 0 || !a.multi { return nil, errArchiveEnd } return nil, errArchiveContinues default: // discard block data _, err = io.Copy(ioutil.Discard, a.byteReader) } if err != nil { return nil, err } } } func (a *archive50) version() int { return fileFmt50 } func (a *archive50) reset() { a.blockKey = nil // reset encryption when opening new volume file } func (a *archive50) isSolid() bool { return a.solid } // newArchive50 creates a new fileBlockReader for a Version 5 archive. func newArchive50(r *bufio.Reader, password string) fileBlockReader { a := new(archive50) a.v = r a.pass = []byte(password) a.buf = make([]byte, 100) return a }