main: try to wipe cryptocore's secret keys on unmount

Raise the bar for recovering keys from memory.

https://github.com/rfjakob/gocryptfs/issues/211
This commit is contained in:
Jakob Unterwurzacher 2018-02-18 11:33:47 +01:00
parent 719693ec5d
commit 18f6c6106c
4 changed files with 50 additions and 20 deletions

View File

@ -8,11 +8,13 @@ import (
"crypto/sha512" "crypto/sha512"
"fmt" "fmt"
"log" "log"
"runtime"
"github.com/rfjakob/eme" "github.com/rfjakob/eme"
"github.com/rfjakob/gocryptfs/internal/siv_aead" "github.com/rfjakob/gocryptfs/internal/siv_aead"
"github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/stupidgcm"
"github.com/rfjakob/gocryptfs/internal/tlog"
) )
// AEADTypeEnum indicates the type of AEAD backend in use. // AEADTypeEnum indicates the type of AEAD backend in use.
@ -129,3 +131,25 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
IVLen: IVLen, IVLen: IVLen,
} }
} }
// Wipe tries to wipe secret keys from memory by overwriting them with zeros
// and/or setting references to nil.
//
// This is not bulletproof due to possible GC copies, but
// still raises to bar for extracting the key.
func (c *CryptoCore) Wipe() {
if c.AEADBackend == BackendOpenSSL {
tlog.Debug.Print("CryptoCore.Wipe: Wiping stupidgcm key")
// We don't use "x, ok :=" because we *want* to crash loudly if the
// type assertion fails (it should never fail).
sgcm := c.AEADCipher.(*stupidgcm.StupidGCM)
sgcm.Wipe()
} else {
tlog.Debug.Print("CryptoCore.Wipe: niling stdlib refs")
}
// We have no access to the keys (or key-equivalents) stored inside the
// Go stdlib. Best we can is to nil the references and force a GC.
c.AEADCipher = nil
c.EMECipher = nil
runtime.GC()
}

View File

@ -24,32 +24,32 @@ const (
) )
// stupidGCM implements the cipher.AEAD interface // stupidGCM implements the cipher.AEAD interface
type stupidGCM struct { type StupidGCM struct {
key []byte key []byte
forceDecode bool forceDecode bool
} }
// Verify that we satisfy the cipher.AEAD interface // Verify that we satisfy the cipher.AEAD interface
var _ cipher.AEAD = &stupidGCM{} var _ cipher.AEAD = &StupidGCM{}
// New returns a new cipher.AEAD implementation.. // New returns a new cipher.AEAD implementation..
func New(key []byte, forceDecode bool) cipher.AEAD { func New(key []byte, forceDecode bool) cipher.AEAD {
if len(key) != keyLen { if len(key) != keyLen {
log.Panicf("Only %d-byte keys are supported", keyLen) log.Panicf("Only %d-byte keys are supported", keyLen)
} }
return &stupidGCM{key: key, forceDecode: forceDecode} return &StupidGCM{key: key, forceDecode: forceDecode}
} }
func (g *stupidGCM) NonceSize() int { func (g *StupidGCM) NonceSize() int {
return ivLen return ivLen
} }
func (g *stupidGCM) Overhead() int { func (g *StupidGCM) Overhead() int {
return tagLen return tagLen
} }
// Seal encrypts "in" using "iv" and "authData" and append the result to "dst" // Seal encrypts "in" using "iv" and "authData" and append the result to "dst"
func (g *stupidGCM) Seal(dst, iv, in, authData []byte) []byte { func (g *StupidGCM) Seal(dst, iv, in, authData []byte) []byte {
if len(iv) != ivLen { if len(iv) != ivLen {
log.Panicf("Only %d-byte IVs are supported", ivLen) log.Panicf("Only %d-byte IVs are supported", ivLen)
} }
@ -136,7 +136,7 @@ func (g *stupidGCM) Seal(dst, iv, in, authData []byte) []byte {
} }
// Open decrypts "in" using "iv" and "authData" and append the result to "dst" // Open decrypts "in" using "iv" and "authData" and append the result to "dst"
func (g *stupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) { func (g *StupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) {
if len(iv) != ivLen { if len(iv) != ivLen {
log.Panicf("Only %d-byte IVs are supported", ivLen) log.Panicf("Only %d-byte IVs are supported", ivLen)
} }
@ -231,12 +231,12 @@ func (g *stupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) {
return append(dst, buf...), nil return append(dst, buf...), nil
} }
// Wipe wipes the AES key from memory by overwriting it with zeros and // Wipe tries to wipe the AES key from memory by overwriting it with zeros
// setting the reference to nil. // and setting the reference to nil.
// //
// This is not bulletproof due to possible GC copies, but // This is not bulletproof due to possible GC copies, but
// still raises to bar for extracting the key. // still raises to bar for extracting the key.
func (g *stupidGCM) Wipe() { func (g *StupidGCM) Wipe() {
for i := range g.key { for i := range g.key {
g.key[i] = 0 g.key[i] = 0
} }

View File

@ -9,7 +9,7 @@ import (
"github.com/rfjakob/gocryptfs/internal/exitcodes" "github.com/rfjakob/gocryptfs/internal/exitcodes"
) )
type stupidGCM struct{} type StupidGCM struct{}
const ( const (
// BuiltWithoutOpenssl indicates if openssl been disabled at compile-time // BuiltWithoutOpenssl indicates if openssl been disabled at compile-time
@ -21,28 +21,32 @@ func errExit() {
os.Exit(exitcodes.OpenSSL) os.Exit(exitcodes.OpenSSL)
} }
func New(_ []byte, _ bool) *stupidGCM { func New(_ []byte, _ bool) *StupidGCM {
errExit() errExit()
// Never reached // Never reached
return &stupidGCM{} return &StupidGCM{}
} }
func (g *stupidGCM) NonceSize() int { func (g *StupidGCM) NonceSize() int {
errExit() errExit()
return -1 return -1
} }
func (g *stupidGCM) Overhead() int { func (g *StupidGCM) Overhead() int {
errExit() errExit()
return -1 return -1
} }
func (g *stupidGCM) Seal(_, _, _, _ []byte) []byte { func (g *StupidGCM) Seal(_, _, _, _ []byte) []byte {
errExit() errExit()
return nil return nil
} }
func (g *stupidGCM) Open(_, _, _, _ []byte) ([]byte, error) { func (g *StupidGCM) Open(_, _, _, _ []byte) ([]byte, error) {
errExit() errExit()
return nil, nil return nil, nil
} }
func (g *StupidGCM) Wipe() {
errExit()
}

View File

@ -123,7 +123,7 @@ func doMount(args *argContainer) int {
// We cannot use JSON for pretty-printing as the fields are unexported // We cannot use JSON for pretty-printing as the fields are unexported
tlog.Debug.Printf("cli args: %#v", args) tlog.Debug.Printf("cli args: %#v", args)
// Initialize FUSE server // Initialize FUSE server
srv := initFuseFrontend(masterkey, args, confFile) srv, wipeKeys := initFuseFrontend(masterkey, args, confFile)
tlog.Info.Println(tlog.ColorGreen + "Filesystem mounted and ready." + tlog.ColorReset) tlog.Info.Println(tlog.ColorGreen + "Filesystem mounted and ready." + tlog.ColorReset)
// We have been forked into the background, as evidenced by the set // We have been forked into the background, as evidenced by the set
// "notifypid". // "notifypid".
@ -162,6 +162,8 @@ func doMount(args *argContainer) int {
debug.FreeOSMemory() debug.FreeOSMemory()
// Jump into server loop. Returns when it gets an umount request from the kernel. // Jump into server loop. Returns when it gets an umount request from the kernel.
srv.Serve() srv.Serve()
// Try to wipe secrect keys from memory
wipeKeys()
return 0 return 0
} }
@ -194,7 +196,7 @@ type ctlsockFs interface {
// initFuseFrontend - initialize gocryptfs/fusefrontend // initFuseFrontend - initialize gocryptfs/fusefrontend
// Calls os.Exit on errors // Calls os.Exit on errors
func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile.ConfFile) *fuse.Server { func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile.ConfFile) (srv *fuse.Server, wipeKeys func()) {
// Reconciliate CLI and config file arguments into a fusefrontend.Args struct // Reconciliate CLI and config file arguments into a fusefrontend.Args struct
// that is passed to the filesystem implementation // that is passed to the filesystem implementation
cryptoBackend := cryptocore.BackendGoGCM cryptoBackend := cryptocore.BackendGoGCM
@ -361,7 +363,7 @@ func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile
// directories with the requested permissions. // directories with the requested permissions.
syscall.Umask(0000) syscall.Umask(0000)
return srv return srv, func() { cCore.Wipe() }
} }
func handleSigint(srv *fuse.Server, mountpoint string) { func handleSigint(srv *fuse.Server, mountpoint string) {