aboutsummaryrefslogtreecommitdiff
path: root/cryptfs
diff options
context:
space:
mode:
Diffstat (limited to 'cryptfs')
-rw-r--r--cryptfs/config_file.go14
-rw-r--r--cryptfs/content_test.go21
-rw-r--r--cryptfs/cryptfs.go1
-rw-r--r--cryptfs/cryptfs_content.go20
-rw-r--r--cryptfs/file_header.go56
-rw-r--r--cryptfs/intrablock.go2
6 files changed, 96 insertions, 18 deletions
diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go
index ef426ff..7e762ad 100644
--- a/cryptfs/config_file.go
+++ b/cryptfs/config_file.go
@@ -1,6 +1,7 @@
package cryptfs
import (
+ "fmt"
"encoding/json"
"io/ioutil"
)
@@ -19,6 +20,8 @@ type ConfFile struct {
EncryptedKey []byte
// Stores parameters for scrypt hashing (key derivation)
ScryptObject scryptKdf
+ // The On-Disk-Format version this filesystem uses
+ Version uint16
}
// CreateConfFile - create a new config with a random key encrypted with
@@ -31,8 +34,11 @@ func CreateConfFile(filename string, password string) error {
key := RandBytes(KEY_LEN)
// Encrypt it using the password
+ // This sets ScryptObject and EncryptedKey
cf.EncryptKey(key, password)
+ cf.Version = HEADER_CURRENT_VERSION
+
// Write file to disk
err := cf.WriteFile()
@@ -58,6 +64,10 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
return nil, nil, err
}
+ if cf.Version != HEADER_CURRENT_VERSION {
+ return nil, nil, fmt.Errorf("Unsupported version %d", cf.Version)
+ }
+
// Generate derived key from password
scryptHash := cf.ScryptObject.DeriveKey(password)
@@ -65,7 +75,7 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
// We use stock go GCM instead of OpenSSL here as speed is not important
// and we get better error messages
cfs := NewCryptFS(scryptHash, false)
- key, err := cfs.DecryptBlock(cf.EncryptedKey, 0)
+ key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil)
if err != nil {
Warn.Printf("failed to unlock master key: %s\n", err.Error())
Warn.Printf("Password incorrect.\n")
@@ -84,7 +94,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string) {
// Lock master key using password-based key
cfs := NewCryptFS(scryptHash, false)
- cf.EncryptedKey = cfs.EncryptBlock(key, 0)
+ cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil)
}
// WriteFile - write out config in JSON format to file "filename.tmp"
diff --git a/cryptfs/content_test.go b/cryptfs/content_test.go
index aa6c9f3..4e1b447 100644
--- a/cryptfs/content_test.go
+++ b/cryptfs/content_test.go
@@ -1,7 +1,6 @@
package cryptfs
import (
- "fmt"
"testing"
)
@@ -17,6 +16,8 @@ func TestSplitRange(t *testing.T) {
testRange{0, 10},
testRange{234, 6511},
testRange{65444, 54},
+ testRange{0, 1024*1024},
+ testRange{0, 65536},
testRange{6654, 8945})
key := make([]byte, KEY_LEN)
@@ -24,10 +25,14 @@ func TestSplitRange(t *testing.T) {
for _, r := range ranges {
parts := f.SplitRange(r.offset, r.length)
+ var lastBlockNo uint64 = 1<<63
for _, p := range parts {
+ if p.BlockNo == lastBlockNo {
+ t.Errorf("Duplicate block number %d", p.BlockNo)
+ }
+ lastBlockNo = p.BlockNo
if p.Length > DEFAULT_PLAINBS || p.Skip >= DEFAULT_PLAINBS {
- fmt.Printf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
- t.Fail()
+ t.Errorf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
}
}
}
@@ -48,13 +53,13 @@ func TestCiphertextRange(t *testing.T) {
for _, r := range ranges {
alignedOffset, alignedLength, skipBytes := f.CiphertextRange(r.offset, r.length)
if alignedLength < r.length {
- t.Fail()
+ t.Errorf("alignedLength=%s is smaller than length=%d", alignedLength, r.length)
}
- if alignedOffset%f.cipherBS != 0 {
- t.Fail()
+ if (alignedOffset - HEADER_LEN)%f.cipherBS != 0 {
+ t.Errorf("alignedOffset=%d is not aligned", alignedOffset)
}
if r.offset%f.plainBS != 0 && skipBytes == 0 {
- t.Fail()
+ t.Errorf("skipBytes=0")
}
}
}
@@ -67,7 +72,7 @@ func TestBlockNo(t *testing.T) {
if b != 0 {
t.Errorf("actual: %d", b)
}
- b = f.BlockNoCipherOff(f.CipherBS())
+ b = f.BlockNoCipherOff(HEADER_LEN + f.CipherBS())
if b != 1 {
t.Errorf("actual: %d", b)
}
diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go
index 1556757..0593214 100644
--- a/cryptfs/cryptfs.go
+++ b/cryptfs/cryptfs.go
@@ -13,6 +13,7 @@ const (
KEY_LEN = 32 // AES-256
NONCE_LEN = 12
AUTH_TAG_LEN = 16
+ BLOCK_OVERHEAD = NONCE_LEN + AUTH_TAG_LEN
)
type CryptFS struct {
diff --git a/cryptfs/cryptfs_content.go b/cryptfs/cryptfs_content.go
index 7a1859a..03253d3 100644
--- a/cryptfs/cryptfs_content.go
+++ b/cryptfs/cryptfs_content.go
@@ -30,14 +30,14 @@ type CryptFile struct {
}
// DecryptBlocks - Decrypt a number of blocks
-func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64) ([]byte, error) {
+func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) {
cBuf := bytes.NewBuffer(ciphertext)
var err error
var pBuf bytes.Buffer
for cBuf.Len() > 0 {
cBlock := cBuf.Next(int(be.cipherBS))
var pBlock []byte
- pBlock, err = be.DecryptBlock(cBlock, firstBlockNo)
+ pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileId)
if err != nil {
break
}
@@ -51,7 +51,7 @@ func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64) ([]byte
//
// Corner case: A full-sized block of all-zero ciphertext bytes is translated
// to an all-zero plaintext block, i.e. file hole passtrough.
-func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, error) {
+func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) {
// Empty block?
if len(ciphertext) == 0 {
@@ -77,6 +77,7 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, erro
// Decrypt
var plaintext []byte
aData := make([]byte, 8)
+ aData = append(aData, fileId...)
binary.BigEndian.PutUint64(aData, blockNo)
plaintext, err := be.gcm.Open(plaintext, nonce, ciphertext, aData)
@@ -89,8 +90,8 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, erro
return plaintext, nil
}
-// encryptBlock - Encrypt and add MAC using GCM
-func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64) []byte {
+// encryptBlock - Encrypt and add IV and MAC
+func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileId []byte) []byte {
// Empty block?
if len(plaintext) == 0 {
@@ -103,6 +104,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64) []byte {
// Encrypt plaintext and append to nonce
aData := make([]byte, 8)
binary.BigEndian.PutUint64(aData, blockNo)
+ aData = append(aData, fileId...)
ciphertext := be.gcm.Seal(nonce, nonce, plaintext, aData)
return ciphertext
@@ -118,6 +120,7 @@ func (be *CryptFS) SplitRange(offset uint64, length uint64) []intraBlock {
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
@@ -134,6 +137,9 @@ func (be *CryptFS) PlainSize(size uint64) uint64 {
return 0
}
+ // Account for header
+ size -= HEADER_LEN
+
overhead := be.cipherBS - be.plainBS
nBlocks := (size + be.cipherBS - 1) / be.cipherBS
if nBlocks*overhead > size {
@@ -171,7 +177,7 @@ func (be *CryptFS) CiphertextRange(offset uint64, length uint64) (alignedOffset
firstBlockNo := offset / be.plainBS
lastBlockNo := (offset + length - 1) / be.plainBS
- alignedOffset = firstBlockNo * be.cipherBS
+ alignedOffset = HEADER_LEN + firstBlockNo * be.cipherBS
alignedLength = (lastBlockNo - firstBlockNo + 1) * be.cipherBS
skipBytes = int(skip)
@@ -232,5 +238,5 @@ func (be *CryptFS) BlockNoPlainOff(plainOffset uint64) uint64 {
// Get the block number at ciphter-text offset
func (be *CryptFS) BlockNoCipherOff(cipherOffset uint64) uint64 {
- return cipherOffset / be.cipherBS
+ return (cipherOffset - HEADER_LEN) / be.cipherBS
}
diff --git a/cryptfs/file_header.go b/cryptfs/file_header.go
new file mode 100644
index 0000000..3fd7266
--- /dev/null
+++ b/cryptfs/file_header.go
@@ -0,0 +1,56 @@
+package cryptfs
+
+// Per-file header
+//
+// Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ]
+
+import (
+ "encoding/binary"
+ "fmt"
+)
+
+const (
+ HEADER_CURRENT_VERSION = 1 // Current on-disk-format version
+ HEADER_VERSION_LEN = 2 // uint16
+ HEADER_ID_LEN = 16 // 128 bit random file id
+ HEADER_LEN = HEADER_VERSION_LEN + HEADER_ID_LEN // Total header length
+)
+
+type FileHeader struct {
+ Version uint16
+ Id []byte
+}
+
+// Pack - serialize fileHeader object
+func (h *FileHeader) Pack() []byte {
+ if len(h.Id) != HEADER_ID_LEN || h.Version != HEADER_CURRENT_VERSION {
+ panic("FileHeader object not properly initialized")
+ }
+ buf := make([]byte, HEADER_LEN)
+ binary.BigEndian.PutUint16(buf[0:HEADER_VERSION_LEN], h.Version)
+ copy(buf[HEADER_VERSION_LEN:], h.Id)
+ return buf
+
+}
+
+// ParseHeader - parse "buf" into fileHeader object
+func ParseHeader(buf []byte) (*FileHeader, error) {
+ if len(buf) != HEADER_LEN {
+ return nil, fmt.Errorf("ParseHeader: invalid length: got %d, want %d", len(buf), HEADER_LEN)
+ }
+ var h FileHeader
+ h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN])
+ if h.Version != HEADER_CURRENT_VERSION {
+ return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, HEADER_CURRENT_VERSION)
+ }
+ h.Id = buf[HEADER_VERSION_LEN:]
+ return &h, nil
+}
+
+// RandomHeader - create new fileHeader object with random Id
+func RandomHeader() *FileHeader {
+ var h FileHeader
+ h.Version = HEADER_CURRENT_VERSION
+ h.Id = RandBytes(HEADER_ID_LEN)
+ return &h
+}
diff --git a/cryptfs/intrablock.go b/cryptfs/intrablock.go
index 7f3a1eb..c83976c 100644
--- a/cryptfs/intrablock.go
+++ b/cryptfs/intrablock.go
@@ -19,7 +19,7 @@ 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 ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
+ return HEADER_LEN + ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
}
// PlaintextRange - get byte range in plaintext corresponding to BlockNo