cryptocore: add support for GCM-SIV

This commit is contained in:
Jakob Unterwurzacher 2016-09-20 21:58:04 +02:00
parent d1762c5b95
commit 7f87ed78f2
12 changed files with 76 additions and 47 deletions

View File

@ -139,7 +139,7 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
// Unlock master key using password-based key // Unlock master key using password-based key
// We use stock go GCM instead of OpenSSL here as we only use 96-bit IVs, // We use stock go GCM instead of OpenSSL here as we only use 96-bit IVs,
// speed is not important and we get better error messages // speed is not important and we get better error messages
cc := cryptocore.New(scryptHash, false, false) cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, 96)
ce := contentenc.New(cc, 4096) ce := contentenc.New(cc, 4096)
tlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password tlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password
@ -163,7 +163,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) {
scryptHash := cf.ScryptObject.DeriveKey(password) scryptHash := cf.ScryptObject.DeriveKey(password)
// Lock master key using password-based key // Lock master key using password-based key
cc := cryptocore.New(scryptHash, false, false) 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)
} }

View File

@ -8,6 +8,7 @@ const (
FlagEMENames FlagEMENames
FlagGCMIV128 FlagGCMIV128
FlagLongNames FlagLongNames
FlagGCMSIV
) )
// knownFlags stores the known feature flags and their string representation // knownFlags stores the known feature flags and their string representation
@ -17,6 +18,7 @@ var knownFlags map[flagIota]string = map[flagIota]string{
FlagEMENames: "EMENames", FlagEMENames: "EMENames",
FlagGCMIV128: "GCMIV128", FlagGCMIV128: "GCMIV128",
FlagLongNames: "LongNames", FlagLongNames: "LongNames",
FlagGCMSIV: "GCMSIV",
} }
// Filesystems that do not have these feature flags set are deprecated. // Filesystems that do not have these feature flags set are deprecated.
@ -27,7 +29,7 @@ var requiredFlagsNormal []flagIota = []flagIota{
} }
// Filesystems without filename encryption obviously don't have or need the // Filesystems without filename encryption obviously don't have or need the
// related feature flags. // filename related feature flags.
var requiredFlagsPlaintextNames []flagIota = []flagIota{ var requiredFlagsPlaintextNames []flagIota = []flagIota{
FlagGCMIV128, FlagGCMIV128,
} }

View File

@ -14,6 +14,8 @@ import (
const ( const (
// Default plaintext block size // Default plaintext block size
DefaultBS = 4096 DefaultBS = 4096
// We always use 128-bit IVs for file content encryption
IVBitLen = 128
) )
type ContentEnc struct { type ContentEnc struct {
@ -100,7 +102,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b
aData := make([]byte, 8) aData := make([]byte, 8)
aData = append(aData, fileId...) aData = append(aData, fileId...)
binary.BigEndian.PutUint64(aData, blockNo) binary.BigEndian.PutUint64(aData, blockNo)
plaintext, err := be.cryptoCore.Gcm.Open(plaintext, nonce, ciphertext, aData) plaintext, err := be.cryptoCore.AEADCipher.Open(plaintext, nonce, ciphertext, aData)
if err != nil { if err != nil {
tlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig)) tlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
@ -133,7 +135,7 @@ func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []by
} }
// Get fresh nonce // Get fresh nonce
nonce := be.cryptoCore.GcmIVGen.Get() nonce := be.cryptoCore.IVGenerator.Get()
// Authenticate block with block number and file ID // Authenticate block with block number and file ID
aData := make([]byte, 8) aData := make([]byte, 8)
@ -141,7 +143,7 @@ func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []by
aData = append(aData, fileID...) aData = append(aData, fileID...)
// Encrypt plaintext and append to nonce // Encrypt plaintext and append to nonce
ciphertext := be.cryptoCore.Gcm.Seal(nonce, nonce, plaintext, aData) ciphertext := be.cryptoCore.AEADCipher.Seal(nonce, nonce, plaintext, aData)
return ciphertext return ciphertext
} }

View File

@ -23,7 +23,7 @@ func TestSplitRange(t *testing.T) {
testRange{6654, 8945}) testRange{6654, 8945})
key := make([]byte, cryptocore.KeyLen) key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, true, true) cc := cryptocore.New(key, cryptocore.BackendOpenSSL, IVBitLen)
f := New(cc, DefaultBS) f := New(cc, DefaultBS)
for _, r := range ranges { for _, r := range ranges {
@ -51,7 +51,7 @@ func TestCiphertextRange(t *testing.T) {
testRange{6654, 8945}) testRange{6654, 8945})
key := make([]byte, cryptocore.KeyLen) key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, true, true) cc := cryptocore.New(key, cryptocore.BackendOpenSSL, IVBitLen)
f := New(cc, DefaultBS) f := New(cc, DefaultBS)
for _, r := range ranges { for _, r := range ranges {
@ -74,7 +74,7 @@ func TestCiphertextRange(t *testing.T) {
func TestBlockNo(t *testing.T) { func TestBlockNo(t *testing.T) {
key := make([]byte, cryptocore.KeyLen) key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, true, true) cc := cryptocore.New(key, cryptocore.BackendOpenSSL, IVBitLen)
f := New(cc, DefaultBS) f := New(cc, DefaultBS)
b := f.CipherOffToBlockNo(788) b := f.CipherOffToBlockNo(788)

View File

@ -8,17 +8,29 @@ import (
"fmt" "fmt"
"github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/stupidgcm"
"github.com/rfjakob/gcmsiv"
) )
type BackendTypeEnum int
const ( const (
KeyLen = 32 // AES-256 KeyLen = 32 // AES-256
AuthTagLen = 16 AuthTagLen = 16
_ = iota // Skip zero
BackendOpenSSL BackendTypeEnum = iota
BackendGoGCM BackendTypeEnum = iota
BackendGCMSIV BackendTypeEnum = iota
) )
type CryptoCore struct { type CryptoCore struct {
// AES-256 block cipher. This is used for EME filename encryption.
BlockCipher cipher.Block BlockCipher cipher.Block
Gcm cipher.AEAD // GCM or GCM-SIV. This is used for content encryption.
GcmIVGen *nonceGenerator AEADCipher cipher.AEAD
// GCM needs unique IVs (nonces)
IVGenerator *nonceGenerator
IVLen int IVLen int
} }
@ -27,17 +39,12 @@ type CryptoCore struct {
// Even though the "GCMIV128" feature flag is now mandatory, we must still // Even though the "GCMIV128" feature flag is now mandatory, we must still
// support 96-bit IVs here because they are used for encrypting the master // support 96-bit IVs here because they are used for encrypting the master
// key in gocryptfs.conf. // key in gocryptfs.conf.
func New(key []byte, useOpenssl bool, GCMIV128 bool) *CryptoCore { func New(key []byte, backend BackendTypeEnum, IVBitLen int) *CryptoCore {
if len(key) != KeyLen { if len(key) != KeyLen {
panic(fmt.Sprintf("Unsupported key length %d", len(key))) panic(fmt.Sprintf("Unsupported key length %d", len(key)))
} }
// We want the IV size in bytes // We want the IV size in bytes
IVLen := 96 / 8 IVLen := IVBitLen / 8
if GCMIV128 {
IVLen = 128 / 8
}
// Name encryption always uses built-in Go AES through BlockCipher. // Name encryption always uses built-in Go AES through BlockCipher.
// Content encryption uses BlockCipher only if useOpenssl=false. // Content encryption uses BlockCipher only if useOpenssl=false.
@ -47,20 +54,27 @@ func New(key []byte, useOpenssl bool, GCMIV128 bool) *CryptoCore {
} }
var gcm cipher.AEAD var gcm cipher.AEAD
if useOpenssl && GCMIV128 { switch backend {
// stupidgcm only supports 128-bit IVs case BackendOpenSSL:
gcm = stupidgcm.New(key) if IVLen != 16 {
} else { panic("stupidgcm only supports 128-bit IVs")
gcm, err = goGCMWrapper(blockCipher, IVLen)
if err != nil {
panic(err)
} }
gcm = stupidgcm.New(key)
case BackendGoGCM:
gcm, err = goGCMWrapper(blockCipher, IVLen)
case BackendGCMSIV:
gcm, err = gcmsiv.NewGCMSIV(key)
default:
panic("unknown backend cipher")
}
if err != nil {
panic(err)
} }
return &CryptoCore{ return &CryptoCore{
BlockCipher: blockCipher, BlockCipher: blockCipher,
Gcm: gcm, AEADCipher: gcm,
GcmIVGen: &nonceGenerator{nonceLen: IVLen}, IVGenerator: &nonceGenerator{nonceLen: IVLen},
IVLen: IVLen, IVLen: IVLen,
} }
} }

View File

@ -7,7 +7,8 @@ import (
"testing" "testing"
) )
// Native Go crypto with 128-bit IVs is only supported on Go 1.5 and up // Native Go crypto with 128-bit IVs is only supported on Go 1.5 and up,
// this should panic.
func TestCryptoCoreNewGo14(t *testing.T) { func TestCryptoCoreNewGo14(t *testing.T) {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
@ -15,5 +16,5 @@ func TestCryptoCoreNewGo14(t *testing.T) {
} }
}() }()
key := make([]byte, 32) key := make([]byte, 32)
New(key, false, true) New(key, BackendGoGCM, 128)
} }

View File

@ -9,7 +9,7 @@ import (
func TestCryptoCoreNewGo15(t *testing.T) { func TestCryptoCoreNewGo15(t *testing.T) {
key := make([]byte, 32) key := make([]byte, 32)
c := New(key, false, true) c := New(key, BackendGoGCM, 128)
if c.IVLen != 16 { if c.IVLen != 16 {
t.Fail() t.Fail()
} }

View File

@ -4,23 +4,19 @@ import (
"testing" "testing"
) )
// "New" should accept all param combinations // "New" should accept at least these param combinations
func TestCryptoCoreNew(t *testing.T) { func TestCryptoCoreNew(t *testing.T) {
key := make([]byte, 32) key := make([]byte, 32)
c := New(key, true, true) c := New(key, BackendOpenSSL, 128)
if c.IVLen != 16 { if c.IVLen != 16 {
t.Fail() t.Fail()
} }
c = New(key, true, false) c = New(key, BackendGoGCM, 96)
if c.IVLen != 12 { if c.IVLen != 12 {
t.Fail() t.Fail()
} }
c = New(key, false, false) // "New(key, BackendGoGCM, 128)" is tested for Go 1.4 and 1.5+ seperately
if c.IVLen != 12 {
t.Fail()
}
// "New(key, false, true)" is tested for Go 1.4 and 1.5+ seperately
} }
// "New" should panic on any key not 32 bytes long // "New" should panic on any key not 32 bytes long
@ -32,5 +28,5 @@ func TestNewPanic(t *testing.T) {
}() }()
key := make([]byte, 16) key := make([]byte, 16)
New(key, true, true) New(key, BackendOpenSSL, 128)
} }

View File

@ -1,10 +1,14 @@
package fusefrontend package fusefrontend
import (
"github.com/rfjakob/gocryptfs/internal/cryptocore"
)
// Container for arguments that are passed from main() to fusefrontend // Container for arguments that are passed from main() to fusefrontend
type Args struct { type Args struct {
Masterkey []byte Masterkey []byte
Cipherdir string Cipherdir string
OpenSSL bool CryptoBackend cryptocore.BackendTypeEnum
PlaintextNames bool PlaintextNames bool
LongNames bool LongNames bool
// Should we chown a file after it has been created? // Should we chown a file after it has been created?

View File

@ -37,7 +37,7 @@ type FS struct {
// Encrypted FUSE overlay filesystem // Encrypted FUSE overlay filesystem
func NewFS(args Args) *FS { func NewFS(args Args) *FS {
cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, true) cryptoCore := cryptocore.New(args.Masterkey, args.CryptoBackend, contentenc.IVBitLen)
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS) contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS)
nameTransform := nametransform.New(cryptoCore, args.LongNames) nameTransform := nametransform.New(cryptoCore, args.LongNames)

View File

@ -36,7 +36,7 @@ type reverseFS struct {
// Encrypted FUSE overlay filesystem // Encrypted FUSE overlay filesystem
func NewFS(args fusefrontend.Args) *reverseFS { func NewFS(args fusefrontend.Args) *reverseFS {
cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, true) cryptoCore := cryptocore.New(args.Masterkey, args.CryptoBackend, contentenc.IVBitLen)
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS) contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS)
nameTransform := nametransform.New(cryptoCore, args.LongNames) nameTransform := nametransform.New(cryptoCore, args.LongNames)

20
main.go
View File

@ -282,20 +282,30 @@ func main() {
// initFuseFrontend - initialize gocryptfs/fusefrontend // initFuseFrontend - initialize gocryptfs/fusefrontend
// Calls os.Exit on errors // Calls os.Exit on errors
func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFile) *fuse.Server { func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFile) *fuse.Server {
// Reconciliate CLI and config file arguments into a fusefrontend.Args struct
// Reconciliate CLI and config file arguments into a Args struct that is passed to the // that is passed to the filesystem implementation
// filesystem implementation cryptoBackend := cryptocore.BackendGoGCM
if args.openssl {
cryptoBackend = cryptocore.BackendOpenSSL
}
if args.reverse {
// reverse implies GCMSIV
cryptoBackend = cryptocore.BackendGCMSIV
}
frontendArgs := fusefrontend.Args{ frontendArgs := fusefrontend.Args{
Cipherdir: args.cipherdir, Cipherdir: args.cipherdir,
Masterkey: key, Masterkey: key,
OpenSSL: args.openssl,
PlaintextNames: args.plaintextnames, PlaintextNames: args.plaintextnames,
LongNames: args.longnames, LongNames: args.longnames,
CryptoBackend: cryptoBackend,
} }
// confFile is nil when "-zerokey" or "-masterkey" was used // confFile is nil when "-zerokey" or "-masterkey" was used
if confFile != nil { if confFile != nil {
// Settings from the config file override command line args // Settings from the config file override command line args
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames) frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
if confFile.IsFeatureFlagSet(configfile.FlagGCMSIV) {
frontendArgs.CryptoBackend = cryptocore.BackendGCMSIV
}
} }
// 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
// the right user. // the right user.
@ -308,7 +318,7 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi
var finalFs pathfs.FileSystem var finalFs pathfs.FileSystem
if args.reverse { if args.reverse {
finalFs = fusefrontend_reverse.NewFS(frontendArgs) finalFs = fusefrontend_reverse.NewFS(frontendArgs)
tlog.Info.Printf(tlog.ColorYellow + "REVERSE MODE IS EXPERIMENTAL" + tlog.ColorReset) tlog.Info.Printf(tlog.ColorYellow + "REVERSE MODE IS EXPERIMENTAL!" + tlog.ColorReset)
} else { } else {
finalFs = fusefrontend.NewFS(frontendArgs) finalFs = fusefrontend.NewFS(frontendArgs)
} }