reverse: use per-purpose nonce generation
Also pull all the deterministic nonce code into fusefrontend_reverse to greatly simplify the normal code path.
This commit is contained in:
parent
bce96b5095
commit
a2510efe12
|
@ -168,7 +168,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) {
|
||||||
// Lock master key using password-based key
|
// Lock master key using password-based key
|
||||||
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, 96)
|
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, 96)
|
||||||
ce := contentenc.New(cc, 4096)
|
ce := contentenc.New(cc, 4096)
|
||||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil, contentenc.RandomNonce, nil)
|
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||||
|
|
|
@ -127,50 +127,34 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b
|
||||||
return plaintext, nil
|
return plaintext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptBlocks - Encrypt a number of blocks
|
// EncryptBlock - Encrypt plaintext using a random nonce.
|
||||||
// Used for reverse mode
|
// blockNo and fileID are used as associated data.
|
||||||
func (be *ContentEnc) EncryptBlocks(plaintext []byte, firstBlockNo uint64, fileId []byte, nMode NonceMode) []byte {
|
// The output is nonce + ciphertext + tag.
|
||||||
inBuf := bytes.NewBuffer(plaintext)
|
func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte {
|
||||||
var outBuf bytes.Buffer
|
// Get a fresh random nonce
|
||||||
for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ {
|
nonce := be.cryptoCore.IVGenerator.Get()
|
||||||
inBlock := inBuf.Next(int(be.plainBS))
|
return be.doEncryptBlock(plaintext, blockNo, fileID, nonce)
|
||||||
outBlock := be.EncryptBlock(inBlock, blockNo, fileId, nMode, nil)
|
|
||||||
outBuf.Write(outBlock)
|
|
||||||
}
|
|
||||||
return outBuf.Bytes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptBlock - Encrypt and add IV and MAC
|
// EncryptBlockNonce - Encrypt plaintext using a nonce chosen by the caller.
|
||||||
func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte, nMode NonceMode, externalNonce []byte) []byte {
|
// blockNo and fileID are used as associated data.
|
||||||
|
// The output is nonce + ciphertext + tag.
|
||||||
|
// This function can only be used in SIV mode.
|
||||||
|
func (be *ContentEnc) EncryptBlockNonce(plaintext []byte, blockNo uint64, fileID []byte, nonce []byte) []byte {
|
||||||
|
if be.cryptoCore.AEADBackend != cryptocore.BackendAESSIV {
|
||||||
|
panic("deterministic nonces are only secure in SIV mode")
|
||||||
|
}
|
||||||
|
return be.doEncryptBlock(plaintext, blockNo, fileID, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doEncryptBlock is the backend for EncryptBlock and EncryptBlockNonce.
|
||||||
|
// blockNo and fileID are used as associated data.
|
||||||
|
// The output is nonce + ciphertext + tag.
|
||||||
|
func (be *ContentEnc) doEncryptBlock(plaintext []byte, blockNo uint64, fileID []byte, nonce []byte) []byte {
|
||||||
// Empty block?
|
// Empty block?
|
||||||
if len(plaintext) == 0 {
|
if len(plaintext) == 0 {
|
||||||
return plaintext
|
return plaintext
|
||||||
}
|
}
|
||||||
|
|
||||||
var nonce []byte
|
|
||||||
switch nMode {
|
|
||||||
case ExternalNonce:
|
|
||||||
if be.cryptoCore.AEADBackend != cryptocore.BackendAESSIV {
|
|
||||||
panic("MUST NOT use deterministic nonces unless in AESSIV mode!")
|
|
||||||
}
|
|
||||||
nonce = externalNonce
|
|
||||||
case ReverseDeterministicNonce:
|
|
||||||
if be.cryptoCore.AEADBackend != cryptocore.BackendAESSIV {
|
|
||||||
panic("MUST NOT use deterministic nonces unless in AESSIV mode!")
|
|
||||||
}
|
|
||||||
l := be.cryptoCore.IVLen
|
|
||||||
nonce = make([]byte, l)
|
|
||||||
copy(nonce, fileID)
|
|
||||||
// Add the block number to the last 8 byte. Plus one so the block-zero
|
|
||||||
// IV is distinct from the fileID.
|
|
||||||
counter := binary.BigEndian.Uint64(nonce[l-8 : l])
|
|
||||||
binary.BigEndian.PutUint64(nonce[l-8:l], counter+blockNo+1)
|
|
||||||
case RandomNonce:
|
|
||||||
// Get a fresh random nonce
|
|
||||||
nonce = be.cryptoCore.IVGenerator.Get()
|
|
||||||
default:
|
|
||||||
panic("invalid nonce mode")
|
|
||||||
}
|
|
||||||
if len(nonce) != be.cryptoCore.IVLen {
|
if len(nonce) != be.cryptoCore.IVLen {
|
||||||
panic("wrong nonce length")
|
panic("wrong nonce length")
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,7 +256,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||||
|
|
||||||
// Encrypt
|
// Encrypt
|
||||||
blockOffset := b.BlockCipherOff()
|
blockOffset := b.BlockCipherOff()
|
||||||
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id, contentenc.RandomNonce, nil)
|
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id)
|
||||||
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
|
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
|
||||||
f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
|
f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
|
||||||
|
|
||||||
|
|
|
@ -326,7 +326,7 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co
|
||||||
return fuse.ToStatus(err)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
// Symlinks are encrypted like file contents (GCM) and base64-encoded
|
// Symlinks are encrypted like file contents (GCM) and base64-encoded
|
||||||
cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil, contentenc.RandomNonce, nil)
|
cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil)
|
||||||
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
||||||
|
|
||||||
// Handle long file name
|
// Handle long file name
|
||||||
|
|
|
@ -84,7 +84,7 @@ func (rfs *reverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
dirIV := derivePathIV(cDir)
|
dirIV := derivePathIV(cDir, ivPurposeDirIV)
|
||||||
e, err := rfs.findLongnameParent(pDir, dirIV, longname)
|
e, err := rfs.findLongnameParent(pDir, dirIV, longname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package fusefrontend_reverse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -20,6 +21,8 @@ type reverseFile struct {
|
||||||
fd *os.File
|
fd *os.File
|
||||||
// File header (contains the IV)
|
// File header (contains the IV)
|
||||||
header contentenc.FileHeader
|
header contentenc.FileHeader
|
||||||
|
// IV for block 0
|
||||||
|
block0IV []byte
|
||||||
// Content encryption helper
|
// Content encryption helper
|
||||||
contentEnc *contentenc.ContentEnc
|
contentEnc *contentenc.ContentEnc
|
||||||
}
|
}
|
||||||
|
@ -33,7 +36,7 @@ func (rfs *reverseFS) NewFile(relPath string, flags uint32) (nodefs.File, fuse.S
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
id := derivePathIV(relPath)
|
id := derivePathIV(relPath, ivPurposeFileID)
|
||||||
header := contentenc.FileHeader{
|
header := contentenc.FileHeader{
|
||||||
Version: contentenc.CurrentVersion,
|
Version: contentenc.CurrentVersion,
|
||||||
Id: id,
|
Id: id,
|
||||||
|
@ -42,6 +45,7 @@ func (rfs *reverseFS) NewFile(relPath string, flags uint32) (nodefs.File, fuse.S
|
||||||
File: nodefs.NewDefaultFile(),
|
File: nodefs.NewDefaultFile(),
|
||||||
fd: fd,
|
fd: fd,
|
||||||
header: header,
|
header: header,
|
||||||
|
block0IV: derivePathIV(relPath, ivPurposeBlock0IV),
|
||||||
contentEnc: rfs.contentEnc,
|
contentEnc: rfs.contentEnc,
|
||||||
}, fuse.OK
|
}, fuse.OK
|
||||||
}
|
}
|
||||||
|
@ -52,6 +56,26 @@ func (rf *reverseFile) GetAttr(*fuse.Attr) fuse.Status {
|
||||||
return fuse.ENOSYS
|
return fuse.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encryptBlocks - encrypt "plaintext" into a number of ciphertext blocks.
|
||||||
|
// "plaintext" must already be block-aligned.
|
||||||
|
func (rf *reverseFile) encryptBlocks(plaintext []byte, firstBlockNo uint64, fileId []byte, block0IV []byte) []byte {
|
||||||
|
nonce := make([]byte, len(block0IV))
|
||||||
|
copy(nonce, block0IV)
|
||||||
|
block0IVlow := binary.BigEndian.Uint64(block0IV[8:])
|
||||||
|
nonceLow := nonce[8:]
|
||||||
|
|
||||||
|
inBuf := bytes.NewBuffer(plaintext)
|
||||||
|
var outBuf bytes.Buffer
|
||||||
|
bs := int(rf.contentEnc.PlainBS())
|
||||||
|
for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ {
|
||||||
|
binary.BigEndian.PutUint64(nonceLow, block0IVlow+blockNo)
|
||||||
|
inBlock := inBuf.Next(bs)
|
||||||
|
outBlock := rf.contentEnc.EncryptBlockNonce(inBlock, blockNo, fileId, nonce)
|
||||||
|
outBuf.Write(outBlock)
|
||||||
|
}
|
||||||
|
return outBuf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
// readBackingFile: read from the backing plaintext file, encrypt it, return the
|
// readBackingFile: read from the backing plaintext file, encrypt it, return the
|
||||||
// ciphertext.
|
// ciphertext.
|
||||||
// "off" ... ciphertext offset (must be >= HEADER_LEN)
|
// "off" ... ciphertext offset (must be >= HEADER_LEN)
|
||||||
|
@ -71,7 +95,7 @@ func (rf *reverseFile) readBackingFile(off uint64, length uint64) (out []byte, e
|
||||||
plaintext = plaintext[0:n]
|
plaintext = plaintext[0:n]
|
||||||
|
|
||||||
// Encrypt blocks
|
// Encrypt blocks
|
||||||
ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, rf.header.Id, contentenc.ReverseDeterministicNonce)
|
ciphertext := rf.encryptBlocks(plaintext, blocks[0].BlockNo, rf.header.Id, rf.block0IV)
|
||||||
|
|
||||||
// Crop down to the relevant part
|
// Crop down to the relevant part
|
||||||
lenHave := len(ciphertext)
|
lenHave := len(ciphertext)
|
||||||
|
|
|
@ -254,7 +254,7 @@ func (rfs *reverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
|
||||||
nVirtual := 1
|
nVirtual := 1
|
||||||
|
|
||||||
// Encrypt names
|
// Encrypt names
|
||||||
dirIV := derivePathIV(cipherPath)
|
dirIV := derivePathIV(cipherPath, ivPurposeDirIV)
|
||||||
for i := range entries {
|
for i := range entries {
|
||||||
var cName string
|
var cName string
|
||||||
// ".gocryptfs.reverse.conf" in the root directory is mapped to "gocryptfs.conf"
|
// ".gocryptfs.reverse.conf" in the root directory is mapped to "gocryptfs.conf"
|
||||||
|
@ -296,9 +296,9 @@ func (rfs *reverseFS) Readlink(cipherPath string, context *fuse.Context) (string
|
||||||
if rfs.args.PlaintextNames {
|
if rfs.args.PlaintextNames {
|
||||||
return plainTarget, fuse.OK
|
return plainTarget, fuse.OK
|
||||||
}
|
}
|
||||||
nonce := derivePathIV(cipherPath)
|
nonce := derivePathIV(cipherPath, ivPurposeSymlinkIV)
|
||||||
// Symlinks are encrypted like file contents and base64-encoded
|
// Symlinks are encrypted like file contents and base64-encoded
|
||||||
cBinTarget := rfs.contentEnc.EncryptBlock([]byte(plainTarget), 0, nil, contentenc.ExternalNonce, nonce)
|
cBinTarget := rfs.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce)
|
||||||
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
||||||
return cTarget, fuse.OK
|
return cTarget, fuse.OK
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,20 @@ func saneDir(path string) string {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// derivePathIV derives an IV from an encrypted path by hashing it
|
type ivPurposeType string
|
||||||
func derivePathIV(path string) []byte {
|
|
||||||
hash := sha256.Sum256([]byte(path))
|
const (
|
||||||
|
ivPurposeDirIV ivPurposeType = "DIRIV"
|
||||||
|
ivPurposeFileID ivPurposeType = "FILEID"
|
||||||
|
ivPurposeSymlinkIV ivPurposeType = "SYMLINKIV"
|
||||||
|
ivPurposeBlock0IV ivPurposeType = "BLOCK0IV"
|
||||||
|
)
|
||||||
|
|
||||||
|
// derivePathIV derives an IV from an encrypted path by hashing it with sha256
|
||||||
|
func derivePathIV(path string, purpose ivPurposeType) []byte {
|
||||||
|
// Use null byte as separator as it cannot occour in the path
|
||||||
|
extended := []byte(path + "\000" + string(purpose))
|
||||||
|
hash := sha256.Sum256(extended)
|
||||||
return hash[:nametransform.DirIVLen]
|
return hash[:nametransform.DirIVLen]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +54,7 @@ func (rfs *reverseFS) decryptPath(relPath string) (string, error) {
|
||||||
// Start at the top and recurse
|
// Start at the top and recurse
|
||||||
currentDir := filepath.Join(parts[:i]...)
|
currentDir := filepath.Join(parts[:i]...)
|
||||||
nameType := nametransform.NameType(part)
|
nameType := nametransform.NameType(part)
|
||||||
dirIV := derivePathIV(currentDir)
|
dirIV := derivePathIV(currentDir, ivPurposeDirIV)
|
||||||
var transformedPart string
|
var transformedPart string
|
||||||
if nameType == nametransform.LongNameNone {
|
if nameType == nametransform.LongNameNone {
|
||||||
transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV)
|
transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV)
|
||||||
|
|
|
@ -14,7 +14,7 @@ func (rfs *reverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
return nil, fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
return rfs.NewVirtualFile(derivePathIV(cDir), absDir)
|
return rfs.NewVirtualFile(derivePathIV(cDir, ivPurposeDirIV), absDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
type virtualFile struct {
|
type virtualFile struct {
|
||||||
|
|
Loading…
Reference in New Issue