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 | 
