diff options
Diffstat (limited to 'cryptfs')
-rw-r--r-- | cryptfs/config_file.go | 14 | ||||
-rw-r--r-- | cryptfs/content_test.go | 21 | ||||
-rw-r--r-- | cryptfs/cryptfs.go | 1 | ||||
-rw-r--r-- | cryptfs/cryptfs_content.go | 20 | ||||
-rw-r--r-- | cryptfs/file_header.go | 56 | ||||
-rw-r--r-- | cryptfs/intrablock.go | 2 |
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 |