From 7f87ed78f2f27831f2fa9409106846e3288c6f6e Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Tue, 20 Sep 2016 21:58:04 +0200 Subject: cryptocore: add support for GCM-SIV --- internal/configfile/config_file.go | 4 +-- internal/configfile/feature_flags.go | 4 ++- internal/contentenc/content.go | 8 +++-- internal/contentenc/content_test.go | 6 ++-- internal/cryptocore/cryptocore.go | 48 ++++++++++++++++++---------- internal/cryptocore/cryptocore_go1.4_test.go | 5 +-- internal/cryptocore/cryptocore_go1.5_test.go | 2 +- internal/cryptocore/cryptocore_test.go | 14 +++----- internal/fusefrontend/args.go | 6 +++- internal/fusefrontend/fs.go | 2 +- internal/fusefrontend_reverse/rfs.go | 2 +- main.go | 20 +++++++++--- 12 files changed, 75 insertions(+), 46 deletions(-) diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index b36980f..178890b 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -139,7 +139,7 @@ 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 we only use 96-bit IVs, // 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) 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) // 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) cf.EncryptedKey = ce.EncryptBlock(key, 0, nil) } diff --git a/internal/configfile/feature_flags.go b/internal/configfile/feature_flags.go index bac8ce8..90b8c22 100644 --- a/internal/configfile/feature_flags.go +++ b/internal/configfile/feature_flags.go @@ -8,6 +8,7 @@ const ( FlagEMENames FlagGCMIV128 FlagLongNames + FlagGCMSIV ) // knownFlags stores the known feature flags and their string representation @@ -17,6 +18,7 @@ var knownFlags map[flagIota]string = map[flagIota]string{ FlagEMENames: "EMENames", FlagGCMIV128: "GCMIV128", FlagLongNames: "LongNames", + FlagGCMSIV: "GCMSIV", } // 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 -// related feature flags. +// filename related feature flags. var requiredFlagsPlaintextNames []flagIota = []flagIota{ FlagGCMIV128, } diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index 375221a..e132536 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -14,6 +14,8 @@ import ( const ( // Default plaintext block size DefaultBS = 4096 + // We always use 128-bit IVs for file content encryption + IVBitLen = 128 ) type ContentEnc struct { @@ -100,7 +102,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b aData := make([]byte, 8) aData = append(aData, fileId...) 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 { 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 - nonce := be.cryptoCore.GcmIVGen.Get() + nonce := be.cryptoCore.IVGenerator.Get() // Authenticate block with block number and file ID aData := make([]byte, 8) @@ -141,7 +143,7 @@ func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []by aData = append(aData, fileID...) // 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 } diff --git a/internal/contentenc/content_test.go b/internal/contentenc/content_test.go index 299c8c8..faa2780 100644 --- a/internal/contentenc/content_test.go +++ b/internal/contentenc/content_test.go @@ -23,7 +23,7 @@ func TestSplitRange(t *testing.T) { testRange{6654, 8945}) key := make([]byte, cryptocore.KeyLen) - cc := cryptocore.New(key, true, true) + cc := cryptocore.New(key, cryptocore.BackendOpenSSL, IVBitLen) f := New(cc, DefaultBS) for _, r := range ranges { @@ -51,7 +51,7 @@ func TestCiphertextRange(t *testing.T) { testRange{6654, 8945}) key := make([]byte, cryptocore.KeyLen) - cc := cryptocore.New(key, true, true) + cc := cryptocore.New(key, cryptocore.BackendOpenSSL, IVBitLen) f := New(cc, DefaultBS) for _, r := range ranges { @@ -74,7 +74,7 @@ func TestCiphertextRange(t *testing.T) { func TestBlockNo(t *testing.T) { key := make([]byte, cryptocore.KeyLen) - cc := cryptocore.New(key, true, true) + cc := cryptocore.New(key, cryptocore.BackendOpenSSL, IVBitLen) f := New(cc, DefaultBS) b := f.CipherOffToBlockNo(788) diff --git a/internal/cryptocore/cryptocore.go b/internal/cryptocore/cryptocore.go index 1839aa2..a6708bd 100644 --- a/internal/cryptocore/cryptocore.go +++ b/internal/cryptocore/cryptocore.go @@ -8,17 +8,29 @@ import ( "fmt" "github.com/rfjakob/gocryptfs/internal/stupidgcm" + + "github.com/rfjakob/gcmsiv" ) +type BackendTypeEnum int + const ( KeyLen = 32 // AES-256 AuthTagLen = 16 + + _ = iota // Skip zero + BackendOpenSSL BackendTypeEnum = iota + BackendGoGCM BackendTypeEnum = iota + BackendGCMSIV BackendTypeEnum = iota ) type CryptoCore struct { + // AES-256 block cipher. This is used for EME filename encryption. BlockCipher cipher.Block - Gcm cipher.AEAD - GcmIVGen *nonceGenerator + // GCM or GCM-SIV. This is used for content encryption. + AEADCipher cipher.AEAD + // GCM needs unique IVs (nonces) + IVGenerator *nonceGenerator IVLen int } @@ -27,17 +39,12 @@ type CryptoCore struct { // 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 // 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 { panic(fmt.Sprintf("Unsupported key length %d", len(key))) } - // We want the IV size in bytes - IVLen := 96 / 8 - if GCMIV128 { - IVLen = 128 / 8 - } + IVLen := IVBitLen / 8 // Name encryption always uses built-in Go AES through BlockCipher. // 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 - if useOpenssl && GCMIV128 { - // stupidgcm only supports 128-bit IVs + switch backend { + case BackendOpenSSL: + if IVLen != 16 { + panic("stupidgcm only supports 128-bit IVs") + } gcm = stupidgcm.New(key) - } else { + case BackendGoGCM: gcm, err = goGCMWrapper(blockCipher, IVLen) - if err != nil { - panic(err) - } + case BackendGCMSIV: + gcm, err = gcmsiv.NewGCMSIV(key) + default: + panic("unknown backend cipher") + } + if err != nil { + panic(err) } return &CryptoCore{ BlockCipher: blockCipher, - Gcm: gcm, - GcmIVGen: &nonceGenerator{nonceLen: IVLen}, + AEADCipher: gcm, + IVGenerator: &nonceGenerator{nonceLen: IVLen}, IVLen: IVLen, } } diff --git a/internal/cryptocore/cryptocore_go1.4_test.go b/internal/cryptocore/cryptocore_go1.4_test.go index 3460d02..14e1e03 100644 --- a/internal/cryptocore/cryptocore_go1.4_test.go +++ b/internal/cryptocore/cryptocore_go1.4_test.go @@ -7,7 +7,8 @@ import ( "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) { defer func() { if r := recover(); r == nil { @@ -15,5 +16,5 @@ func TestCryptoCoreNewGo14(t *testing.T) { } }() key := make([]byte, 32) - New(key, false, true) + New(key, BackendGoGCM, 128) } diff --git a/internal/cryptocore/cryptocore_go1.5_test.go b/internal/cryptocore/cryptocore_go1.5_test.go index 1c93254..f9d38e9 100644 --- a/internal/cryptocore/cryptocore_go1.5_test.go +++ b/internal/cryptocore/cryptocore_go1.5_test.go @@ -9,7 +9,7 @@ import ( func TestCryptoCoreNewGo15(t *testing.T) { key := make([]byte, 32) - c := New(key, false, true) + c := New(key, BackendGoGCM, 128) if c.IVLen != 16 { t.Fail() } diff --git a/internal/cryptocore/cryptocore_test.go b/internal/cryptocore/cryptocore_test.go index 1151591..9da6059 100644 --- a/internal/cryptocore/cryptocore_test.go +++ b/internal/cryptocore/cryptocore_test.go @@ -4,23 +4,19 @@ import ( "testing" ) -// "New" should accept all param combinations +// "New" should accept at least these param combinations func TestCryptoCoreNew(t *testing.T) { key := make([]byte, 32) - c := New(key, true, true) + c := New(key, BackendOpenSSL, 128) if c.IVLen != 16 { t.Fail() } - c = New(key, true, false) + c = New(key, BackendGoGCM, 96) if c.IVLen != 12 { t.Fail() } - c = New(key, false, false) - if c.IVLen != 12 { - t.Fail() - } - // "New(key, false, true)" is tested for Go 1.4 and 1.5+ seperately + // "New(key, BackendGoGCM, 128)" is tested for Go 1.4 and 1.5+ seperately } // "New" should panic on any key not 32 bytes long @@ -32,5 +28,5 @@ func TestNewPanic(t *testing.T) { }() key := make([]byte, 16) - New(key, true, true) + New(key, BackendOpenSSL, 128) } diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 78b9b5b..d0e1835 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -1,10 +1,14 @@ package fusefrontend +import ( + "github.com/rfjakob/gocryptfs/internal/cryptocore" +) + // Container for arguments that are passed from main() to fusefrontend type Args struct { Masterkey []byte Cipherdir string - OpenSSL bool + CryptoBackend cryptocore.BackendTypeEnum PlaintextNames bool LongNames bool // Should we chown a file after it has been created? diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index bc81c37..575865e 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -37,7 +37,7 @@ type FS struct { // Encrypted FUSE overlay filesystem 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) nameTransform := nametransform.New(cryptoCore, args.LongNames) diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index e20c851..4b04b86 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -36,7 +36,7 @@ type reverseFS struct { // Encrypted FUSE overlay filesystem 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) nameTransform := nametransform.New(cryptoCore, args.LongNames) diff --git a/main.go b/main.go index 1f7230a..1877779 100644 --- a/main.go +++ b/main.go @@ -282,20 +282,30 @@ func main() { // initFuseFrontend - initialize gocryptfs/fusefrontend // Calls os.Exit on errors func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFile) *fuse.Server { - - // Reconciliate CLI and config file arguments into a Args struct that is passed to the - // filesystem implementation + // Reconciliate CLI and config file arguments into a fusefrontend.Args struct + // that is passed to the filesystem implementation + cryptoBackend := cryptocore.BackendGoGCM + if args.openssl { + cryptoBackend = cryptocore.BackendOpenSSL + } + if args.reverse { + // reverse implies GCMSIV + cryptoBackend = cryptocore.BackendGCMSIV + } frontendArgs := fusefrontend.Args{ Cipherdir: args.cipherdir, Masterkey: key, - OpenSSL: args.openssl, PlaintextNames: args.plaintextnames, LongNames: args.longnames, + CryptoBackend: cryptoBackend, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil { // Settings from the config file override command line args 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 // the right user. @@ -308,7 +318,7 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi var finalFs pathfs.FileSystem if args.reverse { 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 { finalFs = fusefrontend.NewFS(frontendArgs) } -- cgit v1.2.3