libgocryptfs: update gocryptfs
This commit is contained in:
commit
39af24d4e6
@ -55,7 +55,7 @@ type ConfFile struct {
|
|||||||
// stored in the superblock.
|
// stored in the superblock.
|
||||||
FeatureFlags []string
|
FeatureFlags []string
|
||||||
// FIDO2 parameters
|
// FIDO2 parameters
|
||||||
FIDO2 FIDO2Params
|
FIDO2 *FIDO2Params `json:",omitempty"`
|
||||||
// Filename is the name of the config file. Not exported to JSON.
|
// Filename is the name of the config file. Not exported to JSON.
|
||||||
filename string
|
filename string
|
||||||
}
|
}
|
||||||
@ -101,8 +101,10 @@ func Create(filename string, password []byte, plaintextNames bool,
|
|||||||
}
|
}
|
||||||
if len(fido2CredentialID) > 0 {
|
if len(fido2CredentialID) > 0 {
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2])
|
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2])
|
||||||
cf.FIDO2.CredentialID = fido2CredentialID
|
cf.FIDO2 = &FIDO2Params{
|
||||||
cf.FIDO2.HMACSalt = fido2HmacSalt
|
CredentialID: fido2CredentialID,
|
||||||
|
HMACSalt: fido2HmacSalt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Generate new random master key
|
// Generate new random master key
|
||||||
|
@ -35,6 +35,19 @@ const (
|
|||||||
BackendAESSIV AEADTypeEnum = 5
|
BackendAESSIV AEADTypeEnum = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (a AEADTypeEnum) String() string {
|
||||||
|
switch a {
|
||||||
|
case BackendOpenSSL:
|
||||||
|
return "BackendOpenSSL"
|
||||||
|
case BackendGoGCM:
|
||||||
|
return "BackendGoGCM"
|
||||||
|
case BackendAESSIV:
|
||||||
|
return "BackendAESSIV"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CryptoCore is the low level crypto implementation.
|
// CryptoCore is the low level crypto implementation.
|
||||||
type CryptoCore struct {
|
type CryptoCore struct {
|
||||||
// EME is used for filename encryption.
|
// EME is used for filename encryption.
|
||||||
|
91
internal/nametransform/badname.go
Normal file
91
internal/nametransform/badname.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package nametransform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"../syscallcompat"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BadnameSuffix is appended to filenames in plaintext view if a corrupt
|
||||||
|
// ciphername is shown due to a matching `-badname` pattern
|
||||||
|
BadnameSuffix = " GOCRYPTFS_BAD_NAME"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncryptAndHashBadName tries to find the "name" substring, which (encrypted and hashed)
|
||||||
|
// leads to an unique existing file
|
||||||
|
// Returns ENOENT if cipher file does not exist or is not unique
|
||||||
|
func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int) (cName string, err error) {
|
||||||
|
var st unix.Stat_t
|
||||||
|
var filesFound int
|
||||||
|
lastFoundName, err := be.EncryptAndHashName(name, iv)
|
||||||
|
if !strings.HasSuffix(name, BadnameSuffix) || err != nil {
|
||||||
|
//Default mode: same behaviour on error or no BadNameFlag on "name"
|
||||||
|
return lastFoundName, err
|
||||||
|
}
|
||||||
|
//Default mode: Check if File extists without modifications
|
||||||
|
err = syscallcompat.Fstatat(dirfd, lastFoundName, &st, unix.AT_SYMLINK_NOFOLLOW)
|
||||||
|
if err == nil {
|
||||||
|
//file found, return result
|
||||||
|
return lastFoundName, nil
|
||||||
|
}
|
||||||
|
//BadName Mode: check if the name was tranformed without change (badname suffix and undecryptable cipher name)
|
||||||
|
err = syscallcompat.Fstatat(dirfd, name[:len(name)-len(BadnameSuffix)], &st, unix.AT_SYMLINK_NOFOLLOW)
|
||||||
|
if err == nil {
|
||||||
|
filesFound++
|
||||||
|
lastFoundName = name[:len(name)-len(BadnameSuffix)]
|
||||||
|
}
|
||||||
|
// search for the longest badname pattern match
|
||||||
|
for charpos := len(name) - len(BadnameSuffix); charpos > 0; charpos-- {
|
||||||
|
//only use original cipher name and append assumed suffix (without badname flag)
|
||||||
|
cNamePart, err := be.EncryptName(name[:charpos], iv)
|
||||||
|
if err != nil {
|
||||||
|
//expand suffix on error
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if be.longNames && len(cName) > NameMax {
|
||||||
|
cNamePart = be.HashLongName(cName)
|
||||||
|
}
|
||||||
|
cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)]
|
||||||
|
err = syscallcompat.Fstatat(dirfd, cNameBadReverse, &st, unix.AT_SYMLINK_NOFOLLOW)
|
||||||
|
if err == nil {
|
||||||
|
filesFound++
|
||||||
|
lastFoundName = cNameBadReverse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filesFound == 1 {
|
||||||
|
return lastFoundName, nil
|
||||||
|
}
|
||||||
|
// more than 1 possible file found, ignore
|
||||||
|
return "", syscall.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NameTransform) decryptBadname(cipherName string, iv []byte) (string, error) {
|
||||||
|
for _, pattern := range n.badnamePatterns {
|
||||||
|
match, err := filepath.Match(pattern, cipherName)
|
||||||
|
// Pattern should have been validated already
|
||||||
|
if err == nil && match {
|
||||||
|
// Find longest decryptable substring
|
||||||
|
// At least 16 bytes due to AES --> at least 22 characters in base64
|
||||||
|
nameMin := n.B64.EncodedLen(aes.BlockSize)
|
||||||
|
for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- {
|
||||||
|
res, err := n.decryptName(cipherName[:charpos], iv)
|
||||||
|
if err == nil {
|
||||||
|
return res + cipherName[charpos:] + BadnameSuffix, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cipherName + BadnameSuffix, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", syscall.EBADMSG
|
||||||
|
}
|
||||||
|
|
||||||
|
// HaveBadnamePatterns returns true if `-badname` patterns were provided
|
||||||
|
func (n *NameTransform) HaveBadnamePatterns() bool {
|
||||||
|
return len(n.badnamePatterns) > 0
|
||||||
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"../cryptocore"
|
"../cryptocore"
|
||||||
@ -87,30 +86,3 @@ func WriteDirIVAt(dirfd int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptAndHashName encrypts "name" and hashes it to a longname if it is
|
|
||||||
// too long.
|
|
||||||
// Returns ENAMETOOLONG if "name" is longer than 255 bytes.
|
|
||||||
func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) {
|
|
||||||
// Prevent the user from creating files longer than 255 chars.
|
|
||||||
if len(name) > NameMax {
|
|
||||||
return "", syscall.ENAMETOOLONG
|
|
||||||
}
|
|
||||||
cName, err := be.EncryptName(name, iv)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if be.longNames && len(cName) > NameMax {
|
|
||||||
return be.HashLongName(cName), nil
|
|
||||||
}
|
|
||||||
return cName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dir is like filepath.Dir but returns "" instead of ".".
|
|
||||||
func Dir(path string) string {
|
|
||||||
d := filepath.Dir(path)
|
|
||||||
if d == "." {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
@ -15,21 +15,6 @@ const (
|
|||||||
NameMax = 255
|
NameMax = 255
|
||||||
)
|
)
|
||||||
|
|
||||||
// NameTransformer is an interface used to transform filenames.
|
|
||||||
type NameTransformer interface {
|
|
||||||
DecryptName(cipherName string, iv []byte) (string, error)
|
|
||||||
EncryptName(plainName string, iv []byte) (string, error)
|
|
||||||
EncryptAndHashName(name string, iv []byte) (string, error)
|
|
||||||
// HashLongName - take the hash of a long string "name" and return
|
|
||||||
// "gocryptfs.longname.[sha256]"
|
|
||||||
//
|
|
||||||
// This function does not do any I/O.
|
|
||||||
HashLongName(name string) string
|
|
||||||
WriteLongNameAt(dirfd int, hashName string, plainName string) error
|
|
||||||
B64EncodeToString(src []byte) string
|
|
||||||
B64DecodeString(s string) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NameTransform is used to transform filenames.
|
// NameTransform is used to transform filenames.
|
||||||
type NameTransform struct {
|
type NameTransform struct {
|
||||||
emeCipher *eme.EMECipher
|
emeCipher *eme.EMECipher
|
||||||
@ -38,19 +23,20 @@ type NameTransform struct {
|
|||||||
// on the Raw64 feature flag
|
// on the Raw64 feature flag
|
||||||
B64 *base64.Encoding
|
B64 *base64.Encoding
|
||||||
// Patterns to bypass decryption
|
// Patterns to bypass decryption
|
||||||
BadnamePatterns []string
|
badnamePatterns []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new NameTransform instance.
|
// New returns a new NameTransform instance.
|
||||||
func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform {
|
func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string) *NameTransform {
|
||||||
b64 := base64.URLEncoding
|
b64 := base64.URLEncoding
|
||||||
if raw64 {
|
if raw64 {
|
||||||
b64 = base64.RawURLEncoding
|
b64 = base64.RawURLEncoding
|
||||||
}
|
}
|
||||||
return &NameTransform{
|
return &NameTransform{
|
||||||
emeCipher: e,
|
emeCipher: e,
|
||||||
longNames: longNames,
|
longNames: longNames,
|
||||||
B64: b64,
|
B64: b64,
|
||||||
|
badnamePatterns: badname,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,22 +44,8 @@ func New(e *eme.EMECipher, longNames bool, raw64 bool) *NameTransform {
|
|||||||
// filename "cipherName", and failing that checks if it can be bypassed
|
// filename "cipherName", and failing that checks if it can be bypassed
|
||||||
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
|
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
|
||||||
res, err := n.decryptName(cipherName, iv)
|
res, err := n.decryptName(cipherName, iv)
|
||||||
if err != nil {
|
if err != nil && n.HaveBadnamePatterns() {
|
||||||
for _, pattern := range n.BadnamePatterns {
|
return n.decryptBadname(cipherName, iv)
|
||||||
match, err := filepath.Match(pattern, cipherName)
|
|
||||||
if err == nil && match { // Pattern should have been validated already
|
|
||||||
// Find longest decryptable substring
|
|
||||||
// At least 16 bytes due to AES --> at least 22 characters in base64
|
|
||||||
nameMin := n.B64.EncodedLen(aes.BlockSize)
|
|
||||||
for charpos := len(cipherName) - 1; charpos >= nameMin; charpos-- {
|
|
||||||
res, err = n.decryptName(cipherName[:charpos], iv)
|
|
||||||
if err == nil {
|
|
||||||
return res + cipherName[charpos:] + " GOCRYPTFS_BAD_NAME", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cipherName + " GOCRYPTFS_BAD_NAME", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@ -119,6 +91,24 @@ func (n *NameTransform) EncryptName(plainName string, iv []byte) (cipherName64 s
|
|||||||
return cipherName64, nil
|
return cipherName64, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncryptAndHashName encrypts "name" and hashes it to a longname if it is
|
||||||
|
// too long.
|
||||||
|
// Returns ENAMETOOLONG if "name" is longer than 255 bytes.
|
||||||
|
func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, error) {
|
||||||
|
// Prevent the user from creating files longer than 255 chars.
|
||||||
|
if len(name) > NameMax {
|
||||||
|
return "", syscall.ENAMETOOLONG
|
||||||
|
}
|
||||||
|
cName, err := be.EncryptName(name, iv)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if be.longNames && len(cName) > NameMax {
|
||||||
|
return be.HashLongName(cName), nil
|
||||||
|
}
|
||||||
|
return cName, nil
|
||||||
|
}
|
||||||
|
|
||||||
// B64EncodeToString returns a Base64-encoded string
|
// B64EncodeToString returns a Base64-encoded string
|
||||||
func (n *NameTransform) B64EncodeToString(src []byte) string {
|
func (n *NameTransform) B64EncodeToString(src []byte) string {
|
||||||
return n.B64.EncodeToString(src)
|
return n.B64.EncodeToString(src)
|
||||||
@ -128,3 +118,12 @@ func (n *NameTransform) B64EncodeToString(src []byte) string {
|
|||||||
func (n *NameTransform) B64DecodeString(s string) ([]byte, error) {
|
func (n *NameTransform) B64DecodeString(s string) ([]byte, error) {
|
||||||
return n.B64.DecodeString(s)
|
return n.B64.DecodeString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dir is like filepath.Dir but returns "" instead of ".".
|
||||||
|
func Dir(path string) string {
|
||||||
|
d := filepath.Dir(path)
|
||||||
|
if d == "." {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package pathiv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"../nametransform"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Purpose identifies for which purpose the IV will be used. This is mixed into the
|
|
||||||
// derivation.
|
|
||||||
type Purpose string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PurposeDirIV means the value will be used as a directory IV
|
|
||||||
PurposeDirIV Purpose = "DIRIV"
|
|
||||||
// PurposeFileID means the value will be used as the file ID in the file header
|
|
||||||
PurposeFileID Purpose = "FILEID"
|
|
||||||
// PurposeSymlinkIV means the value will be used as the IV for symlink encryption
|
|
||||||
PurposeSymlinkIV Purpose = "SYMLINKIV"
|
|
||||||
// PurposeBlock0IV means the value will be used as the IV of ciphertext block #0.
|
|
||||||
PurposeBlock0IV Purpose = "BLOCK0IV"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Derive derives an IV from an encrypted path by hashing it with sha256
|
|
||||||
func Derive(path string, purpose Purpose) []byte {
|
|
||||||
// Use null byte as separator as it cannot occur in the path
|
|
||||||
extended := []byte(path + "\000" + string(purpose))
|
|
||||||
hash := sha256.Sum256(extended)
|
|
||||||
return hash[:nametransform.DirIVLen]
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileIVs contains both IVs that are needed to create a file.
|
|
||||||
type FileIVs struct {
|
|
||||||
ID []byte
|
|
||||||
Block0IV []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeriveFile derives both IVs that are needed to create a file and returns them
|
|
||||||
// in a container struct.
|
|
||||||
func DeriveFile(path string) (fileIVs FileIVs) {
|
|
||||||
fileIVs.ID = Derive(path, PurposeFileID)
|
|
||||||
fileIVs.Block0IV = Derive(path, PurposeBlock0IV)
|
|
||||||
return fileIVs
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockIV returns the block IV for block number "blockNo". "block0iv" is the block
|
|
||||||
// IV of block #0.
|
|
||||||
func BlockIV(block0iv []byte, blockNo uint64) []byte {
|
|
||||||
iv := make([]byte, len(block0iv))
|
|
||||||
copy(iv, block0iv)
|
|
||||||
// Add blockNo to one half of the iv
|
|
||||||
lowBytes := iv[8:]
|
|
||||||
lowInt := binary.BigEndian.Uint64(lowBytes)
|
|
||||||
binary.BigEndian.PutUint64(lowBytes, lowInt+blockNo)
|
|
||||||
return iv
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package pathiv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestBlockIV makes sure we don't change the block iv derivation algorithm "BlockIV()"
|
|
||||||
// inadvertedly.
|
|
||||||
func TestBlockIV(t *testing.T) {
|
|
||||||
b0 := make([]byte, 16)
|
|
||||||
b0x := BlockIV(b0, 0)
|
|
||||||
if !bytes.Equal(b0, b0x) {
|
|
||||||
t.Errorf("b0x should be equal to b0")
|
|
||||||
}
|
|
||||||
b27 := BlockIV(b0, 0x27)
|
|
||||||
expected, _ := hex.DecodeString("00000000000000000000000000000027")
|
|
||||||
if !bytes.Equal(b27, expected) {
|
|
||||||
t.Errorf("\nhave=%s\nwant=%s", hex.EncodeToString(b27), hex.EncodeToString(expected))
|
|
||||||
}
|
|
||||||
bff := bytes.Repeat([]byte{0xff}, 16)
|
|
||||||
b28 := BlockIV(bff, 0x28)
|
|
||||||
expected, _ = hex.DecodeString("ffffffffffffffff0000000000000027")
|
|
||||||
if !bytes.Equal(b28, expected) {
|
|
||||||
t.Errorf("\nhave=%s\nwant=%s", hex.EncodeToString(b28), hex.EncodeToString(expected))
|
|
||||||
}
|
|
||||||
}
|
|
@ -74,7 +74,8 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
|
|||||||
forcedecode := false
|
forcedecode := false
|
||||||
newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode)
|
newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode)
|
||||||
newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS, forcedecode)
|
newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS, forcedecode)
|
||||||
newVolume.nameTransform = nametransform.New(newVolume.cryptoCore.EMECipher, true, true)
|
var badname []string
|
||||||
|
newVolume.nameTransform = nametransform.New(newVolume.cryptoCore.EMECipher, true, true, badname)
|
||||||
|
|
||||||
//copying rootCipherDir
|
//copying rootCipherDir
|
||||||
var grcd strings.Builder
|
var grcd strings.Builder
|
||||||
|
Loading…
x
Reference in New Issue
Block a user