Port from go-fuse to bazil/fuse
This commit is contained in:
commit
4e93fdf820
42
backend.go
Normal file
42
backend.go
Normal file
@ -0,0 +1,42 @@
|
||||
package gocryptfs
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/aes"
|
||||
)
|
||||
|
||||
const (
|
||||
NONCE_LEN = 12
|
||||
AUTH_TAG_LEN = 16
|
||||
DEFAULT_PLAINBS = 4096
|
||||
|
||||
ENCRYPT = true
|
||||
DECRYPT = false
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
blockCipher cipher.Block
|
||||
gcm cipher.AEAD
|
||||
plainBS int64
|
||||
cipherBS int64
|
||||
}
|
||||
|
||||
func New(key [16]byte) *Backend {
|
||||
|
||||
b, err := aes.NewCipher(key[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
g, err := cipher.NewGCM(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Backend{
|
||||
blockCipher: b,
|
||||
gcm: g,
|
||||
plainBS: DEFAULT_PLAINBS,
|
||||
cipherBS: DEFAULT_PLAINBS + NONCE_LEN + AUTH_TAG_LEN,
|
||||
}
|
||||
}
|
140
backend_content.go
Normal file
140
backend_content.go
Normal file
@ -0,0 +1,140 @@
|
||||
package gocryptfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// readCipherBlock - Read ciphertext block number "blockNo", decrypt,
|
||||
// return plaintext
|
||||
func (be *Backend) readCipherBlock(blockNo int64, f *os.File) ([]byte, error) {
|
||||
off := blockNo * int64(be.cipherBS)
|
||||
buf := make([]byte, be.cipherBS)
|
||||
|
||||
readN, err := f.ReadAt(buf, off)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Truncate buffer to actually read bytes
|
||||
buf = buf[:readN]
|
||||
|
||||
// Empty block?file:///home/jakob/go/src/github.com/rfjakob/gocryptfs-bazil/backend/backend.go
|
||||
|
||||
if len(buf) == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
if len(buf) < NONCE_LEN {
|
||||
warn.Printf("readCipherBlock: Block is too short: %d bytes\n", len(buf))
|
||||
return nil, errors.New("Block is too short")
|
||||
}
|
||||
|
||||
// Extract nonce
|
||||
nonce := buf[:NONCE_LEN]
|
||||
buf = buf[NONCE_LEN:]
|
||||
|
||||
// Decrypt
|
||||
var plainBuf []byte
|
||||
plainBuf, err = be.gcm.Open(plainBuf, nonce, buf, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("gcm.Open() failed: %d\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plainBuf, nil
|
||||
}
|
||||
|
||||
// intraBlock identifies a part of a file block
|
||||
type intraBlock struct {
|
||||
blockNo int64 // Block number in file
|
||||
offset int64 // Offset into block plaintext
|
||||
length int64 // Length of data from this block
|
||||
}
|
||||
|
||||
// Split a plaintext byte range into (possible partial) blocks
|
||||
func (be *Backend) splitRange(offset int64, length int64, f *os.File) []intraBlock {
|
||||
var b intraBlock
|
||||
var parts []intraBlock
|
||||
|
||||
for length > 0 {
|
||||
b.blockNo = offset / be.plainBS
|
||||
b.offset = offset % be.plainBS
|
||||
b.length = be.min64(length, be.plainBS - b.offset)
|
||||
parts = append(parts, b)
|
||||
offset += b.length
|
||||
length -= b.length
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func (be *Backend) min64(x int64, y int64) int64 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// writeCipherBlock - Encrypt plaintext and write it to file block "blockNo"
|
||||
func (be *Backend) writeCipherBlock(blockNo int64, plain []byte, f *os.File) error {
|
||||
|
||||
if int64(len(plain)) > be.plainBS {
|
||||
panic("writeCipherBlock: Cannot write block that is larger than plainBS")
|
||||
}
|
||||
|
||||
// Get fresh nonce
|
||||
nonce := gcmNonce.Get()
|
||||
// Encrypt data and append to nonce
|
||||
cipherBuf := be.gcm.Seal(nonce, nonce, plain, nil)
|
||||
|
||||
// WriteAt retries short writes autmatically
|
||||
written, err := f.WriteAt(cipherBuf, blockNo * be.cipherBS)
|
||||
|
||||
debug.Printf("writeCipherBlock: wrote %d ciphertext bytes to block %d\n",
|
||||
written, blockNo)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform RMW cycle on block
|
||||
// Write "data" into file location specified in "b"
|
||||
func (be *Backend) rmwWrite(b intraBlock, data []byte, f *os.File) error {
|
||||
if b.length != int64(len(data)) {
|
||||
panic("Length mismatch")
|
||||
}
|
||||
|
||||
oldBlock, err := be.readCipherBlock(b.blockNo, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBlockLen := b.offset + b.length
|
||||
debug.Printf("newBlockLen := %d + %d\n", b.offset, b.length)
|
||||
var newBlock []byte
|
||||
|
||||
// Write goes beyond the old block and grows the file?
|
||||
// Must create a bigger newBlock
|
||||
if newBlockLen > int64(len(oldBlock)) {
|
||||
newBlock = make([]byte, newBlockLen)
|
||||
} else {
|
||||
newBlock = make([]byte, len(oldBlock))
|
||||
}
|
||||
|
||||
// Fill with old data
|
||||
copy(newBlock, oldBlock)
|
||||
// Then overwrite the relevant parts with new data
|
||||
copy(newBlock[b.offset:b.offset + b.length], data)
|
||||
|
||||
// Actual write
|
||||
err = be.writeCipherBlock(b.blockNo, newBlock, f)
|
||||
|
||||
if err != nil {
|
||||
// An incomplete write to a ciphertext block means that the whole block
|
||||
// is destroyed.
|
||||
fmt.Printf("rmwWrite: Write error: %s\n", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
155
backend_metadata.go
Normal file
155
backend_metadata.go
Normal file
@ -0,0 +1,155 @@
|
||||
package gocryptfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"encoding/base64"
|
||||
"crypto/cipher"
|
||||
"crypto/aes"
|
||||
)
|
||||
|
||||
// DecryptName - decrypt filename
|
||||
func (be *Backend) decryptName(cipherName string) (string, error) {
|
||||
|
||||
bin, err := base64.URLEncoding.DecodeString(cipherName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(bin) % aes.BlockSize != 0 {
|
||||
return "", errors.New(fmt.Sprintf("Name len=%d is not a multiple of 16", len(bin)))
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize) // TODO ?
|
||||
cbc := cipher.NewCBCDecrypter(be.blockCipher, iv)
|
||||
cbc.CryptBlocks(bin, bin)
|
||||
|
||||
bin, err = be.unPad16(bin)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plain := string(bin)
|
||||
return plain, err
|
||||
}
|
||||
|
||||
// EncryptName - encrypt filename
|
||||
func (be *Backend) encryptName(plainName string) string {
|
||||
|
||||
bin := []byte(plainName)
|
||||
bin = be.pad16(bin)
|
||||
|
||||
iv := make([]byte, 16) // TODO ?
|
||||
cbc := cipher.NewCBCEncrypter(be.blockCipher, iv)
|
||||
cbc.CryptBlocks(bin, bin)
|
||||
|
||||
cipherName64 := base64.URLEncoding.EncodeToString(bin)
|
||||
|
||||
return cipherName64
|
||||
}
|
||||
|
||||
// TranslatePath - encrypt or decrypt path. Just splits the string on "/"
|
||||
// and hands the parts to EncryptName() / DecryptName()
|
||||
func (be *Backend) translatePath(path string, op bool) (string, error) {
|
||||
var err error
|
||||
|
||||
// Empty string means root directory
|
||||
if path == "" {
|
||||
return path, err
|
||||
}
|
||||
|
||||
// Run operation on each path component
|
||||
var translatedParts []string
|
||||
parts := strings.Split(path, "/")
|
||||
for _, part := range parts {
|
||||
var newPart string
|
||||
if op == ENCRYPT {
|
||||
newPart = be.encryptName(part)
|
||||
} else {
|
||||
newPart, err = be.decryptName(part)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
translatedParts = append(translatedParts, newPart)
|
||||
}
|
||||
|
||||
return strings.Join(translatedParts, "/"), err
|
||||
}
|
||||
|
||||
// EncryptPath - encrypt filename or path. Just hands it to TranslatePath().
|
||||
func (be *Backend) EncryptPath(path string) string {
|
||||
newPath, _ := be.translatePath(path, ENCRYPT)
|
||||
return newPath
|
||||
}
|
||||
|
||||
// DecryptPath - decrypt filename or path. Just hands it to TranslatePath().
|
||||
func (be *Backend) DecryptPath(path string) (string, error) {
|
||||
return be.translatePath(path, DECRYPT)
|
||||
}
|
||||
|
||||
// plainSize - calculate plaintext size from ciphertext size
|
||||
func (be *Backend) PlainSize(s int64) int64 {
|
||||
// Zero sized files stay zero-sized
|
||||
if s > 0 {
|
||||
// Number of blocks
|
||||
n := s / be.cipherBS + 1
|
||||
overhead := be.cipherBS - be.plainBS
|
||||
s -= n * overhead
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
|
||||
// https://tools.ietf.org/html/rfc5652#section-6.3
|
||||
func (be *Backend) pad16(orig []byte) (padded []byte) {
|
||||
oldLen := len(orig)
|
||||
if oldLen == 0 {
|
||||
panic("Padding zero-length string makes no sense")
|
||||
}
|
||||
padLen := aes.BlockSize - oldLen % aes.BlockSize
|
||||
if padLen == 0 {
|
||||
padLen = aes.BlockSize
|
||||
}
|
||||
newLen := oldLen + padLen
|
||||
padded = make([]byte, newLen)
|
||||
copy(padded, orig)
|
||||
padByte := byte(padLen)
|
||||
for i := oldLen; i < newLen; i++ {
|
||||
padded[i] = padByte
|
||||
}
|
||||
return padded
|
||||
}
|
||||
|
||||
// unPad16 - remove padding
|
||||
func (be *Backend) unPad16(orig []byte) ([]byte, error) {
|
||||
oldLen := len(orig)
|
||||
if oldLen % aes.BlockSize != 0 {
|
||||
return nil, errors.New("Unaligned size")
|
||||
}
|
||||
// The last byte is always a padding byte
|
||||
padByte := orig[oldLen -1]
|
||||
// The padding byte's value is the padding length
|
||||
padLen := int(padByte)
|
||||
// Padding must be at least 1 byte
|
||||
if padLen <= 0 {
|
||||
return nil, errors.New("Padding cannot be zero-length")
|
||||
}
|
||||
// Larger paddings make no sense
|
||||
if padLen > aes.BlockSize {
|
||||
return nil, errors.New("Padding cannot be larger than 16")
|
||||
}
|
||||
// All padding bytes must be identical
|
||||
for i := oldLen - padLen; i < oldLen; i++ {
|
||||
if orig[i] != padByte {
|
||||
return nil, errors.New(fmt.Sprintf("Padding byte at i=%d is invalid", i))
|
||||
}
|
||||
}
|
||||
newLen := oldLen - padLen
|
||||
// Padding an empty string makes no sense
|
||||
if newLen == 0 {
|
||||
return nil, errors.New("Unpadded length is zero")
|
||||
}
|
||||
return orig[0:newLen], nil
|
||||
}
|
55
backend_nonce.go
Normal file
55
backend_nonce.go
Normal file
@ -0,0 +1,55 @@
|
||||
package gocryptfs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
type nonce96 struct {
|
||||
lock sync.Mutex
|
||||
high32 uint32
|
||||
low64 uint64
|
||||
ready int
|
||||
}
|
||||
|
||||
var gcmNonce nonce96
|
||||
|
||||
func (n *nonce96) randBytes(len int) []byte {
|
||||
b := make([]byte, len)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic("Could not get random bytes for nonce")
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (n *nonce96) init() {
|
||||
b := n.randBytes(8)
|
||||
n.low64 = binary.BigEndian.Uint64(b)
|
||||
b = n.randBytes(4)
|
||||
n.high32 = binary.BigEndian.Uint32(b)
|
||||
n.ready = 1
|
||||
return
|
||||
}
|
||||
|
||||
func (n *nonce96) Get() []byte {
|
||||
n.lock.Lock()
|
||||
if n.ready == 0 {
|
||||
n.init()
|
||||
}
|
||||
n.low64++
|
||||
if n.low64 == 0 {
|
||||
// Counter has wrapped
|
||||
n.high32++
|
||||
}
|
||||
r := make([]byte, 12)
|
||||
binary.BigEndian.PutUint32(r[0:4], n.high32)
|
||||
binary.BigEndian.PutUint64(r[4:12], n.low64)
|
||||
n.lock.Unlock()
|
||||
|
||||
debug.Printf("nonce96.Get(): %s\n", hex.EncodeToString(r))
|
||||
|
||||
return r
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user