crypt: add suffix option to set a custom suffix for encrypted files - fixes #6392
This commit is contained in:
parent
589b7b4873
commit
281a007b1a
3 changed files with 70 additions and 32 deletions
|
@ -38,7 +38,6 @@ const (
|
||||||
blockHeaderSize = secretbox.Overhead
|
blockHeaderSize = secretbox.Overhead
|
||||||
blockDataSize = 64 * 1024
|
blockDataSize = 64 * 1024
|
||||||
blockSize = blockHeaderSize + blockDataSize
|
blockSize = blockHeaderSize + blockDataSize
|
||||||
encryptedSuffix = ".bin" // when file name encryption is off we add this suffix to make sure the cloud provider doesn't process the file
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors returned by cipher
|
// Errors returned by cipher
|
||||||
|
@ -54,8 +53,9 @@ var (
|
||||||
ErrorEncryptedBadBlock = errors.New("failed to authenticate decrypted block - bad password?")
|
ErrorEncryptedBadBlock = errors.New("failed to authenticate decrypted block - bad password?")
|
||||||
ErrorBadBase32Encoding = errors.New("bad base32 filename encoding")
|
ErrorBadBase32Encoding = errors.New("bad base32 filename encoding")
|
||||||
ErrorFileClosed = errors.New("file already closed")
|
ErrorFileClosed = errors.New("file already closed")
|
||||||
ErrorNotAnEncryptedFile = errors.New("not an encrypted file - no \"" + encryptedSuffix + "\" suffix")
|
ErrorNotAnEncryptedFile = errors.New("not an encrypted file - does not match suffix")
|
||||||
ErrorBadSeek = errors.New("Seek beyond end of file")
|
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}
|
defaultSalt = []byte{0xA8, 0x0D, 0xF4, 0x3A, 0x8F, 0xBD, 0x03, 0x08, 0xA7, 0xCA, 0xB8, 0x3E, 0x58, 0x1F, 0x86, 0xB1}
|
||||||
obfuscQuoteRune = '!'
|
obfuscQuoteRune = '!'
|
||||||
)
|
)
|
||||||
|
@ -170,25 +170,27 @@ func NewNameEncoding(s string) (enc fileNameEncoding, err error) {
|
||||||
|
|
||||||
// Cipher defines an encoding and decoding cipher for the crypt backend
|
// Cipher defines an encoding and decoding cipher for the crypt backend
|
||||||
type Cipher struct {
|
type Cipher struct {
|
||||||
dataKey [32]byte // Key for secretbox
|
dataKey [32]byte // Key for secretbox
|
||||||
nameKey [32]byte // 16,24 or 32 bytes
|
nameKey [32]byte // 16,24 or 32 bytes
|
||||||
nameTweak [nameCipherBlockSize]byte // used to tweak the name crypto
|
nameTweak [nameCipherBlockSize]byte // used to tweak the name crypto
|
||||||
block gocipher.Block
|
block gocipher.Block
|
||||||
mode NameEncryptionMode
|
mode NameEncryptionMode
|
||||||
fileNameEnc fileNameEncoding
|
fileNameEnc fileNameEncoding
|
||||||
buffers sync.Pool // encrypt/decrypt buffers
|
buffers sync.Pool // encrypt/decrypt buffers
|
||||||
cryptoRand io.Reader // read crypto random numbers from here
|
cryptoRand io.Reader // read crypto random numbers from here
|
||||||
dirNameEncrypt bool
|
dirNameEncrypt bool
|
||||||
passBadBlocks bool // if set passed bad blocks as zeroed blocks
|
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
|
// 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) {
|
func newCipher(mode NameEncryptionMode, password, salt string, dirNameEncrypt bool, enc fileNameEncoding) (*Cipher, error) {
|
||||||
c := &Cipher{
|
c := &Cipher{
|
||||||
mode: mode,
|
mode: mode,
|
||||||
fileNameEnc: enc,
|
fileNameEnc: enc,
|
||||||
cryptoRand: rand.Reader,
|
cryptoRand: rand.Reader,
|
||||||
dirNameEncrypt: dirNameEncrypt,
|
dirNameEncrypt: dirNameEncrypt,
|
||||||
|
encryptedSuffix: ".bin",
|
||||||
}
|
}
|
||||||
c.buffers.New = func() interface{} {
|
c.buffers.New = func() interface{} {
|
||||||
return new([blockSize]byte)
|
return new([blockSize]byte)
|
||||||
|
@ -200,6 +202,19 @@ func newCipher(mode NameEncryptionMode, password, salt string, dirNameEncrypt bo
|
||||||
return c, nil
|
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
|
// Call to set bad block pass through
|
||||||
func (c *Cipher) setPassBadBlocks(passBadBlocks bool) {
|
func (c *Cipher) setPassBadBlocks(passBadBlocks bool) {
|
||||||
c.passBadBlocks = passBadBlocks
|
c.passBadBlocks = passBadBlocks
|
||||||
|
@ -512,7 +527,7 @@ func (c *Cipher) encryptFileName(in string) string {
|
||||||
// EncryptFileName encrypts a file path
|
// EncryptFileName encrypts a file path
|
||||||
func (c *Cipher) EncryptFileName(in string) string {
|
func (c *Cipher) EncryptFileName(in string) string {
|
||||||
if c.mode == NameEncryptionOff {
|
if c.mode == NameEncryptionOff {
|
||||||
return in + encryptedSuffix
|
return in + c.encryptedSuffix
|
||||||
}
|
}
|
||||||
return c.encryptFileName(in)
|
return c.encryptFileName(in)
|
||||||
}
|
}
|
||||||
|
@ -572,8 +587,8 @@ func (c *Cipher) decryptFileName(in string) (string, error) {
|
||||||
// DecryptFileName decrypts a file path
|
// DecryptFileName decrypts a file path
|
||||||
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(c.encryptedSuffix)
|
||||||
if remainingLength == 0 || !strings.HasSuffix(in, encryptedSuffix) {
|
if remainingLength == 0 || !strings.HasSuffix(in, c.encryptedSuffix) {
|
||||||
return "", ErrorNotAnEncryptedFile
|
return "", ErrorNotAnEncryptedFile
|
||||||
}
|
}
|
||||||
decrypted := in[:remainingLength]
|
decrypted := in[:remainingLength]
|
||||||
|
|
|
@ -405,6 +405,13 @@ func TestNonStandardEncryptFileName(t *testing.T) {
|
||||||
// Off mode
|
// Off mode
|
||||||
c, _ := newCipher(NameEncryptionOff, "", "", true, nil)
|
c, _ := newCipher(NameEncryptionOff, "", "", true, nil)
|
||||||
assert.Equal(t, "1/12/123.bin", c.EncryptFileName("1/12/123"))
|
assert.Equal(t, "1/12/123.bin", c.EncryptFileName("1/12/123"))
|
||||||
|
// Off mode with custom suffix
|
||||||
|
c, _ = newCipher(NameEncryptionOff, "", "", true, nil)
|
||||||
|
c.setEncryptedSuffix(".jpg")
|
||||||
|
assert.Equal(t, "1/12/123.jpg", c.EncryptFileName("1/12/123"))
|
||||||
|
// Off mode with empty suffix
|
||||||
|
c.setEncryptedSuffix("none")
|
||||||
|
assert.Equal(t, "1/12/123", c.EncryptFileName("1/12/123"))
|
||||||
// Obfuscation mode
|
// Obfuscation mode
|
||||||
c, _ = newCipher(NameEncryptionObfuscated, "", "", true, nil)
|
c, _ = newCipher(NameEncryptionObfuscated, "", "", true, nil)
|
||||||
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"))
|
||||||
|
@ -483,21 +490,27 @@ func TestNonStandardDecryptFileName(t *testing.T) {
|
||||||
in string
|
in string
|
||||||
expected string
|
expected string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
|
customSuffix string
|
||||||
}{
|
}{
|
||||||
{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-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.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},
|
{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},
|
{NameEncryptionOff, true, "1/12/123.jpg", "1/12/123", nil, ".jpg"},
|
||||||
{NameEncryptionObfuscated, true, "hello", "", ErrorNotAnEncryptedFile},
|
{NameEncryptionOff, true, "1/12/123", "1/12/123", nil, "none"},
|
||||||
{NameEncryptionObfuscated, true, "161.\u00e4", "\u00a1", nil},
|
{NameEncryptionObfuscated, true, "!.hello", "hello", nil, ""},
|
||||||
{NameEncryptionObfuscated, true, "160.\u03c2", "\u03a0", nil},
|
{NameEncryptionObfuscated, true, "hello", "", ErrorNotAnEncryptedFile, ""},
|
||||||
{NameEncryptionObfuscated, false, "1/12/123/53.!!lipps", "1/12/123/!hello", nil},
|
{NameEncryptionObfuscated, true, "161.\u00e4", "\u00a1", nil, ""},
|
||||||
{NameEncryptionObfuscated, false, "1/12/123/53-v2001-02-03-040506-123.!!lipps", "1/12/123/!hello-v2001-02-03-040506-123", 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-v2001-02-03-040506-123.!!lipps", "1/12/123/!hello-v2001-02-03-040506-123", nil, ""},
|
||||||
} {
|
} {
|
||||||
c, _ := newCipher(test.mode, "", "", test.dirNameEncrypt, enc)
|
c, _ := newCipher(test.mode, "", "", test.dirNameEncrypt, enc)
|
||||||
|
if test.customSuffix != "" {
|
||||||
|
c.setEncryptedSuffix(test.customSuffix)
|
||||||
|
}
|
||||||
actual, actualErr := c.DecryptFileName(test.in)
|
actual, actualErr := c.DecryptFileName(test.in)
|
||||||
what := fmt.Sprintf("Testing %q (mode=%v)", test.in, test.mode)
|
what := fmt.Sprintf("Testing %q (mode=%v)", test.in, test.mode)
|
||||||
assert.Equal(t, test.expected, actual, what)
|
assert.Equal(t, test.expected, actual, what)
|
||||||
|
|
|
@ -48,7 +48,7 @@ func init() {
|
||||||
Help: "Very simple filename obfuscation.",
|
Help: "Very simple filename obfuscation.",
|
||||||
}, {
|
}, {
|
||||||
Value: "off",
|
Value: "off",
|
||||||
Help: "Don't encrypt the file names.\nAdds a \".bin\" extension only.",
|
Help: "Don't encrypt the file names.\nAdds a \".bin\", or \"suffix\" extension only.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
@ -151,6 +151,14 @@ length and if it's case sensitive.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: "suffix",
|
||||||
|
Help: `If this is set it will override the default suffix of ".bin".
|
||||||
|
|
||||||
|
Setting suffix to "none" will result in an empty suffix. This may be useful
|
||||||
|
when the path length is critical.`,
|
||||||
|
Default: ".bin",
|
||||||
|
Advanced: true,
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -183,6 +191,7 @@ func newCipherForConfig(opt *Options) (*Cipher, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to make cipher: %w", err)
|
return nil, fmt.Errorf("failed to make cipher: %w", err)
|
||||||
}
|
}
|
||||||
|
cipher.setEncryptedSuffix(opt.Suffix)
|
||||||
cipher.setPassBadBlocks(opt.PassBadBlocks)
|
cipher.setPassBadBlocks(opt.PassBadBlocks)
|
||||||
return cipher, nil
|
return cipher, nil
|
||||||
}
|
}
|
||||||
|
@ -274,6 +283,7 @@ type Options struct {
|
||||||
ShowMapping bool `config:"show_mapping"`
|
ShowMapping bool `config:"show_mapping"`
|
||||||
PassBadBlocks bool `config:"pass_bad_blocks"`
|
PassBadBlocks bool `config:"pass_bad_blocks"`
|
||||||
FilenameEncoding string `config:"filename_encoding"`
|
FilenameEncoding string `config:"filename_encoding"`
|
||||||
|
Suffix string `config:"suffix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fs represents a wrapped fs.Fs
|
// Fs represents a wrapped fs.Fs
|
||||||
|
|
Loading…
Reference in a new issue