Major refactoring: Split up "cryptfs" into several internal packages
"git status" for reference: deleted: cryptfs/cryptfs.go deleted: cryptfs/names_core.go modified: integration_tests/cli_test.go modified: integration_tests/helpers.go renamed: cryptfs/config_file.go -> internal/configfile/config_file.go renamed: cryptfs/config_test.go -> internal/configfile/config_test.go renamed: cryptfs/config_test/.gitignore -> internal/configfile/config_test/.gitignore renamed: cryptfs/config_test/PlaintextNames.conf -> internal/configfile/config_test/PlaintextNames.conf renamed: cryptfs/config_test/StrangeFeature.conf -> internal/configfile/config_test/StrangeFeature.conf renamed: cryptfs/config_test/v1.conf -> internal/configfile/config_test/v1.conf renamed: cryptfs/config_test/v2.conf -> internal/configfile/config_test/v2.conf renamed: cryptfs/kdf.go -> internal/configfile/kdf.go renamed: cryptfs/kdf_test.go -> internal/configfile/kdf_test.go renamed: cryptfs/cryptfs_content.go -> internal/contentenc/content.go new file: internal/contentenc/content_api.go renamed: cryptfs/content_test.go -> internal/contentenc/content_test.go renamed: cryptfs/file_header.go -> internal/contentenc/file_header.go renamed: cryptfs/intrablock.go -> internal/contentenc/intrablock.go renamed: cryptfs/address_translation.go -> internal/contentenc/offsets.go new file: internal/cryptocore/crypto_api.go renamed: cryptfs/gcm_go1.4.go -> internal/cryptocore/gcm_go1.4.go renamed: cryptfs/gcm_go1.5.go -> internal/cryptocore/gcm_go1.5.go renamed: cryptfs/nonce.go -> internal/cryptocore/nonce.go renamed: cryptfs/openssl_aead.go -> internal/cryptocore/openssl_aead.go renamed: cryptfs/openssl_benchmark.bash -> internal/cryptocore/openssl_benchmark.bash renamed: cryptfs/openssl_test.go -> internal/cryptocore/openssl_test.go new file: internal/nametransform/name_api.go new file: internal/nametransform/names_core.go renamed: cryptfs/names_diriv.go -> internal/nametransform/names_diriv.go renamed: cryptfs/names_noiv.go -> internal/nametransform/names_noiv.go renamed: cryptfs/names_test.go -> internal/nametransform/names_test.go new file: internal/nametransform/pad16.go renamed: cryptfs/log.go -> internal/toggledlog/log.go renamed: cryptfs/log_go1.4.go -> internal/toggledlog/log_go1.4.go renamed: cryptfs/log_go1.5.go -> internal/toggledlog/log_go1.5.go modified: main.go modified: masterkey.go modified: pathfs_frontend/file.go modified: pathfs_frontend/file_holes.go modified: pathfs_frontend/fs.go modified: pathfs_frontend/fs_dir.go modified: pathfs_frontend/names.go modified: test.bash
This commit is contained in:
parent
adcfbd79a8
commit
2b8cbd9441
@ -1,83 +0,0 @@
|
||||
package cryptfs
|
||||
|
||||
// CryptFS is the crypto backend of GoCryptFS
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
PROGRAM_NAME = "gocryptfs"
|
||||
|
||||
DEFAULT_PLAINBS = 4096
|
||||
KEY_LEN = 32 // AES-256
|
||||
AUTH_TAG_LEN = 16
|
||||
DIRIV_LEN = 16 // identical to AES block size
|
||||
DIRIV_FILENAME = "gocryptfs.diriv"
|
||||
)
|
||||
|
||||
type CryptFS struct {
|
||||
blockCipher cipher.Block
|
||||
gcm cipher.AEAD
|
||||
gcmIVLen int
|
||||
gcmIVGen nonceGenerator
|
||||
plainBS uint64
|
||||
cipherBS uint64
|
||||
// Stores an all-zero block of size cipherBS
|
||||
allZeroBlock []byte
|
||||
// DirIV cache for filename encryption
|
||||
DirIVCache dirIVCache
|
||||
}
|
||||
|
||||
func NewCryptFS(key []byte, useOpenssl bool, plaintextNames bool, GCMIV128 bool) *CryptFS {
|
||||
|
||||
if len(key) != KEY_LEN {
|
||||
panic(fmt.Sprintf("Unsupported key length %d", len(key)))
|
||||
}
|
||||
|
||||
b, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// We want the IV size in bytes
|
||||
gcmIV := 96 / 8
|
||||
if GCMIV128 {
|
||||
gcmIV = 128 / 8
|
||||
}
|
||||
|
||||
var gcm cipher.AEAD
|
||||
if useOpenssl {
|
||||
gcm = opensslGCM{key}
|
||||
} else {
|
||||
gcm, err = goGCMWrapper(b, gcmIV)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
plainBS := DEFAULT_PLAINBS
|
||||
cipherBS := plainBS + gcmIV + AUTH_TAG_LEN
|
||||
|
||||
return &CryptFS{
|
||||
blockCipher: b,
|
||||
gcm: gcm,
|
||||
gcmIVLen: gcmIV,
|
||||
gcmIVGen: nonceGenerator{nonceLen: gcmIV},
|
||||
plainBS: uint64(plainBS),
|
||||
cipherBS: uint64(cipherBS),
|
||||
allZeroBlock: make([]byte, cipherBS),
|
||||
}
|
||||
}
|
||||
|
||||
// Get plaintext block size
|
||||
func (be *CryptFS) PlainBS() uint64 {
|
||||
return be.plainBS
|
||||
}
|
||||
|
||||
// Per-block storage overhead
|
||||
func (be *CryptFS) BlockOverhead() uint64 {
|
||||
return be.cipherBS - be.plainBS
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
package cryptfs
|
||||
|
||||
// Filename encryption / decryption functions
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/eme"
|
||||
)
|
||||
|
||||
// DecryptName - decrypt base64-encoded encrypted filename "cipherName"
|
||||
// The used encryption is either CBC or EME, depending on the "EMENames" argument.
|
||||
//
|
||||
// This function is exported because it allows for a very efficient readdir
|
||||
// implementation (read IV once, decrypt all names using this function).
|
||||
func (be *CryptFS) DecryptName(cipherName string, iv []byte, EMENames bool) (string, error) {
|
||||
return be.decryptName(cipherName, iv, EMENames)
|
||||
}
|
||||
|
||||
// decryptName - decrypt base64-encoded encrypted filename "cipherName".
|
||||
// The used encryption is either CBC or EME, depending on the "EMENames" argument.
|
||||
func (be *CryptFS) decryptName(cipherName string, iv []byte, EMENames bool) (string, error) {
|
||||
|
||||
// Make sure relative symlinks still work after encryption
|
||||
// by passing these through unchanged
|
||||
if cipherName == "." || cipherName == ".." {
|
||||
return cipherName, nil
|
||||
}
|
||||
|
||||
bin, err := base64.URLEncoding.DecodeString(cipherName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(bin)%aes.BlockSize != 0 {
|
||||
return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin))
|
||||
}
|
||||
|
||||
if EMENames {
|
||||
bin = eme.Transform(be.blockCipher, iv, bin, eme.DirectionDecrypt)
|
||||
} else {
|
||||
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 "plainName", return base64-encoded "cipherName64"
|
||||
// The used encryption is either CBC or EME, depending on the "EMENames" argument.
|
||||
func (be *CryptFS) encryptName(plainName string, iv []byte, EMENames bool) (cipherName64 string) {
|
||||
|
||||
// Make sure relative symlinks still work after encryption
|
||||
// by passing these trough unchanged
|
||||
if plainName == "." || plainName == ".." {
|
||||
return plainName
|
||||
}
|
||||
|
||||
bin := []byte(plainName)
|
||||
bin = be.pad16(bin)
|
||||
|
||||
if EMENames {
|
||||
bin = eme.Transform(be.blockCipher, iv, bin, eme.DirectionEncrypt)
|
||||
} else {
|
||||
cbc := cipher.NewCBCEncrypter(be.blockCipher, iv)
|
||||
cbc.CryptBlocks(bin, bin)
|
||||
}
|
||||
|
||||
cipherName64 = base64.URLEncoding.EncodeToString(bin)
|
||||
return cipherName64
|
||||
}
|
||||
|
||||
// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
|
||||
// https://tools.ietf.org/html/rfc5652#section-6.3
|
||||
func (be *CryptFS) 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 *CryptFS) 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
|
||||
}
|
@ -7,7 +7,8 @@ import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||
)
|
||||
|
||||
// Test -init flag
|
||||
@ -26,7 +27,7 @@ func TestInit(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = os.Stat(dir + cryptfs.ConfDefaultName)
|
||||
_, err = os.Stat(dir + configfile.ConfDefaultName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -96,22 +97,22 @@ func TestInitPlaintextNames(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = os.Stat(dir + cryptfs.ConfDefaultName)
|
||||
_, err = os.Stat(dir + configfile.ConfDefaultName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = os.Stat(dir + cryptfs.DIRIV_FILENAME)
|
||||
_, err = os.Stat(dir + nametransform.DirIVFilename)
|
||||
if err == nil {
|
||||
t.Errorf("gocryptfs.diriv should not have been created with -plaintextnames")
|
||||
}
|
||||
_, cf, err := cryptfs.LoadConfFile(dir+cryptfs.ConfDefaultName, "test")
|
||||
_, cf, err := configfile.LoadConfFile(dir+configfile.ConfDefaultName, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !cf.IsFeatureFlagSet(cryptfs.FlagPlaintextNames) {
|
||||
if !cf.IsFeatureFlagSet(configfile.FlagPlaintextNames) {
|
||||
t.Error("PlaintextNames flag should be set but isnt")
|
||||
}
|
||||
if cf.IsFeatureFlagSet(cryptfs.FlagEMENames) || cf.IsFeatureFlagSet(cryptfs.FlagDirIV) {
|
||||
if cf.IsFeatureFlagSet(configfile.FlagEMENames) || cf.IsFeatureFlagSet(configfile.FlagDirIV) {
|
||||
t.Error("FlagEMENames and FlagDirIV should be not set")
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||
)
|
||||
|
||||
// Note: the code assumes that all have a trailing slash
|
||||
@ -42,7 +42,7 @@ func resetTmpDir() {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = cryptfs.WriteDirIV(defaultCipherDir)
|
||||
err = nametransform.WriteDirIV(defaultCipherDir)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@ -58,9 +58,9 @@ func mount(c string, p string, extraArgs ...string) {
|
||||
args = append(args, c)
|
||||
args = append(args, p)
|
||||
cmd := exec.Command(gocryptfsBinary, args...)
|
||||
cmd.Stderr = os.Stderr
|
||||
if testing.Verbose() {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
|
@ -1,10 +1,14 @@
|
||||
package cryptfs
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
)
|
||||
import "os"
|
||||
|
||||
@ -36,10 +40,10 @@ type ConfFile struct {
|
||||
func CreateConfFile(filename string, password string, plaintextNames bool, logN int) error {
|
||||
var cf ConfFile
|
||||
cf.filename = filename
|
||||
cf.Version = HEADER_CURRENT_VERSION
|
||||
cf.Version = contentenc.CurrentVersion
|
||||
|
||||
// Generate new random master key
|
||||
key := RandBytes(KEY_LEN)
|
||||
key := cryptocore.RandBytes(cryptocore.KeyLen)
|
||||
|
||||
// Encrypt it using the password
|
||||
// This sets ScryptObject and EncryptedKey
|
||||
@ -75,11 +79,11 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
|
||||
// Unmarshal
|
||||
err = json.Unmarshal(js, &cf)
|
||||
if err != nil {
|
||||
Warn.Printf("Failed to unmarshal config file")
|
||||
toggledlog.Warn.Printf("Failed to unmarshal config file")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cf.Version != HEADER_CURRENT_VERSION {
|
||||
if cf.Version != contentenc.CurrentVersion {
|
||||
return nil, nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version)
|
||||
}
|
||||
|
||||
@ -95,11 +99,13 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
|
||||
// Unlock master key using password-based key
|
||||
// We use stock go GCM instead of OpenSSL here as speed is not important
|
||||
// and we get better error messages
|
||||
cfs := NewCryptFS(scryptHash, false, false, false)
|
||||
key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
cc := cryptocore.New(scryptHash, false, false)
|
||||
ce := contentenc.New(cc, 4096)
|
||||
|
||||
key, err := ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
if err != nil {
|
||||
Warn.Printf("failed to unlock master key: %s", err.Error())
|
||||
Warn.Printf("Password incorrect.")
|
||||
toggledlog.Warn.Printf("failed to unlock master key: %s", err.Error())
|
||||
toggledlog.Warn.Printf("Password incorrect.")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@ -116,8 +122,9 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) {
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
// Lock master key using password-based key
|
||||
cfs := NewCryptFS(scryptHash, false, false, false)
|
||||
cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil)
|
||||
cc := cryptocore.New(scryptHash, false, false)
|
||||
ce := contentenc.New(cc, 4096)
|
||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
||||
}
|
||||
|
||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,16 +1,19 @@
|
||||
package cryptfs
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
// 1 << 16 uses 64MB of memory,
|
||||
// takes 4 seconds on my Atom Z3735F netbook
|
||||
SCRYPT_DEFAULT_LOGN = 16
|
||||
ScryptDefaultLogN = 16
|
||||
)
|
||||
|
||||
type scryptKdf struct {
|
||||
@ -23,9 +26,9 @@ type scryptKdf struct {
|
||||
|
||||
func NewScryptKdf(logN int) scryptKdf {
|
||||
var s scryptKdf
|
||||
s.Salt = RandBytes(KEY_LEN)
|
||||
s.Salt = cryptocore.RandBytes(cryptocore.KeyLen)
|
||||
if logN <= 0 {
|
||||
s.N = 1 << SCRYPT_DEFAULT_LOGN
|
||||
s.N = 1 << ScryptDefaultLogN
|
||||
} else {
|
||||
if logN < 10 {
|
||||
fmt.Println("Error: scryptn below 10 is too low to make sense. Aborting.")
|
||||
@ -35,7 +38,7 @@ func NewScryptKdf(logN int) scryptKdf {
|
||||
}
|
||||
s.R = 8 // Always 8
|
||||
s.P = 1 // Always 1
|
||||
s.KeyLen = KEY_LEN
|
||||
s.KeyLen = cryptocore.KeyLen
|
||||
return s
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,31 +1,18 @@
|
||||
package cryptfs
|
||||
package contentenc
|
||||
|
||||
// File content encryption / decryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
)
|
||||
|
||||
// md5sum - debug helper, return md5 hex string
|
||||
func md5sum(buf []byte) string {
|
||||
rawHash := md5.Sum(buf)
|
||||
hash := hex.EncodeToString(rawHash[:])
|
||||
return hash
|
||||
}
|
||||
|
||||
type CryptFile struct {
|
||||
file *os.File
|
||||
gcm cipher.AEAD
|
||||
}
|
||||
|
||||
// DecryptBlocks - Decrypt a number of blocks
|
||||
func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) {
|
||||
func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) {
|
||||
cBuf := bytes.NewBuffer(ciphertext)
|
||||
var err error
|
||||
var pBuf bytes.Buffer
|
||||
@ -46,7 +33,7 @@ func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId
|
||||
//
|
||||
// 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, fileId []byte) ([]byte, error) {
|
||||
func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) {
|
||||
|
||||
// Empty block?
|
||||
if len(ciphertext) == 0 {
|
||||
@ -55,30 +42,30 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte
|
||||
|
||||
// All-zero block?
|
||||
if bytes.Equal(ciphertext, be.allZeroBlock) {
|
||||
Debug.Printf("DecryptBlock: file hole encountered")
|
||||
toggledlog.Debug.Printf("DecryptBlock: file hole encountered")
|
||||
return make([]byte, be.plainBS), nil
|
||||
}
|
||||
|
||||
if len(ciphertext) < be.gcmIVLen {
|
||||
Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
|
||||
if len(ciphertext) < be.cryptoCore.IVLen {
|
||||
toggledlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
|
||||
return nil, errors.New("Block is too short")
|
||||
}
|
||||
|
||||
// Extract nonce
|
||||
nonce := ciphertext[:be.gcmIVLen]
|
||||
nonce := ciphertext[:be.cryptoCore.IVLen]
|
||||
ciphertextOrig := ciphertext
|
||||
ciphertext = ciphertext[be.gcmIVLen:]
|
||||
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
||||
|
||||
// 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)
|
||||
plaintext, err := be.cryptoCore.Gcm.Open(plaintext, nonce, ciphertext, aData)
|
||||
|
||||
if err != nil {
|
||||
Warn.Printf("DecryptBlock: %s, len=%d, md5=%s", err.Error(), len(ciphertextOrig), md5sum(ciphertextOrig))
|
||||
Debug.Println(hex.Dump(ciphertextOrig))
|
||||
toggledlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
|
||||
toggledlog.Debug.Println(hex.Dump(ciphertextOrig))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -86,7 +73,7 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte
|
||||
}
|
||||
|
||||
// encryptBlock - Encrypt and add IV and MAC
|
||||
func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte {
|
||||
func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte {
|
||||
|
||||
// Empty block?
|
||||
if len(plaintext) == 0 {
|
||||
@ -94,7 +81,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte)
|
||||
}
|
||||
|
||||
// Get fresh nonce
|
||||
nonce := be.gcmIVGen.Get()
|
||||
nonce := be.cryptoCore.GcmIVGen.Get()
|
||||
|
||||
// Authenticate block with block number and file ID
|
||||
aData := make([]byte, 8)
|
||||
@ -102,14 +89,14 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte)
|
||||
aData = append(aData, fileID...)
|
||||
|
||||
// Encrypt plaintext and append to nonce
|
||||
ciphertext := be.gcm.Seal(nonce, nonce, plaintext, aData)
|
||||
ciphertext := be.cryptoCore.Gcm.Seal(nonce, nonce, plaintext, aData)
|
||||
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
// MergeBlocks - Merge newData into oldData at offset
|
||||
// New block may be bigger than both newData and oldData
|
||||
func (be *CryptFS) MergeBlocks(oldData []byte, newData []byte, offset int) []byte {
|
||||
func (be *ContentEnc) MergeBlocks(oldData []byte, newData []byte, offset int) []byte {
|
||||
|
||||
// Make block of maximum size
|
||||
out := make([]byte, be.plainBS)
|
31
internal/contentenc/content_api.go
Normal file
31
internal/contentenc/content_api.go
Normal file
@ -0,0 +1,31 @@
|
||||
package contentenc
|
||||
|
||||
import "github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
|
||||
type ContentEnc struct {
|
||||
// Cryptographic primitives
|
||||
cryptoCore *cryptocore.CryptoCore
|
||||
// Plaintext block size
|
||||
plainBS uint64
|
||||
// Ciphertext block size
|
||||
cipherBS uint64
|
||||
// All-zero block of size cipherBS, for fast compares
|
||||
allZeroBlock []byte
|
||||
}
|
||||
|
||||
func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
||||
|
||||
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
||||
|
||||
return &ContentEnc{
|
||||
cryptoCore: cc,
|
||||
plainBS: plainBS,
|
||||
cipherBS: cipherBS,
|
||||
allZeroBlock: make([]byte, cipherBS),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (be *ContentEnc) PlainBS() uint64 {
|
||||
return be.plainBS
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package contentenc
|
||||
|
||||
import (
|
||||
"testing"
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package contentenc
|
||||
|
||||
// Per-file header
|
||||
//
|
||||
@ -7,10 +7,14 @@ package cryptfs
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
HEADER_CURRENT_VERSION = 2 // Current on-disk-format version
|
||||
// Current On-Disk-Format version
|
||||
CurrentVersion = 2
|
||||
|
||||
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
|
||||
@ -23,7 +27,7 @@ type FileHeader struct {
|
||||
|
||||
// Pack - serialize fileHeader object
|
||||
func (h *FileHeader) Pack() []byte {
|
||||
if len(h.Id) != HEADER_ID_LEN || h.Version != HEADER_CURRENT_VERSION {
|
||||
if len(h.Id) != HEADER_ID_LEN || h.Version != CurrentVersion {
|
||||
panic("FileHeader object not properly initialized")
|
||||
}
|
||||
buf := make([]byte, HEADER_LEN)
|
||||
@ -40,8 +44,8 @@ func ParseHeader(buf []byte) (*FileHeader, error) {
|
||||
}
|
||||
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)
|
||||
if h.Version != CurrentVersion {
|
||||
return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, CurrentVersion)
|
||||
}
|
||||
h.Id = buf[HEADER_VERSION_LEN:]
|
||||
return &h, nil
|
||||
@ -50,7 +54,7 @@ func ParseHeader(buf []byte) (*FileHeader, error) {
|
||||
// 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)
|
||||
h.Version = CurrentVersion
|
||||
h.Id = cryptocore.RandBytes(HEADER_ID_LEN)
|
||||
return &h
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package cryptfs
|
||||
package contentenc
|
||||
|
||||
// intraBlock identifies a part of a file block
|
||||
type intraBlock struct {
|
||||
BlockNo uint64 // Block number in file
|
||||
Skip uint64 // Offset into block plaintext
|
||||
Length uint64 // Length of data from this block
|
||||
fs *CryptFS
|
||||
fs *ContentEnc
|
||||
}
|
||||
|
||||
// isPartial - is the block partial? This means we have to do read-modify-write.
|
@ -1,29 +1,33 @@
|
||||
package cryptfs
|
||||
package contentenc
|
||||
|
||||
// CryptFS methods that translate offsets between ciphertext and plaintext
|
||||
import (
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
)
|
||||
|
||||
// Contentenc methods that translate offsets between ciphertext and plaintext
|
||||
|
||||
// get the block number at plain-text offset
|
||||
func (be *CryptFS) PlainOffToBlockNo(plainOffset uint64) uint64 {
|
||||
func (be *ContentEnc) PlainOffToBlockNo(plainOffset uint64) uint64 {
|
||||
return plainOffset / be.plainBS
|
||||
}
|
||||
|
||||
// get the block number at ciphter-text offset
|
||||
func (be *CryptFS) CipherOffToBlockNo(cipherOffset uint64) uint64 {
|
||||
func (be *ContentEnc) CipherOffToBlockNo(cipherOffset uint64) uint64 {
|
||||
return (cipherOffset - HEADER_LEN) / be.cipherBS
|
||||
}
|
||||
|
||||
// get ciphertext offset of block "blockNo"
|
||||
func (be *CryptFS) BlockNoToCipherOff(blockNo uint64) uint64 {
|
||||
func (be *ContentEnc) BlockNoToCipherOff(blockNo uint64) uint64 {
|
||||
return HEADER_LEN + blockNo*be.cipherBS
|
||||
}
|
||||
|
||||
// get plaintext offset of block "blockNo"
|
||||
func (be *CryptFS) BlockNoToPlainOff(blockNo uint64) uint64 {
|
||||
func (be *ContentEnc) BlockNoToPlainOff(blockNo uint64) uint64 {
|
||||
return blockNo * be.plainBS
|
||||
}
|
||||
|
||||
// PlainSize - calculate plaintext size from ciphertext size
|
||||
func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
|
||||
// Zero sized files stay zero-sized
|
||||
if cipherSize == 0 {
|
||||
@ -31,12 +35,12 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
}
|
||||
|
||||
if cipherSize == HEADER_LEN {
|
||||
Warn.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
|
||||
toggledlog.Warn.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
|
||||
return 0
|
||||
}
|
||||
|
||||
if cipherSize < HEADER_LEN {
|
||||
Warn.Printf("cipherSize %d < header size: corrupt file\n", cipherSize)
|
||||
toggledlog.Warn.Printf("cipherSize %d < header size: corrupt file\n", cipherSize)
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -50,7 +54,7 @@ func (be *CryptFS) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
}
|
||||
|
||||
// CipherSize - calculate ciphertext size from plaintext size
|
||||
func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 {
|
||||
func (be *ContentEnc) PlainSizeToCipherSize(plainSize uint64) uint64 {
|
||||
|
||||
// Block number at last byte
|
||||
blockNo := be.PlainOffToBlockNo(plainSize - 1)
|
||||
@ -62,7 +66,7 @@ func (be *CryptFS) PlainSizeToCipherSize(plainSize uint64) uint64 {
|
||||
}
|
||||
|
||||
// Split a plaintext byte range into (possibly partial) blocks
|
||||
func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock {
|
||||
func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []intraBlock {
|
||||
var blocks []intraBlock
|
||||
var nextBlock intraBlock
|
||||
nextBlock.fs = be
|
||||
@ -81,6 +85,10 @@ func (be *CryptFS) ExplodePlainRange(offset uint64, length uint64) []intraBlock
|
||||
return blocks
|
||||
}
|
||||
|
||||
func (be *ContentEnc) BlockOverhead() uint64 {
|
||||
return be.cipherBS - be.plainBS
|
||||
}
|
||||
|
||||
func MinUint64(x uint64, y uint64) uint64 {
|
||||
if x < y {
|
||||
return x
|
56
internal/cryptocore/crypto_api.go
Normal file
56
internal/cryptocore/crypto_api.go
Normal file
@ -0,0 +1,56 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/aes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyLen = 32 // AES-256
|
||||
AuthTagLen = 16
|
||||
)
|
||||
|
||||
type CryptoCore struct {
|
||||
BlockCipher cipher.Block
|
||||
Gcm cipher.AEAD
|
||||
GcmIVGen *nonceGenerator
|
||||
IVLen int
|
||||
}
|
||||
|
||||
func New(key []byte, useOpenssl bool, GCMIV128 bool) *CryptoCore {
|
||||
|
||||
if len(key) != KeyLen {
|
||||
panic(fmt.Sprintf("Unsupported key length %d", len(key)))
|
||||
}
|
||||
|
||||
// We want the IV size in bytes
|
||||
IVLen := 96 / 8
|
||||
if GCMIV128 {
|
||||
IVLen = 128 / 8
|
||||
}
|
||||
|
||||
// We always use built-in Go crypto for blockCipher because it is not
|
||||
// performance-critical.
|
||||
blockCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var gcm cipher.AEAD
|
||||
if useOpenssl {
|
||||
gcm = opensslGCM{key}
|
||||
} else {
|
||||
gcm, err = goGCMWrapper(blockCipher, IVLen)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &CryptoCore{
|
||||
BlockCipher: blockCipher,
|
||||
Gcm: gcm,
|
||||
GcmIVGen: &nonceGenerator{nonceLen: IVLen},
|
||||
IVLen: IVLen,
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// +build !go1.5
|
||||
// = go 1.4 or lower
|
||||
|
||||
package cryptfs
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
@ -1,7 +1,7 @@
|
||||
// +build go1.5
|
||||
// = go 1.5 or higher
|
||||
|
||||
package cryptfs
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
)
|
||||
|
||||
// Get "n" random bytes from /dev/urandom or panic
|
||||
@ -29,10 +31,10 @@ type nonceGenerator struct {
|
||||
nonceLen int // bytes
|
||||
}
|
||||
|
||||
// Get a random 96 bit nonce
|
||||
// Get a random "nonceLen"-byte nonce
|
||||
func (n *nonceGenerator) Get() []byte {
|
||||
nonce := RandBytes(n.nonceLen)
|
||||
Debug.Printf("nonceGenerator.Get(): %s\n", hex.EncodeToString(nonce))
|
||||
toggledlog.Debug.Printf("nonceGenerator.Get(): %s\n", hex.EncodeToString(nonce))
|
||||
if bytes.Equal(nonce, n.lastNonce) {
|
||||
m := fmt.Sprintf("Got the same nonce twice: %s. This should never happen!", hex.EncodeToString(nonce))
|
||||
panic(m)
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package cryptocore
|
||||
|
||||
// Implements cipher.AEAD with OpenSSL backend
|
||||
|
||||
@ -13,7 +13,7 @@ type opensslGCM struct {
|
||||
}
|
||||
|
||||
func (be opensslGCM) Overhead() int {
|
||||
return AUTH_TAG_LEN
|
||||
return AuthTagLen
|
||||
}
|
||||
|
||||
func (be opensslGCM) NonceSize() int {
|
||||
@ -28,11 +28,11 @@ func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
|
||||
// Preallocate output buffer
|
||||
var cipherBuf bytes.Buffer
|
||||
cipherBuf.Grow(len(dst) + len(plaintext) + AUTH_TAG_LEN)
|
||||
cipherBuf.Grow(len(dst) + len(plaintext) + AuthTagLen)
|
||||
// Output will be appended to dst
|
||||
cipherBuf.Write(dst)
|
||||
|
||||
ectx, err := openssl.NewGCMEncryptionCipherCtx(KEY_LEN*8, nil, be.key, nonce)
|
||||
ectx, err := openssl.NewGCMEncryptionCipherCtx(KeyLen*8, nil, be.key, nonce)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -69,11 +69,11 @@ func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
func (be opensslGCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
|
||||
l := len(ciphertext)
|
||||
tag := ciphertext[l-AUTH_TAG_LEN : l]
|
||||
ciphertext = ciphertext[0 : l-AUTH_TAG_LEN]
|
||||
tag := ciphertext[l-AuthTagLen : l]
|
||||
ciphertext = ciphertext[0 : l-AuthTagLen]
|
||||
plainBuf := bytes.NewBuffer(dst)
|
||||
|
||||
dctx, err := openssl.NewGCMDecryptionCipherCtx(KEY_LEN*8, nil, be.key, nonce)
|
||||
dctx, err := openssl.NewGCMDecryptionCipherCtx(KeyLen*8, nil, be.key, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package cryptocore
|
||||
|
||||
// Benchmark go built-int GCM against spacemonkey openssl bindings
|
||||
//
|
16
internal/nametransform/name_api.go
Normal file
16
internal/nametransform/name_api.go
Normal file
@ -0,0 +1,16 @@
|
||||
package nametransform
|
||||
|
||||
import "github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
|
||||
type NameTransform struct {
|
||||
cryptoCore *cryptocore.CryptoCore
|
||||
useEME bool
|
||||
DirIVCache dirIVCache
|
||||
}
|
||||
|
||||
func New(c *cryptocore.CryptoCore, useEME bool) *NameTransform {
|
||||
return &NameTransform{
|
||||
cryptoCore: c,
|
||||
useEME: useEME,
|
||||
}
|
||||
}
|
63
internal/nametransform/names_core.go
Normal file
63
internal/nametransform/names_core.go
Normal file
@ -0,0 +1,63 @@
|
||||
package nametransform
|
||||
|
||||
// Filename encryption / decryption functions
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/eme"
|
||||
)
|
||||
|
||||
// DecryptName - decrypt base64-encoded encrypted filename "cipherName"
|
||||
// The used encryption is either CBC or EME, depending on "useEME".
|
||||
//
|
||||
// This function is exported because it allows for a very efficient readdir
|
||||
// implementation (read IV once, decrypt all names using this function).
|
||||
func (n *NameTransform) DecryptName(cipherName string, iv []byte) (string, error) {
|
||||
|
||||
bin, err := base64.URLEncoding.DecodeString(cipherName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(bin)%aes.BlockSize != 0 {
|
||||
return "", fmt.Errorf("Decoded length %d is not a multiple of the AES block size", len(bin))
|
||||
}
|
||||
|
||||
if n.useEME {
|
||||
bin = eme.Transform(n.cryptoCore.BlockCipher, iv, bin, eme.DirectionDecrypt)
|
||||
} else {
|
||||
cbc := cipher.NewCBCDecrypter(n.cryptoCore.BlockCipher, iv)
|
||||
cbc.CryptBlocks(bin, bin)
|
||||
}
|
||||
|
||||
bin, err = unPad16(bin)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plain := string(bin)
|
||||
return plain, err
|
||||
}
|
||||
|
||||
// encryptName - encrypt "plainName", return base64-encoded "cipherName64"
|
||||
// The used encryption is either CBC or EME, depending on "useEME".
|
||||
func (n *NameTransform) encryptName(plainName string, iv []byte) (cipherName64 string) {
|
||||
|
||||
bin := []byte(plainName)
|
||||
bin = pad16(bin)
|
||||
|
||||
if n.useEME {
|
||||
bin = eme.Transform(n.cryptoCore.BlockCipher, iv, bin, eme.DirectionEncrypt)
|
||||
} else {
|
||||
cbc := cipher.NewCBCEncrypter(n.cryptoCore.BlockCipher, iv)
|
||||
cbc.CryptBlocks(bin, bin)
|
||||
}
|
||||
|
||||
cipherName64 = base64.URLEncoding.EncodeToString(bin)
|
||||
return cipherName64
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -7,6 +7,17 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
// identical to AES block size
|
||||
dirIVLen = 16
|
||||
// dirIV is stored in this file. Exported because we have to ignore this
|
||||
// name in directory listing.
|
||||
DirIVFilename = "gocryptfs.diriv"
|
||||
)
|
||||
|
||||
// A simple one-entry DirIV cache
|
||||
@ -50,23 +61,23 @@ func (c *dirIVCache) Clear() {
|
||||
}
|
||||
|
||||
// readDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path)
|
||||
func (be *CryptFS) ReadDirIV(dir string) (iv []byte, readErr error) {
|
||||
ivfile := filepath.Join(dir, DIRIV_FILENAME)
|
||||
Debug.Printf("ReadDirIV: reading %s\n", ivfile)
|
||||
func (be *NameTransform) ReadDirIV(dir string) (iv []byte, readErr error) {
|
||||
ivfile := filepath.Join(dir, DirIVFilename)
|
||||
toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile)
|
||||
iv, readErr = ioutil.ReadFile(ivfile)
|
||||
if readErr != nil {
|
||||
// The directory may have been concurrently deleted or moved. Failure to
|
||||
// read the diriv is not an error in that case.
|
||||
_, statErr := os.Stat(dir)
|
||||
if os.IsNotExist(statErr) {
|
||||
Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir)
|
||||
toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir)
|
||||
} else {
|
||||
// This should not happen
|
||||
Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr)
|
||||
toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr)
|
||||
}
|
||||
return nil, readErr
|
||||
}
|
||||
if len(iv) != DIRIV_LEN {
|
||||
if len(iv) != dirIVLen {
|
||||
return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv))
|
||||
}
|
||||
return iv, nil
|
||||
@ -76,14 +87,14 @@ func (be *CryptFS) ReadDirIV(dir string) (iv []byte, readErr error) {
|
||||
// This function is exported because it is used from pathfs_frontend, main,
|
||||
// and also the automated tests.
|
||||
func WriteDirIV(dir string) error {
|
||||
iv := RandBytes(DIRIV_LEN)
|
||||
file := filepath.Join(dir, DIRIV_FILENAME)
|
||||
iv := cryptocore.RandBytes(dirIVLen)
|
||||
file := filepath.Join(dir, DirIVFilename)
|
||||
// 0444 permissions: the file is not secret but should not be written to
|
||||
return ioutil.WriteFile(file, iv, 0444)
|
||||
}
|
||||
|
||||
// EncryptPathDirIV - encrypt path using EME with DirIV
|
||||
func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool) (cipherPath string, err error) {
|
||||
func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) {
|
||||
// Empty string means root directory
|
||||
if plainPath == "" {
|
||||
return plainPath, nil
|
||||
@ -94,7 +105,7 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool)
|
||||
if found {
|
||||
//fmt.Print("h")
|
||||
baseName := filepath.Base(plainPath)
|
||||
cBaseName := be.encryptName(baseName, iv, eme)
|
||||
cBaseName := be.encryptName(baseName, iv)
|
||||
cipherPath = cParentDir + "/" + cBaseName
|
||||
return cipherPath, nil
|
||||
}
|
||||
@ -107,7 +118,7 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encryptedName := be.encryptName(plainName, iv, eme)
|
||||
encryptedName := be.encryptName(plainName, iv)
|
||||
encryptedNames = append(encryptedNames, encryptedName)
|
||||
wd = filepath.Join(wd, encryptedName)
|
||||
}
|
||||
@ -119,17 +130,17 @@ func (be *CryptFS) EncryptPathDirIV(plainPath string, rootDir string, eme bool)
|
||||
}
|
||||
|
||||
// DecryptPathDirIV - decrypt path using EME with DirIV
|
||||
func (be *CryptFS) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) {
|
||||
func (be *NameTransform) DecryptPathDirIV(encryptedPath string, rootDir string, eme bool) (string, error) {
|
||||
var wd = rootDir
|
||||
var plainNames []string
|
||||
encryptedNames := strings.Split(encryptedPath, "/")
|
||||
Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames)
|
||||
toggledlog.Debug.Printf("DecryptPathDirIV: decrypting %v\n", encryptedNames)
|
||||
for _, encryptedName := range encryptedNames {
|
||||
iv, err := be.ReadDirIV(wd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
plainName, err := be.decryptName(encryptedName, iv, eme)
|
||||
plainName, err := be.DecryptName(encryptedName, iv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@ -12,7 +12,7 @@ const (
|
||||
// DecryptPathNoIV - decrypt path using CBC without any IV.
|
||||
// This function is deprecated by the the more secure DirIV variant and only retained
|
||||
// for compatability with old filesystems.
|
||||
func (be *CryptFS) DecryptPathNoIV(cipherPath string) (plainPath string, err error) {
|
||||
func (be *NameTransform) DecryptPathNoIV(cipherPath string) (plainPath string, err error) {
|
||||
plainPath, err = be.translatePathNoIV(cipherPath, OpDecrypt)
|
||||
return plainPath, err
|
||||
}
|
||||
@ -20,14 +20,14 @@ func (be *CryptFS) DecryptPathNoIV(cipherPath string) (plainPath string, err err
|
||||
// EncryptPathNoIV - decrypt path using CBC without any IV.
|
||||
// This function is deprecated by the the more secure DirIV variant and only retained
|
||||
// for compatability with old filesystems.
|
||||
func (be *CryptFS) EncryptPathNoIV(plainPath string) (cipherPath string) {
|
||||
func (be *NameTransform) EncryptPathNoIV(plainPath string) (cipherPath string) {
|
||||
cipherPath, _ = be.translatePathNoIV(plainPath, OpEncrypt)
|
||||
return cipherPath
|
||||
}
|
||||
|
||||
// translatePathZeroIV - encrypt or decrypt path using CBC with an all-zero IV.
|
||||
// Just splits the string on "/" and hands the parts to encryptName() / decryptName()
|
||||
func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) {
|
||||
func (be *NameTransform) translatePathNoIV(path string, op int) (string, error) {
|
||||
var err error
|
||||
|
||||
// Empty string means root directory
|
||||
@ -35,7 +35,7 @@ func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) {
|
||||
return path, err
|
||||
}
|
||||
|
||||
zeroIV := make([]byte, DIRIV_LEN)
|
||||
zeroIV := make([]byte, dirIVLen)
|
||||
|
||||
// Run operation on each path component
|
||||
var translatedParts []string
|
||||
@ -49,9 +49,9 @@ func (be *CryptFS) translatePathNoIV(path string, op int) (string, error) {
|
||||
}
|
||||
var newPart string
|
||||
if op == OpEncrypt {
|
||||
newPart = be.encryptName(part, zeroIV, false)
|
||||
newPart = be.encryptName(part, zeroIV)
|
||||
} else {
|
||||
newPart, err = be.decryptName(part, zeroIV, false)
|
||||
newPart, err = be.DecryptName(part, zeroIV)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"bytes"
|
60
internal/nametransform/pad16.go
Normal file
60
internal/nametransform/pad16.go
Normal file
@ -0,0 +1,60 @@
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"crypto/aes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// pad16 - pad data to AES block size (=16 byte) using standard PKCS#7 padding
|
||||
// https://tools.ietf.org/html/rfc5652#section-6.3
|
||||
func 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 unPad16(padded []byte) ([]byte, error) {
|
||||
oldLen := len(padded)
|
||||
if oldLen%aes.BlockSize != 0 {
|
||||
return nil, errors.New("Unaligned size")
|
||||
}
|
||||
// The last byte is always a padding byte
|
||||
padByte := padded[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, fmt.Errorf("Padding too long, padLen = %d > 16", padLen)
|
||||
}
|
||||
// All padding bytes must be identical
|
||||
for i := oldLen - padLen; i < oldLen; i++ {
|
||||
if padded[i] != padByte {
|
||||
return nil, fmt.Errorf("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 padded[0:newLen], nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cryptfs
|
||||
package toggledlog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -7,6 +7,10 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
ProgramName = "gocryptfs"
|
||||
)
|
||||
|
||||
func JSONDump(obj interface{}) string {
|
||||
b, err := json.MarshalIndent(obj, "", "\t")
|
||||
if err != nil {
|
@ -1,7 +1,7 @@
|
||||
// +build !go1.5
|
||||
// = go 1.4 or lower
|
||||
|
||||
package cryptfs
|
||||
package toggledlog
|
||||
|
||||
import (
|
||||
"log/syslog"
|
@ -1,14 +1,14 @@
|
||||
// +build go1.5
|
||||
// = go 1.5 or higher
|
||||
|
||||
package cryptfs
|
||||
package toggledlog
|
||||
|
||||
import (
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func (l *toggledLogger) SwitchToSyslog(p syslog.Priority) {
|
||||
w, err := syslog.New(p, PROGRAM_NAME)
|
||||
w, err := syslog.New(p, ProgramName)
|
||||
if err != nil {
|
||||
Warn.Printf("Cannot switch 0x%02x to syslog: %v", p, err)
|
||||
} else {
|
106
main.go
106
main.go
@ -16,12 +16,16 @@ import (
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
||||
"github.com/rfjakob/gocryptfs/pathfs_frontend"
|
||||
|
||||
"github.com/hanwen/go-fuse/fuse"
|
||||
"github.com/hanwen/go-fuse/fuse/nodefs"
|
||||
"github.com/hanwen/go-fuse/fuse/pathfs"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/pathfs_frontend"
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -56,9 +60,9 @@ func initDir(args *argContainer) {
|
||||
}
|
||||
|
||||
// Create gocryptfs.conf
|
||||
cryptfs.Info.Printf("Choose a password for protecting your files.")
|
||||
toggledlog.Info.Printf("Choose a password for protecting your files.")
|
||||
password := readPasswordTwice(args.extpass)
|
||||
err = cryptfs.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn)
|
||||
err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(ERREXIT_INIT)
|
||||
@ -66,30 +70,30 @@ func initDir(args *argContainer) {
|
||||
|
||||
if args.diriv && !args.plaintextnames {
|
||||