contentenc: add GCM-SIV support
Also add ReverseDummyNonce nonce generation.
This commit is contained in:
parent
90f0bdc224
commit
5f726aaa9d
@ -39,7 +39,7 @@ func initDir(args *argContainer) {
|
|||||||
}
|
}
|
||||||
password := readpassword.Twice(args.extpass)
|
password := readpassword.Twice(args.extpass)
|
||||||
creator := tlog.ProgramName + " " + GitVersion
|
creator := tlog.ProgramName + " " + GitVersion
|
||||||
err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator)
|
err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.reverse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Println(err)
|
tlog.Fatal.Println(err)
|
||||||
os.Exit(ERREXIT_INIT)
|
os.Exit(ERREXIT_INIT)
|
||||||
|
@ -45,7 +45,7 @@ type ConfFile struct {
|
|||||||
// CreateConfFile - create a new config with a random key encrypted with
|
// CreateConfFile - create a new config with a random key encrypted with
|
||||||
// "password" and write it to "filename".
|
// "password" and write it to "filename".
|
||||||
// Uses scrypt with cost parameter logN.
|
// Uses scrypt with cost parameter logN.
|
||||||
func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string) error {
|
func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string, reverse bool) error {
|
||||||
var cf ConfFile
|
var cf ConfFile
|
||||||
cf.filename = filename
|
cf.filename = filename
|
||||||
cf.Creator = creator
|
cf.Creator = creator
|
||||||
@ -67,6 +67,9 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN
|
|||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames])
|
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames])
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames])
|
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames])
|
||||||
}
|
}
|
||||||
|
if reverse {
|
||||||
|
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMSIV])
|
||||||
|
}
|
||||||
|
|
||||||
// Write file to disk
|
// Write file to disk
|
||||||
return cf.WriteFile()
|
return cf.WriteFile()
|
||||||
@ -165,7 +168,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) {
|
|||||||
// Lock master key using password-based key
|
// Lock master key using password-based key
|
||||||
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, 96)
|
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, 96)
|
||||||
ce := contentenc.New(cc, 4096)
|
ce := contentenc.New(cc, 4096)
|
||||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil, contentenc.RandomNonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||||
|
@ -60,7 +60,7 @@ func TestLoadV2StrangeFeature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateConfFile(t *testing.T) {
|
func TestCreateConfFile(t *testing.T) {
|
||||||
err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test")
|
err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -71,6 +71,20 @@ func TestCreateConfFile(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateConfFileReverse(t *testing.T) {
|
||||||
|
err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, c, err := LoadConfFile("config_test/tmp.conf", "test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !c.IsFeatureFlagSet(FlagGCMSIV) {
|
||||||
|
t.Error("GCMSIV flag should be set but is not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsFeatureFlagKnown(t *testing.T) {
|
func TestIsFeatureFlagKnown(t *testing.T) {
|
||||||
// Test a few hardcoded values
|
// Test a few hardcoded values
|
||||||
testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames"}
|
testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames"}
|
||||||
|
@ -11,11 +11,17 @@ import (
|
|||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NonceMode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Default plaintext block size
|
// Default plaintext block size
|
||||||
DefaultBS = 4096
|
DefaultBS = 4096
|
||||||
// We always use 128-bit IVs for file content encryption
|
// We always use 128-bit IVs for file content encryption
|
||||||
IVBitLen = 128
|
IVBitLen = 128
|
||||||
|
|
||||||
|
_ = iota // skip zero
|
||||||
|
RandomNonce NonceMode = iota
|
||||||
|
ReverseDummyNonce NonceMode = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContentEnc struct {
|
type ContentEnc struct {
|
||||||
@ -27,6 +33,8 @@ type ContentEnc struct {
|
|||||||
cipherBS uint64
|
cipherBS uint64
|
||||||
// All-zero block of size cipherBS, for fast compares
|
// All-zero block of size cipherBS, for fast compares
|
||||||
allZeroBlock []byte
|
allZeroBlock []byte
|
||||||
|
// All-zero block of size IVBitLen/8, for fast compares
|
||||||
|
allZeroNonce []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
||||||
@ -38,6 +46,7 @@ func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
|||||||
plainBS: plainBS,
|
plainBS: plainBS,
|
||||||
cipherBS: cipherBS,
|
cipherBS: cipherBS,
|
||||||
allZeroBlock: make([]byte, cipherBS),
|
allZeroBlock: make([]byte, cipherBS),
|
||||||
|
allZeroNonce: make([]byte, IVBitLen/8),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +103,9 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b
|
|||||||
|
|
||||||
// Extract nonce
|
// Extract nonce
|
||||||
nonce := ciphertext[:be.cryptoCore.IVLen]
|
nonce := ciphertext[:be.cryptoCore.IVLen]
|
||||||
|
if bytes.Equal(nonce, be.allZeroNonce) && be.cryptoCore.AEADBackend != cryptocore.BackendGCMSIV {
|
||||||
|
panic("Hit an all-zero nonce with GCMSIV off. This MUST NOT happen!")
|
||||||
|
}
|
||||||
ciphertextOrig := ciphertext
|
ciphertextOrig := ciphertext
|
||||||
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
||||||
|
|
||||||
@ -115,27 +127,38 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b
|
|||||||
|
|
||||||
// EncryptBlocks - Encrypt a number of blocks
|
// EncryptBlocks - Encrypt a number of blocks
|
||||||
// Used for reverse mode
|
// Used for reverse mode
|
||||||
func (be *ContentEnc) EncryptBlocks(plaintext []byte, firstBlockNo uint64, fileId []byte) []byte {
|
func (be *ContentEnc) EncryptBlocks(plaintext []byte, firstBlockNo uint64, fileId []byte, nMode NonceMode) []byte {
|
||||||
inBuf := bytes.NewBuffer(plaintext)
|
inBuf := bytes.NewBuffer(plaintext)
|
||||||
var outBuf bytes.Buffer
|
var outBuf bytes.Buffer
|
||||||
for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ {
|
for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ {
|
||||||
inBlock := inBuf.Next(int(be.plainBS))
|
inBlock := inBuf.Next(int(be.plainBS))
|
||||||
outBlock := be.EncryptBlock(inBlock, blockNo, fileId)
|
outBlock := be.EncryptBlock(inBlock, blockNo, fileId, nMode)
|
||||||
outBuf.Write(outBlock)
|
outBuf.Write(outBlock)
|
||||||
}
|
}
|
||||||
return outBuf.Bytes()
|
return outBuf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptBlock - Encrypt and add IV and MAC
|
// encryptBlock - Encrypt and add IV and MAC
|
||||||
func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte {
|
func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte, nMode NonceMode) []byte {
|
||||||
|
|
||||||
// Empty block?
|
// Empty block?
|
||||||
if len(plaintext) == 0 {
|
if len(plaintext) == 0 {
|
||||||
return plaintext
|
return plaintext
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get fresh nonce
|
var nonce []byte
|
||||||
nonce := be.cryptoCore.IVGenerator.Get()
|
switch nMode {
|
||||||
|
case ReverseDummyNonce:
|
||||||
|
if be.cryptoCore.AEADBackend != cryptocore.BackendGCMSIV {
|
||||||
|
panic("MUST NOT use dummy nonces unless in GCMSIV mode!")
|
||||||
|
}
|
||||||
|
nonce = make([]byte, IVBitLen/8)
|
||||||
|
binary.BigEndian.PutUint64(nonce, blockNo)
|
||||||
|
case RandomNonce:
|
||||||
|
// Get a fresh random nonce
|
||||||
|
nonce = be.cryptoCore.IVGenerator.Get()
|
||||||
|
default:
|
||||||
|
panic("invalid nonce mode")
|
||||||
|
}
|
||||||
|
|
||||||
// Authenticate block with block number and file ID
|
// Authenticate block with block number and file ID
|
||||||
aData := make([]byte, 8)
|
aData := make([]byte, 8)
|
||||||
|
@ -29,6 +29,8 @@ type CryptoCore struct {
|
|||||||
BlockCipher cipher.Block
|
BlockCipher cipher.Block
|
||||||
// GCM or GCM-SIV. This is used for content encryption.
|
// GCM or GCM-SIV. This is used for content encryption.
|
||||||
AEADCipher cipher.AEAD
|
AEADCipher cipher.AEAD
|
||||||
|
// Which backend is behind AEADCipher?
|
||||||
|
AEADBackend BackendTypeEnum
|
||||||
// GCM needs unique IVs (nonces)
|
// GCM needs unique IVs (nonces)
|
||||||
IVGenerator *nonceGenerator
|
IVGenerator *nonceGenerator
|
||||||
IVLen int
|
IVLen int
|
||||||
@ -74,6 +76,7 @@ func New(key []byte, backend BackendTypeEnum, IVBitLen int) *CryptoCore {
|
|||||||
return &CryptoCore{
|
return &CryptoCore{
|
||||||
BlockCipher: blockCipher,
|
BlockCipher: blockCipher,
|
||||||
AEADCipher: gcm,
|
AEADCipher: gcm,
|
||||||
|
AEADBackend: backend,
|
||||||
IVGenerator: &nonceGenerator{nonceLen: IVLen},
|
IVGenerator: &nonceGenerator{nonceLen: IVLen},
|
||||||
IVLen: IVLen,
|
IVLen: IVLen,
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
|||||||
|
|
||||||
// Encrypt
|
// Encrypt
|
||||||
blockOffset := b.BlockCipherOff()
|
blockOffset := b.BlockCipherOff()
|
||||||
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id)
|
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id, contentenc.RandomNonce)
|
||||||
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
|
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
|
||||||
f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
|
f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co
|
|||||||
return fuse.ToStatus(err)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
// Symlinks are encrypted like file contents (GCM) and base64-encoded
|
// Symlinks are encrypted like file contents (GCM) and base64-encoded
|
||||||
cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil)
|
cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil, contentenc.RandomNonce)
|
||||||
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
||||||
|
|
||||||
// Handle long file name
|
// Handle long file name
|
||||||
|
@ -64,7 +64,7 @@ func (rf *reverseFile) readBackingFile(off uint64, length uint64) (out []byte, e
|
|||||||
plaintext = plaintext[0:n]
|
plaintext = plaintext[0:n]
|
||||||
|
|
||||||
// Encrypt blocks
|
// Encrypt blocks
|
||||||
ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileHeader.Id)
|
ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileHeader.Id, contentenc.ReverseDummyNonce)
|
||||||
|
|
||||||
// Crop down to the relevant part
|
// Crop down to the relevant part
|
||||||
lenHave := len(ciphertext)
|
lenHave := len(ciphertext)
|
||||||
|
3
main.go
3
main.go
@ -305,6 +305,9 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi
|
|||||||
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
|
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
|
||||||
if confFile.IsFeatureFlagSet(configfile.FlagGCMSIV) {
|
if confFile.IsFeatureFlagSet(configfile.FlagGCMSIV) {
|
||||||
frontendArgs.CryptoBackend = cryptocore.BackendGCMSIV
|
frontendArgs.CryptoBackend = cryptocore.BackendGCMSIV
|
||||||
|
} else if args.reverse {
|
||||||
|
tlog.Fatal.Printf("GCMSIV is required by reverse mode, but not enabled in the config file")
|
||||||
|
os.Exit(ERREXIT_USAGE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If allow_other is set and we run as root, try to give newly created files to
|
// If allow_other is set and we run as root, try to give newly created files to
|
||||||
|
Loading…
x
Reference in New Issue
Block a user