aboutsummaryrefslogtreecommitdiff
path: root/cryptfs/cryptfs_content.go
blob: 46585295e2787264cee2c05a253c935c8c3a868b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package cryptfs

// File content encryption / decryption

import (
	"bytes"
	"os"
	"errors"
	"crypto/cipher"
)

type CryptFile struct {
	file *os.File
	gcm cipher.AEAD
}

// DecryptBlocks - Decrypt a number of blocks
func (be *CryptFS) DecryptBlocks(ciphertext []byte) ([]byte, error) {
	cBuf := bytes.NewBuffer(ciphertext)
	var err error
	var pBuf bytes.Buffer
	for cBuf.Len() > 0 {
		cBlock := cBuf.Next(int(be.cipherBS))
		pBlock, err := be.DecryptBlock(cBlock)
		if err != nil {
			break
		}
		pBuf.Write(pBlock)
	}
	return pBuf.Bytes(), err
}

// DecryptBlock - Verify and decrypt GCM block
func (be *CryptFS) DecryptBlock(ciphertext []byte) ([]byte, error) {

	// Empty block?
	if len(ciphertext) == 0 {
		return ciphertext, nil
	}

	if len(ciphertext) < NONCE_LEN {
		Warn.Printf("decryptBlock: Block is too short: %d bytes\n", len(ciphertext))
		return nil, errors.New("Block is too short")
	}

	// Extract nonce
	nonce := ciphertext[:NONCE_LEN]
	ciphertext = ciphertext[NONCE_LEN:]

	// Decrypt
	var plaintext []byte

	plaintext, err := be.gcm.Open(plaintext, nonce, ciphertext, nil)

	if err != nil {
		Warn.Printf("DecryptBlock: %s\n", err.Error())
		return nil, err
	}

	return plaintext, nil
}

// encryptBlock - Encrypt and add MAC using GCM
func (be *CryptFS) EncryptBlock(plaintext []byte) []byte {

	// Empty block?
	if len(plaintext) == 0 {
		return plaintext
	}

	// Get fresh nonce
	nonce := gcmNonce.Get()

	// Encrypt plaintext and append to nonce
	ciphertext := be.gcm.Seal(nonce, nonce, plaintext, nil)

	return ciphertext
}

// Split a plaintext byte range into (possible 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.Offset = offset % be.plainBS
		b.Length = be.minu64(length, be.plainBS - b.Offset)
		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 {
		overhead := be.cipherBS - be.plainBS
		nBlocks := (size + be.cipherBS - 1) / be.cipherBS
		size -= nBlocks * overhead
	}
	return size
}

func (be *CryptFS) minu64(x uint64, y uint64) uint64 {
	if x < y {
		return x
	}
	return y
}

// 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].Offset
	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:len(plaintext)]
	} else {
		cropped = plaintext[offset:offset+length]
	}
	return cropped
}