Refactor ciphertext <-> plaintext offset translation functions
Move all the intelligence into the new file address_translation.go. That the calculations were spread out too much became apparent when adding the file header. This should make the code much easier to modify in the future.
This commit is contained in:
parent
14276c9632
commit
902babdf22
79
cryptfs/address_translation.go
Normal file
79
cryptfs/address_translation.go
Normal file
@ -0,0 +1,79 @@
|
||||
package cryptfs
|
||||
|
||||
// CryptFS methods that translate offsets between ciphertext and plaintext
|
||||
|
||||
// get the block number at plain-text offset
|
||||
func (be *CryptFS) PlainOffToBlockNo(plainOffset uint64) uint64 {
|
||||
return plainOffset / be.plainBS
|
||||
}
|
||||
|
||||
// get the block number at ciphter-text offset
|
||||
func (be *CryptFS) CipherOffToBlockNo(cipherOffset uint64) uint64 {
|
||||
return (cipherOffset - HEADER_LEN) / be.cipherBS
|
||||
}
|
||||
|
||||
// get ciphertext offset of block "blockNo"
|
||||
func (be *CryptFS) BlockNoToCipherOff(blockNo uint64) uint64 {
|
||||
return HEADER_LEN + blockNo*be.cipherBS
|
||||
}
|
||||
|
||||
// get plaintext offset of block "blockNo"
|
||||
func (be *CryptFS) BlockNoToPlainOff(blockNo uint64) uint64 {
|
||||
return blockNo * be.plainBS
|
||||
}
|
||||
|
||||
// PlainSize - calculate plaintext size from ciphertext size
|
||||
func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
|
||||
// Zero sized files stay zero-sized
|
||||
if cipherSize == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Block number at last byte
|
||||
blockNo := be.CipherOffToBlockNo(cipherSize - 1)
|
||||
blockCount := blockNo + 1
|
||||
|
||||
overhead := BLOCK_OVERHEAD*blockCount + HEADER_LEN
|
||||
|
||||
return cipherSize - overhead
|
||||
}
|
||||
|
||||
// CipherSize - calculate ciphertext size from plaintext size
|
||||
func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 {
|
||||
|
||||
// Block number at last byte
|
||||
blockNo := be.PlainOffToBlockNo(plainSize - 1)
|
||||
blockCount := blockNo + 1
|
||||
|
||||
overhead := BLOCK_OVERHEAD*blockCount + HEADER_LEN
|
||||
|
||||
return plainSize + overhead
|
||||
}
|
||||
|
||||
// Split a plaintext byte range into (possibly partial) blocks
|
||||
func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock {
|
||||
var blocks []intraBlock
|
||||
var nextBlock intraBlock
|
||||
nextBlock.fs = be
|
||||
|
||||
for length > 0 {
|
||||
nextBlock.BlockNo = be.PlainOffToBlockNo(offset)
|
||||
nextBlock.Skip = offset - be.BlockNoToPlainOff(nextBlock.BlockNo)
|
||||
|
||||
// Minimum of remaining data and remaining space in the block
|
||||
nextBlock.Length = MinUint64(length, be.plainBS-nextBlock.Skip)
|
||||
|
||||
blocks = append(blocks, nextBlock)
|
||||
offset += nextBlock.Length
|
||||
length -= nextBlock.Length
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
func MinUint64(x uint64, y uint64) uint64 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package cryptfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
import "os"
|
||||
|
@ -24,7 +24,7 @@ func TestSplitRange(t *testing.T) {
|
||||
f := NewCryptFS(key, true)
|
||||
|
||||
for _, r := range ranges {
|
||||
parts := f.SplitRange(r.offset, r.length)
|
||||
parts := f.ExplodePlainRange(r.offset, r.length)
|
||||
var lastBlockNo uint64 = 1 << 63
|
||||
for _, p := range parts {
|
||||
if p.BlockNo == lastBlockNo {
|
||||
@ -51,7 +51,11 @@ func TestCiphertextRange(t *testing.T) {
|
||||
f := NewCryptFS(key, true)
|
||||
|
||||
for _, r := range ranges {
|
||||
alignedOffset, alignedLength, skipBytes := f.CiphertextRange(r.offset, r.length)
|
||||
|
||||
blocks := f.ExplodePlainRange(r.offset, r.length)
|
||||
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
|
||||
skipBytes := blocks[0].Skip
|
||||
|
||||
if alignedLength < r.length {
|
||||
t.Errorf("alignedLength=%s is smaller than length=%d", alignedLength, r.length)
|
||||
}
|
||||
@ -68,19 +72,19 @@ func TestBlockNo(t *testing.T) {
|
||||
key := make([]byte, KEY_LEN)
|
||||
f := NewCryptFS(key, true)
|
||||
|
||||
b := f.BlockNoCipherOff(788)
|
||||
b := f.CipherOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.BlockNoCipherOff(HEADER_LEN + f.CipherBS())
|
||||
b = f.CipherOffToBlockNo(HEADER_LEN + f.cipherBS)
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.BlockNoPlainOff(788)
|
||||
b = f.PlainOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.BlockNoPlainOff(f.PlainBS())
|
||||
b = f.PlainOffToBlockNo(f.plainBS)
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
|
@ -61,8 +61,3 @@ func NewCryptFS(key []byte, useOpenssl bool) *CryptFS {
|
||||
func (be *CryptFS) PlainBS() uint64 {
|
||||
return be.plainBS
|
||||
}
|
||||
|
||||
// Get ciphertext block size
|
||||
func (be *CryptFS) CipherBS() uint64 {
|
||||
return be.cipherBS
|
||||
}
|
||||
|
@ -12,11 +12,6 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// A block of 4124 zero bytes has this md5
|
||||
ZeroBlockMd5 = "64331af89bd15a987b39855338336237"
|
||||
)
|
||||
|
||||
// md5sum - debug helper, return md5 hex string
|
||||
func md5sum(buf []byte) string {
|
||||
rawHash := md5.Sum(buf)
|
||||
@ -110,106 +105,6 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileId []byte)
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
// Split a plaintext byte range into (possibly partial) blocks
|
||||
func (be *CryptFS) SplitRange(offset uint64, length uint64) []intraBlock {
|
||||
var b intraBlock
|
||||
var parts []intraBlock
|
||||
|
||||
b.fs = be
|
||||
|
||||
for length > 0 {
|
||||
b.BlockNo = offset / be.plainBS
|
||||
b.Skip = offset % be.plainBS
|
||||
// Minimum of remaining data and remaining space in the block
|
||||
b.Length = be.minu64(length, be.plainBS-b.Skip)
|
||||
parts = append(parts, b)
|
||||
offset += b.Length
|
||||
length -= b.Length
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
// PlainSize - calculate plaintext size from ciphertext size
|
||||
func (be *CryptFS) PlainSize(size uint64) uint64 {
|
||||
|
||||
// Zero sized files stay zero-sized
|
||||
if size == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Account for header
|
||||
size -= HEADER_LEN
|
||||
|
||||
overhead := be.cipherBS - be.plainBS
|
||||
nBlocks := (size + be.cipherBS - 1) / be.cipherBS
|
||||
if nBlocks*overhead > size {
|
||||
Warn.Printf("PlainSize: Negative size, returning 0 instead\n")
|
||||
return 0
|
||||
}
|
||||
size -= nBlocks * overhead
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
// CipherSize - calculate ciphertext size from plaintext size
|
||||
func (be *CryptFS) CipherSize(size uint64) uint64 {
|
||||
overhead := be.cipherBS - be.plainBS
|
||||
nBlocks := (size + be.plainBS - 1) / be.plainBS
|
||||
size += nBlocks * overhead
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func (be *CryptFS) minu64(x uint64, y uint64) uint64 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// CiphertextRange - Get byte range in backing ciphertext corresponding
|
||||
// to plaintext range. Returns a range aligned to ciphertext blocks.
|
||||
func (be *CryptFS) CiphertextRange(offset uint64, length uint64) (alignedOffset uint64, alignedLength uint64, skipBytes int) {
|
||||
// Decrypting the ciphertext will yield too many plaintext bytes. Skip this number
|
||||
// of bytes from the front.
|
||||
skip := offset % be.plainBS
|
||||
|
||||
firstBlockNo := offset / be.plainBS
|
||||
lastBlockNo := (offset + length - 1) / be.plainBS
|
||||
|
||||
alignedOffset = HEADER_LEN + firstBlockNo * be.cipherBS
|
||||
alignedLength = (lastBlockNo - firstBlockNo + 1) * be.cipherBS
|
||||
|
||||
skipBytes = int(skip)
|
||||
return alignedOffset, alignedLength, skipBytes
|
||||
}
|
||||
|
||||
// Get the byte range in the ciphertext corresponding to blocks
|
||||
// (full blocks!)
|
||||
func (be *CryptFS) JoinCiphertextRange(blocks []intraBlock) (uint64, uint64) {
|
||||
|
||||
offset, _ := blocks[0].CiphertextRange()
|
||||
last := blocks[len(blocks)-1]
|
||||
length := (last.BlockNo - blocks[0].BlockNo + 1) * be.cipherBS
|
||||
|
||||
return offset, length
|
||||
}
|
||||
|
||||
// Crop plaintext that correspons to complete cipher blocks down to what is
|
||||
// requested according to "iblocks"
|
||||
func (be *CryptFS) CropPlaintext(plaintext []byte, blocks []intraBlock) []byte {
|
||||
offset := blocks[0].Skip
|
||||
last := blocks[len(blocks)-1]
|
||||
length := (last.BlockNo - blocks[0].BlockNo + 1) * be.plainBS
|
||||
var cropped []byte
|
||||
if offset+length > uint64(len(plaintext)) {
|
||||
cropped = plaintext[offset:]
|
||||
} else {
|
||||
cropped = plaintext[offset : offset+length]
|
||||
}
|
||||
return cropped
|
||||
}
|
||||
|
||||
// MergeBlocks - Merge newData into oldData at offset
|
||||
// New block may be bigger than both newData and oldData
|
||||
func (be *CryptFS) MergeBlocks(oldData []byte, newData []byte, offset int) []byte {
|
||||
@ -230,13 +125,3 @@ func (be *CryptFS) MergeBlocks(oldData []byte, newData []byte, offset int) []byt
|
||||
}
|
||||
return out[0:outLen]
|
||||
}
|
||||
|
||||
// Get the block number at plain-text offset
|
||||
func (be *CryptFS) BlockNoPlainOff(plainOffset uint64) uint64 {
|
||||
return plainOffset / be.plainBS
|
||||
}
|
||||
|
||||
// Get the block number at ciphter-text offset
|
||||
func (be *CryptFS) BlockNoCipherOff(cipherOffset uint64) uint64 {
|
||||
return (cipherOffset - HEADER_LEN) / be.cipherBS
|
||||
}
|
||||
|
@ -19,13 +19,13 @@ func (ib *intraBlock) IsPartial() bool {
|
||||
// CiphertextRange - get byte range in ciphertext file corresponding to BlockNo
|
||||
// (complete block)
|
||||
func (ib *intraBlock) CiphertextRange() (offset uint64, length uint64) {
|
||||
return HEADER_LEN + ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
|
||||
return ib.fs.BlockNoToCipherOff(ib.BlockNo), ib.fs.cipherBS
|
||||
}
|
||||
|
||||
// PlaintextRange - get byte range in plaintext corresponding to BlockNo
|
||||
// (complete block)
|
||||
func (ib *intraBlock) PlaintextRange() (offset uint64, length uint64) {
|
||||
return ib.BlockNo * ib.fs.plainBS, ib.fs.plainBS
|
||||
return ib.fs.BlockNoToPlainOff(ib.BlockNo), ib.fs.plainBS
|
||||
}
|
||||
|
||||
// CropBlock - crop a potentially larger plaintext block down to the relevant part
|
||||
@ -37,3 +37,15 @@ func (ib *intraBlock) CropBlock(d []byte) []byte {
|
||||
}
|
||||
return d[ib.Skip:lenWant]
|
||||
}
|
||||
|
||||
// Ciphertext range corresponding to the sum of all "blocks" (complete blocks)
|
||||
func (ib *intraBlock) JointCiphertextRange(blocks []intraBlock) (offset uint64, length uint64) {
|
||||
firstBlock := blocks[0]
|
||||
lastBlock := blocks[len(blocks)-1]
|
||||
|
||||
offset = ib.fs.BlockNoToCipherOff(firstBlock.BlockNo)
|
||||
offsetLast := ib.fs.BlockNoToCipherOff(lastBlock.BlockNo)
|
||||
length = offsetLast + ib.fs.cipherBS - offset
|
||||
|
||||
return offset, length
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
@ -11,6 +9,8 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -128,8 +128,10 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
|
||||
}
|
||||
|
||||
// Read the backing ciphertext in one go
|
||||
alignedOffset, alignedLength, skip := f.cfs.CiphertextRange(off, length)
|
||||
cryptfs.Debug.Printf("CiphertextRange(%d, %d) -> %d, %d, %d\n", off, length, alignedOffset, alignedLength, skip)
|
||||
blocks := f.cfs.ExplodePlainRange(off, length)
|
||||
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
|
||||
skip := blocks[0].Skip
|
||||
cryptfs.Debug.Printf("JointCiphertextRange(%d, %d) -> %d, %d, %d\n", off, length, alignedOffset, alignedLength, skip)
|
||||
ciphertext := make([]byte, int(alignedLength))
|
||||
f.fdLock.Lock()
|
||||
n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset))
|
||||
@ -141,27 +143,27 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
|
||||
// Truncate ciphertext buffer down to actually read bytes
|
||||
ciphertext = ciphertext[0:n]
|
||||
|
||||
blockNo := (alignedOffset - cryptfs.HEADER_LEN) / f.cfs.CipherBS()
|
||||
cryptfs.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d\n", alignedOffset, blockNo, alignedLength, n)
|
||||
firstBlockNo := blocks[0].BlockNo
|
||||
cryptfs.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d\n", alignedOffset, firstBlockNo, alignedLength, n)
|
||||
|
||||
// Decrypt it
|
||||
plaintext, err := f.cfs.DecryptBlocks(ciphertext, blockNo, f.header.Id)
|
||||
plaintext, err := f.cfs.DecryptBlocks(ciphertext, firstBlockNo, f.header.Id)
|
||||
if err != nil {
|
||||
blockNo := (alignedOffset + uint64(len(plaintext))) / f.cfs.PlainBS()
|
||||
cipherOff := cryptfs.HEADER_LEN + blockNo * f.cfs.CipherBS()
|
||||
plainOff := blockNo * f.cfs.PlainBS()
|
||||
cryptfs.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d/%d, cipherOff=%d/%d)\n",
|
||||
f.ino, blockNo, plainOff, f.cfs.PlainBS(), cipherOff, f.cfs.CipherBS())
|
||||
curruptBlockNo := firstBlockNo + f.cfs.PlainOffToBlockNo(uint64(len(plaintext)))
|
||||
cipherOff := f.cfs.BlockNoToCipherOff(curruptBlockNo)
|
||||
plainOff := f.cfs.BlockNoToPlainOff(curruptBlockNo)
|
||||
cryptfs.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d, cipherOff=%d)\n",
|
||||
f.ino, curruptBlockNo, plainOff, cipherOff)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// Crop down to the relevant part
|
||||
var out []byte
|
||||
lenHave := len(plaintext)
|
||||
lenWant := skip + int(length)
|
||||
lenWant := int(skip + length)
|
||||
if lenHave > lenWant {
|
||||
out = plaintext[skip : skip+int(length)]
|
||||
} else if lenHave > skip {
|
||||
out = plaintext[skip:lenWant]
|
||||
} else if lenHave > int(skip) {
|
||||
out = plaintext[skip:lenHave]
|
||||
}
|
||||
// else: out stays empty, file was smaller than the requested offset
|
||||
@ -216,7 +218,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||
var written uint32
|
||||
status := fuse.OK
|
||||
dataBuf := bytes.NewBuffer(data)
|
||||
blocks := f.cfs.SplitRange(uint64(off), uint64(len(data)))
|
||||
blocks := f.cfs.ExplodePlainRange(uint64(off), uint64(len(data)))
|
||||
for _, b := range blocks {
|
||||
|
||||
blockData := dataBuf.Next(int(b.Length))
|
||||
@ -240,10 +242,6 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||
blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id)
|
||||
cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d, md5=%s\n",
|
||||
f.ino, len(blockData)-cryptfs.BLOCK_OVERHEAD, b.BlockNo, cryptfs.Debug.Md5sum(blockData))
|
||||
if len(blockData) != int(f.cfs.CipherBS()) {
|
||||
cryptfs.Debug.Printf("ino%d: Writing partial block #%d (%d bytes)\n",
|
||||
f.ino, b.BlockNo, len(blockData) - cryptfs.BLOCK_OVERHEAD)
|
||||
}
|
||||
f.fdLock.Lock()
|
||||
_, err := f.fd.WriteAt(blockData, int64(blockOffset))
|
||||
f.fdLock.Unlock()
|
||||
@ -267,7 +265,7 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
|
||||
cryptfs.Warn.Printf("Write: Fstat failed: %v\n", err)
|
||||
return 0, fuse.ToStatus(err)
|
||||
}
|
||||
plainSize := f.cfs.PlainSize(uint64(fi.Size()))
|
||||
plainSize := f.cfs.CipherSizeToPlainSize(uint64(fi.Size()))
|
||||
if f.createsHole(plainSize, off) {
|
||||
status := f.zeroPad(plainSize)
|
||||
if status != fuse.OK {
|
||||
@ -332,7 +330,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
cryptfs.Warn.Printf("Truncate: Fstat failed: %v\n", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
oldSize := f.cfs.PlainSize(uint64(fi.Size()))
|
||||
oldSize := f.cfs.CipherSizeToPlainSize(uint64(fi.Size()))
|
||||
{
|
||||
oldB := float32(oldSize) / float32(f.cfs.PlainBS())
|
||||
newB := float32(newSize) / float32(f.cfs.PlainBS())
|
||||
@ -350,7 +348,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
}
|
||||
}
|
||||
|
||||
blocks := f.cfs.SplitRange(oldSize, newSize-oldSize)
|
||||
blocks := f.cfs.ExplodePlainRange(oldSize, newSize-oldSize)
|
||||
for _, b := range blocks {
|
||||
// First and last block may be partial
|
||||
if b.IsPartial() {
|
||||
@ -374,9 +372,9 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
return fuse.OK
|
||||
} else {
|
||||
// File shrinks
|
||||
blockNo := f.cfs.BlockNoPlainOff(newSize)
|
||||
cipherOff := cryptfs.HEADER_LEN + blockNo * f.cfs.CipherBS()
|
||||
plainOff := blockNo * f.cfs.PlainBS()
|
||||
blockNo := f.cfs.PlainOffToBlockNo(newSize)
|
||||
cipherOff := f.cfs.BlockNoToCipherOff(blockNo)
|
||||
plainOff := f.cfs.BlockNoToPlainOff(blockNo)
|
||||
lastBlockLen := newSize - plainOff
|
||||
var data []byte
|
||||
if lastBlockLen > 0 {
|
||||
@ -430,7 +428,7 @@ func (f *file) GetAttr(a *fuse.Attr) fuse.Status {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
a.FromStat(&st)
|
||||
a.Size = f.cfs.PlainSize(a.Size)
|
||||
a.Size = f.cfs.CipherSizeToPlainSize(a.Size)
|
||||
|
||||
return fuse.OK
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
|
||||
// Will a write to offset "off" create a file hole?
|
||||
func (f *file) createsHole(plainSize uint64, off int64) bool {
|
||||
nextBlock := f.cfs.BlockNoPlainOff(plainSize)
|
||||
targetBlock := f.cfs.BlockNoPlainOff(uint64(off))
|
||||
nextBlock := f.cfs.PlainOffToBlockNo(plainSize)
|
||||
targetBlock := f.cfs.PlainOffToBlockNo(uint64(off))
|
||||
if targetBlock > nextBlock {
|
||||
return true
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func (fs *FS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Stat
|
||||
return a, status
|
||||
}
|
||||
if a.IsRegular() {
|
||||
a.Size = fs.PlainSize(a.Size)
|
||||
a.Size = fs.CipherSizeToPlainSize(a.Size)
|
||||
} else if a.IsSymlink() {
|
||||
target, _ := fs.Readlink(name, context)
|
||||
a.Size = uint64(len(target))
|
||||
|
Loading…
Reference in New Issue
Block a user