123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155 |
- package crypt
- import (
- "bytes"
- "context"
- "crypto/aes"
- gocipher "crypto/cipher"
- "crypto/rand"
- "encoding/base32"
- "encoding/base64"
- "errors"
- "fmt"
- "io"
- "strconv"
- "strings"
- "sync"
- "time"
- "unicode/utf8"
- "github.com/Max-Sum/base32768"
- "github.com/rclone/rclone/backend/crypt/pkcs7"
- "github.com/rclone/rclone/fs"
- "github.com/rclone/rclone/fs/accounting"
- "github.com/rclone/rclone/lib/readers"
- "github.com/rclone/rclone/lib/version"
- "github.com/rfjakob/eme"
- "golang.org/x/crypto/nacl/secretbox"
- "golang.org/x/crypto/scrypt"
- )
- // Constants
- const (
- nameCipherBlockSize = aes.BlockSize
- fileMagic = "RCLONE\x00\x00"
- fileMagicSize = len(fileMagic)
- fileNonceSize = 24
- fileHeaderSize = fileMagicSize + fileNonceSize
- blockHeaderSize = secretbox.Overhead
- blockDataSize = 64 * 1024
- blockSize = blockHeaderSize + blockDataSize
- )
- // Errors returned by cipher
- var (
- ErrorBadDecryptUTF8 = errors.New("bad decryption - utf-8 invalid")
- ErrorBadDecryptControlChar = errors.New("bad decryption - contains control chars")
- ErrorNotAMultipleOfBlocksize = errors.New("not a multiple of blocksize")
- ErrorTooShortAfterDecode = errors.New("too short after base32 decode")
- ErrorTooLongAfterDecode = errors.New("too long after base32 decode")
- ErrorEncryptedFileTooShort = errors.New("file is too short to be encrypted")
- ErrorEncryptedFileBadHeader = errors.New("file has truncated block header")
- ErrorEncryptedBadMagic = errors.New("not an encrypted file - bad magic string")
- ErrorEncryptedBadBlock = errors.New("failed to authenticate decrypted block - bad password?")
- ErrorBadBase32Encoding = errors.New("bad base32 filename encoding")
- ErrorFileClosed = errors.New("file already closed")
- ErrorNotAnEncryptedFile = errors.New("not an encrypted file - does not match suffix")
- ErrorBadSeek = errors.New("Seek beyond end of file")
- ErrorSuffixMissingDot = errors.New("suffix config setting should include a '.'")
- defaultSalt = []byte{0xA8, 0x0D, 0xF4, 0x3A, 0x8F, 0xBD, 0x03, 0x08, 0xA7, 0xCA, 0xB8, 0x3E, 0x58, 0x1F, 0x86, 0xB1}
- obfuscQuoteRune = '!'
- )
- // Global variables
- var (
- fileMagicBytes = []byte(fileMagic)
- )
- // ReadSeekCloser is the interface of the read handles
- type ReadSeekCloser interface {
- io.Reader
- io.Seeker
- io.Closer
- fs.RangeSeeker
- }
- // OpenRangeSeek opens the file handle at the offset with the limit given
- type OpenRangeSeek func(ctx context.Context, offset, limit int64) (io.ReadCloser, error)
- // NameEncryptionMode is the type of file name encryption in use
- type NameEncryptionMode int
- // NameEncryptionMode levels
- const (
- NameEncryptionOff NameEncryptionMode = iota
- NameEncryptionStandard
- NameEncryptionObfuscated
- )
- // NewNameEncryptionMode turns a string into a NameEncryptionMode
- func NewNameEncryptionMode(s string) (mode NameEncryptionMode, err error) {
- s = strings.ToLower(s)
- switch s {
- case "off":
- mode = NameEncryptionOff
- case "standard":
- mode = NameEncryptionStandard
- case "obfuscate":
- mode = NameEncryptionObfuscated
- default:
- err = fmt.Errorf("unknown file name encryption mode %q", s)
- }
- return mode, err
- }
- // String turns mode into a human-readable string
- func (mode NameEncryptionMode) String() (out string) {
- switch mode {
- case NameEncryptionOff:
- out = "off"
- case NameEncryptionStandard:
- out = "standard"
- case NameEncryptionObfuscated:
- out = "obfuscate"
- default:
- out = fmt.Sprintf("Unknown mode #%d", mode)
- }
- return out
- }
- // fileNameEncoding are the encoding methods dealing with encrypted file names
- type fileNameEncoding interface {
- EncodeToString(src []byte) string
- DecodeString(s string) ([]byte, error)
- }
- // caseInsensitiveBase32Encoding defines a file name encoding
- // using a modified version of standard base32 as described in
- // RFC4648
- //
- // The standard encoding is modified in two ways
- // - it becomes lower case (no-one likes upper case filenames!)
- // - we strip the padding character `=`
- type caseInsensitiveBase32Encoding struct{}
- // EncodeToString encodes a string using the modified version of
- // base32 encoding.
- func (caseInsensitiveBase32Encoding) EncodeToString(src []byte) string {
- encoded := base32.HexEncoding.EncodeToString(src)
- encoded = strings.TrimRight(encoded, "=")
- return strings.ToLower(encoded)
- }
- // DecodeString decodes a string as encoded by EncodeToString
- func (caseInsensitiveBase32Encoding) DecodeString(s string) ([]byte, error) {
- if strings.HasSuffix(s, "=") {
- return nil, ErrorBadBase32Encoding
- }
- // First figure out how many padding characters to add
- roundUpToMultipleOf8 := (len(s) + 7) &^ 7
- equals := roundUpToMultipleOf8 - len(s)
- s = strings.ToUpper(s) + "========"[:equals]
- return base32.HexEncoding.DecodeString(s)
- }
- // NewNameEncoding creates a NameEncoding from a string
- func NewNameEncoding(s string) (enc fileNameEncoding, err error) {
- s = strings.ToLower(s)
- switch s {
- case "base32":
- enc = caseInsensitiveBase32Encoding{}
- case "base64":
- enc = base64.RawURLEncoding
- case "base32768":
- enc = base32768.SafeEncoding
- default:
- err = fmt.Errorf("unknown file name encoding mode %q", s)
- }
- return enc, err
- }
- // Cipher defines an encoding and decoding cipher for the crypt backend
- type Cipher struct {
- dataKey [32]byte // Key for secretbox
- nameKey [32]byte // 16,24 or 32 bytes
- nameTweak [nameCipherBlockSize]byte // used to tweak the name crypto
- block gocipher.Block
- mode NameEncryptionMode
- fileNameEnc fileNameEncoding
- buffers sync.Pool // encrypt/decrypt buffers
- cryptoRand io.Reader // read crypto random numbers from here
- dirNameEncrypt bool
- passBadBlocks bool // if set passed bad blocks as zeroed blocks
- encryptedSuffix string
- }
- // newCipher initialises the cipher. If salt is "" then it uses a built in salt val
- func newCipher(mode NameEncryptionMode, password, salt string, dirNameEncrypt bool, enc fileNameEncoding) (*Cipher, error) {
- c := &Cipher{
- mode: mode,
- fileNameEnc: enc,
- cryptoRand: rand.Reader,
- dirNameEncrypt: dirNameEncrypt,
- encryptedSuffix: ".bin",
- }
- c.buffers.New = func() interface{} {
- return new([blockSize]byte)
- }
- err := c.Key(password, salt)
- if err != nil {
- return nil, err
- }
- return c, nil
- }
- // setEncryptedSuffix set suffix, or an empty string
- func (c *Cipher) setEncryptedSuffix(suffix string) {
- if strings.EqualFold(suffix, "none") {
- c.encryptedSuffix = ""
- return
- }
- if !strings.HasPrefix(suffix, ".") {
- fs.Errorf(nil, "crypt: bad suffix: %v", ErrorSuffixMissingDot)
- suffix = "." + suffix
- }
- c.encryptedSuffix = suffix
- }
- // Call to set bad block pass through
- func (c *Cipher) setPassBadBlocks(passBadBlocks bool) {
- c.passBadBlocks = passBadBlocks
- }
- // Key creates all the internal keys from the password passed in using
- // scrypt.
- //
- // If salt is "" we use a fixed salt just to make attackers lives
- // slightly harder than using no salt.
- //
- // Note that empty password makes all 0x00 keys which is used in the
- // tests.
- func (c *Cipher) Key(password, salt string) (err error) {
- const keySize = len(c.dataKey) + len(c.nameKey) + len(c.nameTweak)
- var saltBytes = defaultSalt
- if salt != "" {
- saltBytes = []byte(salt)
- }
- var key []byte
- if password == "" {
- key = make([]byte, keySize)
- } else {
- key, err = scrypt.Key([]byte(password), saltBytes, 16384, 8, 1, keySize)
- if err != nil {
- return err
- }
- }
- copy(c.dataKey[:], key)
- copy(c.nameKey[:], key[len(c.dataKey):])
- copy(c.nameTweak[:], key[len(c.dataKey)+len(c.nameKey):])
- // Key the name cipher
- c.block, err = aes.NewCipher(c.nameKey[:])
- return err
- }
- // getBlock gets a block from the pool of size blockSize
- func (c *Cipher) getBlock() *[blockSize]byte {
- return c.buffers.Get().(*[blockSize]byte)
- }
- // putBlock returns a block to the pool of size blockSize
- func (c *Cipher) putBlock(buf *[blockSize]byte) {
- c.buffers.Put(buf)
- }
- // encryptSegment encrypts a path segment
- //
- // This uses EME with AES.
- //
- // EME (ECB-Mix-ECB) is a wide-block encryption mode presented in the
- // 2003 paper "A Parallelizable Enciphering Mode" by Halevi and
- // Rogaway.
- //
- // This makes for deterministic encryption which is what we want - the
- // same filename must encrypt to the same thing.
- //
- // This means that
- // - filenames with the same name will encrypt the same
- // - filenames which start the same won't have a common prefix
- func (c *Cipher) encryptSegment(plaintext string) string {
- if plaintext == "" {
- return ""
- }
- paddedPlaintext := pkcs7.Pad(nameCipherBlockSize, []byte(plaintext))
- ciphertext := eme.Transform(c.block, c.nameTweak[:], paddedPlaintext, eme.DirectionEncrypt)
- return c.fileNameEnc.EncodeToString(ciphertext)
- }
- // decryptSegment decrypts a path segment
- func (c *Cipher) decryptSegment(ciphertext string) (string, error) {
- if ciphertext == "" {
- return "", nil
- }
- rawCiphertext, err := c.fileNameEnc.DecodeString(ciphertext)
- if err != nil {
- return "", err
- }
- if len(rawCiphertext)%nameCipherBlockSize != 0 {
- return "", ErrorNotAMultipleOfBlocksize
- }
- if len(rawCiphertext) == 0 {
- // not possible if decodeFilename() working correctly
- return "", ErrorTooShortAfterDecode
- }
- if len(rawCiphertext) > 2048 {
- return "", ErrorTooLongAfterDecode
- }
- paddedPlaintext := eme.Transform(c.block, c.nameTweak[:], rawCiphertext, eme.DirectionDecrypt)
- plaintext, err := pkcs7.Unpad(nameCipherBlockSize, paddedPlaintext)
- if err != nil {
- return "", err
- }
- return string(plaintext), err
- }
- // Simple obfuscation routines
- func (c *Cipher) obfuscateSegment(plaintext string) string {
- if plaintext == "" {
- return ""
- }
- // If the string isn't valid UTF8 then don't rotate; just
- // prepend a !.
- if !utf8.ValidString(plaintext) {
- return "!." + plaintext
- }
- // Calculate a simple rotation based on the filename and
- // the nameKey
- var dir int
- for _, runeValue := range plaintext {
- dir += int(runeValue)
- }
- dir = dir % 256
- // We'll use this number to store in the result filename...
- var result bytes.Buffer
- _, _ = result.WriteString(strconv.Itoa(dir) + ".")
- // but we'll augment it with the nameKey for real calculation
- for i := 0; i < len(c.nameKey); i++ {
- dir += int(c.nameKey[i])
- }
- // Now for each character, depending on the range it is in
- // we will actually rotate a different amount
- for _, runeValue := range plaintext {
- switch {
- case runeValue == obfuscQuoteRune:
- // Quote the Quote character
- _, _ = result.WriteRune(obfuscQuoteRune)
- _, _ = result.WriteRune(obfuscQuoteRune)
- case runeValue >= '0' && runeValue <= '9':
- // Number
- thisdir := (dir % 9) + 1
- newRune := '0' + (int(runeValue)-'0'+thisdir)%10
- _, _ = result.WriteRune(rune(newRune))
- case (runeValue >= 'A' && runeValue <= 'Z') ||
- (runeValue >= 'a' && runeValue <= 'z'):
- // ASCII letter. Try to avoid trivial A->a mappings
- thisdir := dir%25 + 1
- // Calculate the offset of this character in A-Za-z
- pos := int(runeValue - 'A')
- if pos >= 26 {
- pos -= 6 // It's lower case
- }
- // Rotate the character to the new location
- pos = (pos + thisdir) % 52
- if pos >= 26 {
- pos += 6 // and handle lower case offset again
- }
- _, _ = result.WriteRune(rune('A' + pos))
- case runeValue >= 0xA0 && runeValue <= 0xFF:
- // Latin 1 supplement
- thisdir := (dir % 95) + 1
- newRune := 0xA0 + (int(runeValue)-0xA0+thisdir)%96
- _, _ = result.WriteRune(rune(newRune))
- case runeValue >= 0x100:
- // Some random Unicode range; we have no good rules here
- thisdir := (dir % 127) + 1
- base := int(runeValue - runeValue%256)
- newRune := rune(base + (int(runeValue)-base+thisdir)%256)
- // If the new character isn't a valid UTF8 char
- // then don't rotate it. Quote it instead
- if !utf8.ValidRune(newRune) {
- _, _ = result.WriteRune(obfuscQuoteRune)
- _, _ = result.WriteRune(runeValue)
- } else {
- _, _ = result.WriteRune(newRune)
- }
- default:
- // Leave character untouched
- _, _ = result.WriteRune(runeValue)
- }
- }
- return result.String()
- }
- func (c *Cipher) deobfuscateSegment(ciphertext string) (string, error) {
- if ciphertext == "" {
- return "", nil
- }
- pos := strings.Index(ciphertext, ".")
- if pos == -1 {
- return "", ErrorNotAnEncryptedFile
- } // No .
- num := ciphertext[:pos]
- if num == "!" {
- // No rotation; probably original was not valid unicode
- return ciphertext[pos+1:], nil
- }
- dir, err := strconv.Atoi(num)
- if err != nil {
- return "", ErrorNotAnEncryptedFile // Not a number
- }
- // add the nameKey to get the real rotate distance
- for i := 0; i < len(c.nameKey); i++ {
- dir += int(c.nameKey[i])
- }
- var result bytes.Buffer
- inQuote := false
- for _, runeValue := range ciphertext[pos+1:] {
- switch {
- case inQuote:
- _, _ = result.WriteRune(runeValue)
- inQuote = false
- case runeValue == obfuscQuoteRune:
- inQuote = true
- case runeValue >= '0' && runeValue <= '9':
- // Number
- thisdir := (dir % 9) + 1
- newRune := '0' + int(runeValue) - '0' - thisdir
- if newRune < '0' {
- newRune += 10
- }
- _, _ = result.WriteRune(rune(newRune))
- case (runeValue >= 'A' && runeValue <= 'Z') ||
- (runeValue >= 'a' && runeValue <= 'z'):
- thisdir := dir%25 + 1
- pos := int(runeValue - 'A')
- if pos >= 26 {
- pos -= 6
- }
- pos = pos - thisdir
- if pos < 0 {
- pos += 52
- }
- if pos >= 26 {
- pos += 6
- }
- _, _ = result.WriteRune(rune('A' + pos))
- case runeValue >= 0xA0 && runeValue <= 0xFF:
- thisdir := (dir % 95) + 1
- newRune := 0xA0 + int(runeValue) - 0xA0 - thisdir
- if newRune < 0xA0 {
- newRune += 96
- }
- _, _ = result.WriteRune(rune(newRune))
- case runeValue >= 0x100:
- thisdir := (dir % 127) + 1
- base := int(runeValue - runeValue%256)
- newRune := rune(base + (int(runeValue) - base - thisdir))
- if int(newRune) < base {
- newRune += 256
- }
- _, _ = result.WriteRune(newRune)
- default:
- _, _ = result.WriteRune(runeValue)
- }
- }
- return result.String(), nil
- }
- // encryptFileName encrypts a file path
- func (c *Cipher) encryptFileName(in string) string {
- segments := strings.Split(in, "/")
- for i := range segments {
- // Skip directory name encryption if the user chose to
- // leave them intact
- if !c.dirNameEncrypt && i != (len(segments)-1) {
- continue
- }
- // Strip version string so that only the non-versioned part
- // of the file name gets encrypted/obfuscated
- hasVersion := false
- var t time.Time
- if i == (len(segments)-1) && version.Match(segments[i]) {
- var s string
- t, s = version.Remove(segments[i])
- // version.Remove can fail, in which case it returns segments[i]
- if s != segments[i] {
- segments[i] = s
- hasVersion = true
- }
- }
- if c.mode == NameEncryptionStandard {
- segments[i] = c.encryptSegment(segments[i])
- } else {
- segments[i] = c.obfuscateSegment(segments[i])
- }
- // Add back a version to the encrypted/obfuscated
- // file name, if we stripped it off earlier
- if hasVersion {
- segments[i] = version.Add(segments[i], t)
- }
- }
- return strings.Join(segments, "/")
- }
- // EncryptFileName encrypts a file path
- func (c *Cipher) EncryptFileName(in string) string {
- if c.mode == NameEncryptionOff {
- return in + c.encryptedSuffix
- }
- return c.encryptFileName(in)
- }
- // EncryptDirName encrypts a directory path
- func (c *Cipher) EncryptDirName(in string) string {
- if c.mode == NameEncryptionOff || !c.dirNameEncrypt {
- return in
- }
- return c.encryptFileName(in)
- }
- // decryptFileName decrypts a file path
- func (c *Cipher) decryptFileName(in string) (string, error) {
- segments := strings.Split(in, "/")
- for i := range segments {
- var err error
- // Skip directory name decryption if the user chose to
- // leave them intact
- if !c.dirNameEncrypt && i != (len(segments)-1) {
- continue
- }
- // Strip version string so that only the non-versioned part
- // of the file name gets decrypted/deobfuscated
- hasVersion := false
- var t time.Time
- if i == (len(segments)-1) && version.Match(segments[i]) {
- var s string
- t, s = version.Remove(segments[i])
- // version.Remove can fail, in which case it returns segments[i]
- if s != segments[i] {
- segments[i] = s
- hasVersion = true
- }
- }
- if c.mode == NameEncryptionStandard {
- segments[i], err = c.decryptSegment(segments[i])
- } else {
- segments[i], err = c.deobfuscateSegment(segments[i])
- }
- if err != nil {
- return "", err
- }
- // Add back a version to the decrypted/deobfuscated
- // file name, if we stripped it off earlier
- if hasVersion {
- segments[i] = version.Add(segments[i], t)
- }
- }
- return strings.Join(segments, "/"), nil
- }
- // DecryptFileName decrypts a file path
- func (c *Cipher) DecryptFileName(in string) (string, error) {
- if c.mode == NameEncryptionOff {
- remainingLength := len(in) - len(c.encryptedSuffix)
- if remainingLength == 0 || !strings.HasSuffix(in, c.encryptedSuffix) {
- return "", ErrorNotAnEncryptedFile
- }
- decrypted := in[:remainingLength]
- if version.Match(decrypted) {
- _, unversioned := version.Remove(decrypted)
- if unversioned == "" {
- return "", ErrorNotAnEncryptedFile
- }
- }
- // Leave the version string on, if it was there
- return decrypted, nil
- }
- return c.decryptFileName(in)
- }
- // DecryptDirName decrypts a directory path
- func (c *Cipher) DecryptDirName(in string) (string, error) {
- if c.mode == NameEncryptionOff || !c.dirNameEncrypt {
- return in, nil
- }
- return c.decryptFileName(in)
- }
- // NameEncryptionMode returns the encryption mode in use for names
- func (c *Cipher) NameEncryptionMode() NameEncryptionMode {
- return c.mode
- }
- // nonce is an NACL secretbox nonce
- type nonce [fileNonceSize]byte
- // pointer returns the nonce as a *[24]byte for secretbox
- func (n *nonce) pointer() *[fileNonceSize]byte {
- return (*[fileNonceSize]byte)(n)
- }
- // fromReader fills the nonce from an io.Reader - normally the OSes
- // crypto random number generator
- func (n *nonce) fromReader(in io.Reader) error {
- read, err := readers.ReadFill(in, (*n)[:])
- if read != fileNonceSize {
- return fmt.Errorf("short read of nonce: %w", err)
- }
- return nil
- }
- // fromBuf fills the nonce from the buffer passed in
- func (n *nonce) fromBuf(buf []byte) {
- read := copy((*n)[:], buf)
- if read != fileNonceSize {
- panic("buffer to short to read nonce")
- }
- }
- // carry 1 up the nonce from position i
- func (n *nonce) carry(i int) {
- for ; i < len(*n); i++ {
- digit := (*n)[i]
- newDigit := digit + 1
- (*n)[i] = newDigit
- if newDigit >= digit {
- // exit if no carry
- break
- }
- }
- }
- // increment to add 1 to the nonce
- func (n *nonce) increment() {
- n.carry(0)
- }
- // add a uint64 to the nonce
- func (n *nonce) add(x uint64) {
- carry := uint16(0)
- for i := 0; i < 8; i++ {
- digit := (*n)[i]
- xDigit := byte(x)
- x >>= 8
- carry += uint16(digit) + uint16(xDigit)
- (*n)[i] = byte(carry)
- carry >>= 8
- }
- if carry != 0 {
- n.carry(8)
- }
- }
- // encrypter encrypts an io.Reader on the fly
- type encrypter struct {
- mu sync.Mutex
- in io.Reader
- c *Cipher
- nonce nonce
- buf *[blockSize]byte
- readBuf *[blockSize]byte
- bufIndex int
- bufSize int
- err error
- }
- // newEncrypter creates a new file handle encrypting on the fly
- func (c *Cipher) newEncrypter(in io.Reader, nonce *nonce) (*encrypter, error) {
- fh := &encrypter{
- in: in,
- c: c,
- buf: c.getBlock(),
- readBuf: c.getBlock(),
- bufSize: fileHeaderSize,
- }
- // Initialise nonce
- if nonce != nil {
- fh.nonce = *nonce
- } else {
- err := fh.nonce.fromReader(c.cryptoRand)
- if err != nil {
- return nil, err
- }
- }
- // Copy magic into buffer
- copy((*fh.buf)[:], fileMagicBytes)
- // Copy nonce into buffer
- copy((*fh.buf)[fileMagicSize:], fh.nonce[:])
- return fh, nil
- }
- // Read as per io.Reader
- func (fh *encrypter) Read(p []byte) (n int, err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- if fh.err != nil {
- return 0, fh.err
- }
- if fh.bufIndex >= fh.bufSize {
- // Read data
- // FIXME should overlap the reads with a go-routine and 2 buffers?
- readBuf := (*fh.readBuf)[:blockDataSize]
- n, err = readers.ReadFill(fh.in, readBuf)
- if n == 0 {
- return fh.finish(err)
- }
- // possibly err != nil here, but we will process the
- // data and the next call to ReadFill will return 0, err
- // Encrypt the block using the nonce
- secretbox.Seal((*fh.buf)[:0], readBuf[:n], fh.nonce.pointer(), &fh.c.dataKey)
- fh.bufIndex = 0
- fh.bufSize = blockHeaderSize + n
- fh.nonce.increment()
- }
- n = copy(p, (*fh.buf)[fh.bufIndex:fh.bufSize])
- fh.bufIndex += n
- return n, nil
- }
- // finish sets the final error and tidies up
- func (fh *encrypter) finish(err error) (int, error) {
- if fh.err != nil {
- return 0, fh.err
- }
- fh.err = err
- fh.c.putBlock(fh.buf)
- fh.buf = nil
- fh.c.putBlock(fh.readBuf)
- fh.readBuf = nil
- return 0, err
- }
- // Encrypt data encrypts the data stream
- func (c *Cipher) encryptData(in io.Reader) (io.Reader, *encrypter, error) {
- in, wrap := accounting.UnWrap(in) // unwrap the accounting off the Reader
- out, err := c.newEncrypter(in, nil)
- if err != nil {
- return nil, nil, err
- }
- return wrap(out), out, nil // and wrap the accounting back on
- }
- // EncryptData encrypts the data stream
- func (c *Cipher) EncryptData(in io.Reader) (io.Reader, error) {
- out, _, err := c.encryptData(in)
- return out, err
- }
- // decrypter decrypts an io.ReaderCloser on the fly
- type decrypter struct {
- mu sync.Mutex
- rc io.ReadCloser
- nonce nonce
- initialNonce nonce
- c *Cipher
- buf *[blockSize]byte
- readBuf *[blockSize]byte
- bufIndex int
- bufSize int
- err error
- limit int64 // limit of bytes to read, -1 for unlimited
- open OpenRangeSeek
- }
- // newDecrypter creates a new file handle decrypting on the fly
- func (c *Cipher) newDecrypter(rc io.ReadCloser) (*decrypter, error) {
- fh := &decrypter{
- rc: rc,
- c: c,
- buf: c.getBlock(),
- readBuf: c.getBlock(),
- limit: -1,
- }
- // Read file header (magic + nonce)
- readBuf := (*fh.readBuf)[:fileHeaderSize]
- n, err := readers.ReadFill(fh.rc, readBuf)
- if n < fileHeaderSize && err == io.EOF {
- // This read from 0..fileHeaderSize-1 bytes
- return nil, fh.finishAndClose(ErrorEncryptedFileTooShort)
- } else if err != io.EOF && err != nil {
- return nil, fh.finishAndClose(err)
- }
- // check the magic
- if !bytes.Equal(readBuf[:fileMagicSize], fileMagicBytes) {
- return nil, fh.finishAndClose(ErrorEncryptedBadMagic)
- }
- // retrieve the nonce
- fh.nonce.fromBuf(readBuf[fileMagicSize:])
- fh.initialNonce = fh.nonce
- return fh, nil
- }
- // newDecrypterSeek creates a new file handle decrypting on the fly
- func (c *Cipher) newDecrypterSeek(ctx context.Context, open OpenRangeSeek, offset, limit int64) (fh *decrypter, err error) {
- var rc io.ReadCloser
- doRangeSeek := false
- setLimit := false
- // Open initially with no seek
- if offset == 0 && limit < 0 {
- // If no offset or limit then open whole file
- rc, err = open(ctx, 0, -1)
- } else if offset == 0 {
- // If no offset open the header + limit worth of the file
- _, underlyingLimit, _, _ := calculateUnderlying(offset, limit)
- rc, err = open(ctx, 0, int64(fileHeaderSize)+underlyingLimit)
- setLimit = true
- } else {
- // Otherwise just read the header to start with
- rc, err = open(ctx, 0, int64(fileHeaderSize))
- doRangeSeek = true
- }
- if err != nil {
- return nil, err
- }
- // Open the stream which fills in the nonce
- fh, err = c.newDecrypter(rc)
- if err != nil {
- return nil, err
- }
- fh.open = open // will be called by fh.RangeSeek
- if doRangeSeek {
- _, err = fh.RangeSeek(ctx, offset, io.SeekStart, limit)
- if err != nil {
- _ = fh.Close()
- return nil, err
- }
- }
- if setLimit {
- fh.limit = limit
- }
- return fh, nil
- }
- // read data into internal buffer - call with fh.mu held
- func (fh *decrypter) fillBuffer() (err error) {
- // FIXME should overlap the reads with a go-routine and 2 buffers?
- readBuf := fh.readBuf
- n, err := readers.ReadFill(fh.rc, (*readBuf)[:])
- if n == 0 {
- return err
- }
- // possibly err != nil here, but we will process the data and
- // the next call to ReadFull will return 0, err
- // Check header + 1 byte exists
- if n <= blockHeaderSize {
- if err != nil && err != io.EOF {
- return err // return pending error as it is likely more accurate
- }
- return ErrorEncryptedFileBadHeader
- }
- // Decrypt the block using the nonce
- _, ok := secretbox.Open((*fh.buf)[:0], (*readBuf)[:n], fh.nonce.pointer(), &fh.c.dataKey)
- if !ok {
- if err != nil && err != io.EOF {
- return err // return pending error as it is likely more accurate
- }
- if !fh.c.passBadBlocks {
- return ErrorEncryptedBadBlock
- }
- fs.Errorf(nil, "crypt: ignoring: %v", ErrorEncryptedBadBlock)
- // Zero out the bad block and continue
- for i := range (*fh.buf)[:n] {
- (*fh.buf)[i] = 0
- }
- }
- fh.bufIndex = 0
- fh.bufSize = n - blockHeaderSize
- fh.nonce.increment()
- return nil
- }
- // Read as per io.Reader
- func (fh *decrypter) Read(p []byte) (n int, err error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- if fh.err != nil {
- return 0, fh.err
- }
- if fh.bufIndex >= fh.bufSize {
- err = fh.fillBuffer()
- if err != nil {
- return 0, fh.finish(err)
- }
- }
- toCopy := fh.bufSize - fh.bufIndex
- if fh.limit >= 0 && fh.limit < int64(toCopy) {
- toCopy = int(fh.limit)
- }
- n = copy(p, (*fh.buf)[fh.bufIndex:fh.bufIndex+toCopy])
- fh.bufIndex += n
- if fh.limit >= 0 {
- fh.limit -= int64(n)
- if fh.limit == 0 {
- return n, fh.finish(io.EOF)
- }
- }
- return n, nil
- }
- // calculateUnderlying converts an (offset, limit) in an encrypted file
- // into an (underlyingOffset, underlyingLimit) for the underlying file.
- //
- // It also returns number of bytes to discard after reading the first
- // block and number of blocks this is from the start so the nonce can
- // be incremented.
- func calculateUnderlying(offset, limit int64) (underlyingOffset, underlyingLimit, discard, blocks int64) {
- // blocks we need to seek, plus bytes we need to discard
- blocks, discard = offset/blockDataSize, offset%blockDataSize
- // Offset in underlying stream we need to seek
- underlyingOffset = int64(fileHeaderSize) + blocks*(blockHeaderSize+blockDataSize)
- // work out how many blocks we need to read
- underlyingLimit = int64(-1)
- if limit >= 0 {
- // bytes to read beyond the first block
- bytesToRead := limit - (blockDataSize - discard)
- // Read the first block
- blocksToRead := int64(1)
- if bytesToRead > 0 {
- // Blocks that need to be read plus left over blocks
- extraBlocksToRead, endBytes := bytesToRead/blockDataSize, bytesToRead%blockDataSize
- if endBytes != 0 {
- // If left over bytes must read another block
- extraBlocksToRead++
- }
- blocksToRead += extraBlocksToRead
- }
- // Must read a whole number of blocks
- underlyingLimit = blocksToRead * (blockHeaderSize + blockDataSize)
- }
- return
- }
- // RangeSeek behaves like a call to Seek(offset int64, whence
- // int) with the output wrapped in an io.LimitedReader
- // limiting the total length to limit.
- //
- // RangeSeek with a limit of < 0 is equivalent to a regular Seek.
- func (fh *decrypter) RangeSeek(ctx context.Context, offset int64, whence int, limit int64) (int64, error) {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- if fh.open == nil {
- return 0, fh.finish(errors.New("can't seek - not initialised with newDecrypterSeek"))
- }
- if whence != io.SeekStart {
- return 0, fh.finish(errors.New("can only seek from the start"))
- }
- // Reset error or return it if not EOF
- if fh.err == io.EOF {
- fh.unFinish()
- } else if fh.err != nil {
- return 0, fh.err
- }
- underlyingOffset, underlyingLimit, discard, blocks := calculateUnderlying(offset, limit)
- // Move the nonce on the correct number of blocks from the start
- fh.nonce = fh.initialNonce
- fh.nonce.add(uint64(blocks))
- // Can we seek underlying stream directly?
- if do, ok := fh.rc.(fs.RangeSeeker); ok {
- // Seek underlying stream directly
- _, err := do.RangeSeek(ctx, underlyingOffset, 0, underlyingLimit)
- if err != nil {
- return 0, fh.finish(err)
- }
- } else {
- // if not reopen with seek
- _ = fh.rc.Close() // close underlying file
- fh.rc = nil
- // Re-open the underlying object with the offset given
- rc, err := fh.open(ctx, underlyingOffset, underlyingLimit)
- if err != nil {
- return 0, fh.finish(fmt.Errorf("couldn't reopen file with offset and limit: %w", err))
- }
- // Set the file handle
- fh.rc = rc
- }
- // Fill the buffer
- err := fh.fillBuffer()
- if err != nil {
- return 0, fh.finish(err)
- }
- // Discard bytes from the buffer
- if int(discard) > fh.bufSize {
- return 0, fh.finish(ErrorBadSeek)
- }
- fh.bufIndex = int(discard)
- // Set the limit
- fh.limit = limit
- return offset, nil
- }
- // Seek implements the io.Seeker interface
- func (fh *decrypter) Seek(offset int64, whence int) (int64, error) {
- return fh.RangeSeek(context.TODO(), offset, whence, -1)
- }
- // finish sets the final error and tidies up
- func (fh *decrypter) finish(err error) error {
- if fh.err != nil {
- return fh.err
- }
- fh.err = err
- fh.c.putBlock(fh.buf)
- fh.buf = nil
- fh.c.putBlock(fh.readBuf)
- fh.readBuf = nil
- return err
- }
- // unFinish undoes the effects of finish
- func (fh *decrypter) unFinish() {
- // Clear error
- fh.err = nil
- // reinstate the buffers
- fh.buf = fh.c.getBlock()
- fh.readBuf = fh.c.getBlock()
- // Empty the buffer
- fh.bufIndex = 0
- fh.bufSize = 0
- }
- // Close
- func (fh *decrypter) Close() error {
- fh.mu.Lock()
- defer fh.mu.Unlock()
- // Check already closed
- if fh.err == ErrorFileClosed {
- return fh.err
- }
- // Closed before reading EOF so not finish()ed yet
- if fh.err == nil {
- _ = fh.finish(io.EOF)
- }
- // Show file now closed
- fh.err = ErrorFileClosed
- if fh.rc == nil {
- return nil
- }
- return fh.rc.Close()
- }
- // finishAndClose does finish then Close()
- //
- // Used when we are returning a nil fh from new
- func (fh *decrypter) finishAndClose(err error) error {
- _ = fh.finish(err)
- _ = fh.Close()
- return err
- }
- // DecryptData decrypts the data stream
- func (c *Cipher) DecryptData(rc io.ReadCloser) (io.ReadCloser, error) {
- out, err := c.newDecrypter(rc)
- if err != nil {
- return nil, err
- }
- return out, nil
- }
- // DecryptDataSeek decrypts the data stream from offset
- //
- // The open function must return a ReadCloser opened to the offset supplied.
- //
- // You must use this form of DecryptData if you might want to Seek the file handle
- func (c *Cipher) DecryptDataSeek(ctx context.Context, open OpenRangeSeek, offset, limit int64) (ReadSeekCloser, error) {
- out, err := c.newDecrypterSeek(ctx, open, offset, limit)
- if err != nil {
- return nil, err
- }
- return out, nil
- }
- // EncryptedSize calculates the size of the data when encrypted
- func (c *Cipher) EncryptedSize(size int64) int64 {
- blocks, residue := size/blockDataSize, size%blockDataSize
- encryptedSize := int64(fileHeaderSize) + blocks*(blockHeaderSize+blockDataSize)
- if residue != 0 {
- encryptedSize += blockHeaderSize + residue
- }
- return encryptedSize
- }
- // DecryptedSize calculates the size of the data when decrypted
- func (c *Cipher) DecryptedSize(size int64) (int64, error) {
- size -= int64(fileHeaderSize)
- if size < 0 {
- return 0, ErrorEncryptedFileTooShort
- }
- blocks, residue := size/blockSize, size%blockSize
- decryptedSize := blocks * blockDataSize
- if residue != 0 {
- residue -= blockHeaderSize
- if residue <= 0 {
- return 0, ErrorEncryptedFileBadHeader
- }
- }
- decryptedSize += residue
- return decryptedSize, nil
- }
- // check interfaces
- var (
- _ io.ReadCloser = (*decrypter)(nil)
- _ io.Seeker = (*decrypter)(nil)
- _ fs.RangeSeeker = (*decrypter)(nil)
- _ io.Reader = (*encrypter)(nil)
- )
|