crypt: support timestamped filenames from --b2-versions
With the file version format standardized in lib/version, `crypt` can now treat the version strings separately from the encrypted/decrypted file names. This allows --b2-versions to work with `crypt`. Fixes #1627 Co-authored-by: Luc Ritchie <luc.ritchie@gmail.com>
This commit is contained in:
parent
c163e6b250
commit
3fe2aaf96c
3 changed files with 67 additions and 8 deletions
|
@ -12,12 +12,14 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rclone/rclone/backend/crypt/pkcs7"
|
"github.com/rclone/rclone/backend/crypt/pkcs7"
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/accounting"
|
"github.com/rclone/rclone/fs/accounting"
|
||||||
|
"github.com/rclone/rclone/lib/version"
|
||||||
"github.com/rfjakob/eme"
|
"github.com/rfjakob/eme"
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
|
@ -442,11 +444,32 @@ func (c *Cipher) encryptFileName(in string) string {
|
||||||
if !c.dirNameEncrypt && i != (len(segments)-1) {
|
if !c.dirNameEncrypt && i != (len(segments)-1) {
|
||||||
continue
|
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 {
|
if c.mode == NameEncryptionStandard {
|
||||||
segments[i] = c.encryptSegment(segments[i])
|
segments[i] = c.encryptSegment(segments[i])
|
||||||
} else {
|
} else {
|
||||||
segments[i] = c.obfuscateSegment(segments[i])
|
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, "/")
|
return strings.Join(segments, "/")
|
||||||
}
|
}
|
||||||
|
@ -477,6 +500,21 @@ func (c *Cipher) decryptFileName(in string) (string, error) {
|
||||||
if !c.dirNameEncrypt && i != (len(segments)-1) {
|
if !c.dirNameEncrypt && i != (len(segments)-1) {
|
||||||
continue
|
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 {
|
if c.mode == NameEncryptionStandard {
|
||||||
segments[i], err = c.decryptSegment(segments[i])
|
segments[i], err = c.decryptSegment(segments[i])
|
||||||
} else {
|
} else {
|
||||||
|
@ -486,6 +524,12 @@ func (c *Cipher) decryptFileName(in string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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
|
return strings.Join(segments, "/"), nil
|
||||||
}
|
}
|
||||||
|
@ -494,10 +538,18 @@ func (c *Cipher) decryptFileName(in string) (string, error) {
|
||||||
func (c *Cipher) DecryptFileName(in string) (string, error) {
|
func (c *Cipher) DecryptFileName(in string) (string, error) {
|
||||||
if c.mode == NameEncryptionOff {
|
if c.mode == NameEncryptionOff {
|
||||||
remainingLength := len(in) - len(encryptedSuffix)
|
remainingLength := len(in) - len(encryptedSuffix)
|
||||||
if remainingLength > 0 && strings.HasSuffix(in, encryptedSuffix) {
|
if remainingLength == 0 || !strings.HasSuffix(in, encryptedSuffix) {
|
||||||
return in[:remainingLength], nil
|
return "", ErrorNotAnEncryptedFile
|
||||||
}
|
}
|
||||||
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)
|
return c.decryptFileName(in)
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,22 +160,29 @@ func TestEncryptFileName(t *testing.T) {
|
||||||
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s", c.EncryptFileName("1"))
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s", c.EncryptFileName("1"))
|
||||||
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng", c.EncryptFileName("1/12"))
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng", c.EncryptFileName("1/12"))
|
||||||
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", c.EncryptFileName("1/12/123"))
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", c.EncryptFileName("1/12/123"))
|
||||||
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s-v2001-02-03-040506-123", c.EncryptFileName("1-v2001-02-03-040506-123"))
|
||||||
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng-v2001-02-03-040506-123", c.EncryptFileName("1/12-v2001-02-03-040506-123"))
|
||||||
// Standard mode with directory name encryption off
|
// Standard mode with directory name encryption off
|
||||||
c, _ = newCipher(NameEncryptionStandard, "", "", false)
|
c, _ = newCipher(NameEncryptionStandard, "", "", false)
|
||||||
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s", c.EncryptFileName("1"))
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s", c.EncryptFileName("1"))
|
||||||
assert.Equal(t, "1/l42g6771hnv3an9cgc8cr2n1ng", c.EncryptFileName("1/12"))
|
assert.Equal(t, "1/l42g6771hnv3an9cgc8cr2n1ng", c.EncryptFileName("1/12"))
|
||||||
assert.Equal(t, "1/12/qgm4avr35m5loi1th53ato71v0", c.EncryptFileName("1/12/123"))
|
assert.Equal(t, "1/12/qgm4avr35m5loi1th53ato71v0", c.EncryptFileName("1/12/123"))
|
||||||
|
assert.Equal(t, "p0e52nreeaj0a5ea7s64m4j72s-v2001-02-03-040506-123", c.EncryptFileName("1-v2001-02-03-040506-123"))
|
||||||
|
assert.Equal(t, "1/l42g6771hnv3an9cgc8cr2n1ng-v2001-02-03-040506-123", c.EncryptFileName("1/12-v2001-02-03-040506-123"))
|
||||||
// Now off mode
|
// Now off mode
|
||||||
c, _ = newCipher(NameEncryptionOff, "", "", true)
|
c, _ = newCipher(NameEncryptionOff, "", "", true)
|
||||||
assert.Equal(t, "1/12/123.bin", c.EncryptFileName("1/12/123"))
|
assert.Equal(t, "1/12/123.bin", c.EncryptFileName("1/12/123"))
|
||||||
// Obfuscation mode
|
// Obfuscation mode
|
||||||
c, _ = newCipher(NameEncryptionObfuscated, "", "", true)
|
c, _ = newCipher(NameEncryptionObfuscated, "", "", true)
|
||||||
assert.Equal(t, "49.6/99.23/150.890/53.!!lipps", c.EncryptFileName("1/12/123/!hello"))
|
assert.Equal(t, "49.6/99.23/150.890/53.!!lipps", c.EncryptFileName("1/12/123/!hello"))
|
||||||
|
assert.Equal(t, "49.6/99.23/150.890/53-v2001-02-03-040506-123.!!lipps", c.EncryptFileName("1/12/123/!hello-v2001-02-03-040506-123"))
|
||||||
|
assert.Equal(t, "49.6/99.23/150.890/162.uryyB-v2001-02-03-040506-123.GKG", c.EncryptFileName("1/12/123/hello-v2001-02-03-040506-123.txt"))
|
||||||
assert.Equal(t, "161.\u00e4", c.EncryptFileName("\u00a1"))
|
assert.Equal(t, "161.\u00e4", c.EncryptFileName("\u00a1"))
|
||||||
assert.Equal(t, "160.\u03c2", c.EncryptFileName("\u03a0"))
|
assert.Equal(t, "160.\u03c2", c.EncryptFileName("\u03a0"))
|
||||||
// Obfuscation mode with directory name encryption off
|
// Obfuscation mode with directory name encryption off
|
||||||
c, _ = newCipher(NameEncryptionObfuscated, "", "", false)
|
c, _ = newCipher(NameEncryptionObfuscated, "", "", false)
|
||||||
assert.Equal(t, "1/12/123/53.!!lipps", c.EncryptFileName("1/12/123/!hello"))
|
assert.Equal(t, "1/12/123/53.!!lipps", c.EncryptFileName("1/12/123/!hello"))
|
||||||
|
assert.Equal(t, "1/12/123/53-v2001-02-03-040506-123.!!lipps", c.EncryptFileName("1/12/123/!hello-v2001-02-03-040506-123"))
|
||||||
assert.Equal(t, "161.\u00e4", c.EncryptFileName("\u00a1"))
|
assert.Equal(t, "161.\u00e4", c.EncryptFileName("\u00a1"))
|
||||||
assert.Equal(t, "160.\u03c2", c.EncryptFileName("\u03a0"))
|
assert.Equal(t, "160.\u03c2", c.EncryptFileName("\u03a0"))
|
||||||
}
|
}
|
||||||
|
@ -194,14 +201,19 @@ func TestDecryptFileName(t *testing.T) {
|
||||||
{NameEncryptionStandard, true, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", "1/12/123", nil},
|
{NameEncryptionStandard, true, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1ng/qgm4avr35m5loi1th53ato71v0", "1/12/123", nil},
|
||||||
{NameEncryptionStandard, true, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1/qgm4avr35m5loi1th53ato71v0", "", ErrorNotAMultipleOfBlocksize},
|
{NameEncryptionStandard, true, "p0e52nreeaj0a5ea7s64m4j72s/l42g6771hnv3an9cgc8cr2n1/qgm4avr35m5loi1th53ato71v0", "", ErrorNotAMultipleOfBlocksize},
|
||||||
{NameEncryptionStandard, false, "1/12/qgm4avr35m5loi1th53ato71v0", "1/12/123", nil},
|
{NameEncryptionStandard, false, "1/12/qgm4avr35m5loi1th53ato71v0", "1/12/123", nil},
|
||||||
|
{NameEncryptionStandard, true, "p0e52nreeaj0a5ea7s64m4j72s-v2001-02-03-040506-123", "1-v2001-02-03-040506-123", nil},
|
||||||
{NameEncryptionOff, true, "1/12/123.bin", "1/12/123", nil},
|
{NameEncryptionOff, true, "1/12/123.bin", "1/12/123", nil},
|
||||||
{NameEncryptionOff, true, "1/12/123.bix", "", ErrorNotAnEncryptedFile},
|
{NameEncryptionOff, true, "1/12/123.bix", "", ErrorNotAnEncryptedFile},
|
||||||
{NameEncryptionOff, true, ".bin", "", ErrorNotAnEncryptedFile},
|
{NameEncryptionOff, true, ".bin", "", ErrorNotAnEncryptedFile},
|
||||||
|
{NameEncryptionOff, true, "1/12/123-v2001-02-03-040506-123.bin", "1/12/123-v2001-02-03-040506-123", nil},
|
||||||
|
{NameEncryptionOff, true, "1/12/123-v1970-01-01-010101-123-v2001-02-03-040506-123.bin", "1/12/123-v1970-01-01-010101-123-v2001-02-03-040506-123", nil},
|
||||||
|
{NameEncryptionOff, true, "1/12/123-v1970-01-01-010101-123-v2001-02-03-040506-123.txt.bin", "1/12/123-v1970-01-01-010101-123-v2001-02-03-040506-123.txt", nil},
|
||||||
{NameEncryptionObfuscated, true, "!.hello", "hello", nil},
|
{NameEncryptionObfuscated, true, "!.hello", "hello", nil},
|
||||||
{NameEncryptionObfuscated, true, "hello", "", ErrorNotAnEncryptedFile},
|
{NameEncryptionObfuscated, true, "hello", "", ErrorNotAnEncryptedFile},
|
||||||
{NameEncryptionObfuscated, true, "161.\u00e4", "\u00a1", nil},
|
{NameEncryptionObfuscated, true, "161.\u00e4", "\u00a1", nil},
|
||||||
{NameEncryptionObfuscated, true, "160.\u03c2", "\u03a0", nil},
|
{NameEncryptionObfuscated, true, "160.\u03c2", "\u03a0", nil},
|
||||||
{NameEncryptionObfuscated, false, "1/12/123/53.!!lipps", "1/12/123/!hello", nil},
|
{NameEncryptionObfuscated, false, "1/12/123/53.!!lipps", "1/12/123/!hello", nil},
|
||||||
|
{NameEncryptionObfuscated, false, "1/12/123/53-v2001-02-03-040506-123.!!lipps", "1/12/123/!hello-v2001-02-03-040506-123", nil},
|
||||||
} {
|
} {
|
||||||
c, _ := newCipher(test.mode, "", "", test.dirNameEncrypt)
|
c, _ := newCipher(test.mode, "", "", test.dirNameEncrypt)
|
||||||
actual, actualErr := c.DecryptFileName(test.in)
|
actual, actualErr := c.DecryptFileName(test.in)
|
||||||
|
|
|
@ -172,11 +172,6 @@ the file instead of hiding it.
|
||||||
Old versions of files, where available, are visible using the
|
Old versions of files, where available, are visible using the
|
||||||
`--b2-versions` flag.
|
`--b2-versions` flag.
|
||||||
|
|
||||||
**NB** Note that `--b2-versions` does not work with crypt at the
|
|
||||||
moment [#1627](https://github.com/rclone/rclone/issues/1627). Using
|
|
||||||
[--backup-dir](/docs/#backup-dir-dir) with rclone is the recommended
|
|
||||||
way of working around this.
|
|
||||||
|
|
||||||
If you wish to remove all the old versions then you can use the
|
If you wish to remove all the old versions then you can use the
|
||||||
`rclone cleanup remote:bucket` command which will delete all the old
|
`rclone cleanup remote:bucket` command which will delete all the old
|
||||||
versions of files, leaving the current ones intact. You can also
|
versions of files, leaving the current ones intact. You can also
|
||||||
|
|
Loading…
Reference in a new issue