diff --git a/symmetric.go b/symmetric.go index 587ae5b..afe618f 100644 --- a/symmetric.go +++ b/symmetric.go @@ -27,6 +27,7 @@ import ( "fmt" "github.com/miekg/pkcs11" "github.com/youtube/vitess/go/pools" + "runtime" ) // SymmetricGenParams holds a consistent (key type, mechanism) key generation pair. @@ -454,6 +455,17 @@ func (g gcmAead) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er // Stream encryption/decryption ----------------------------------------- // BlockModeCloser represents a block cipher running in a block-based mode (CBC, ECB etc). +// +// BlockModeCloser implements cipher.BlockMode, and can be used as such. +// However, in this case +// (or if the Close() method is not explicitly called for any other reason), +// resources allocated to it may remain live longer than necessary. +// +// The underlying implementations set a finalizer +// so these resources will eventually be released, +// but if your application has resource consumption problems or hangs +// then adding an explicit Close() call may be the solution. +// If that is not possible then adding calls to runtime.GC() may help. type BlockModeCloser interface { cipher.BlockMode @@ -466,16 +478,34 @@ const ( modeDecrypt // blockModeCloser is in decrypt mode ) -// NewCBCEncrypter returns a BlockModeCloser which encrypts in cipher block chaining mode, using the given key. +// NewCBCEncrypter returns a cipher.BlockMode which encrypts in cipher block chaining mode, using the given key. +// The length of iv must be the same as the key's block size. +// +// The new BlockMode acquires persistent resources which are released (eventually) by a finalizer. +// If this is a problem for your application then use NewCBCEncrypterCloser instead. +func (key *PKCS11SecretKey) NewCBCEncrypter(iv []byte) (bm cipher.BlockMode, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeEncrypt, iv, true) +} + +// NewCBCDecrypter returns a cipher.BlockMode which decrypts in cipher block chaining mode, using the given key. +// The length of iv must be the same as the key's block size and must match the iv used to encrypt the data. +// +// The new BlockMode acquires persistent resources which are released (eventually) by a finalizer. +// If this is a problem for your application then use NewCBCDecrypterCloser instead. +func (key *PKCS11SecretKey) NewCBCDecrypter(iv []byte) (bm cipher.BlockMode, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeDecrypt, iv, true) +} + +// NewCBCEncrypterCloser returns a BlockModeCloser which encrypts in cipher block chaining mode, using the given key. // The length of iv must be the same as the key's block size. -func (key *PKCS11SecretKey) NewCBCEncrypter(iv []byte) (bmc BlockModeCloser, err error) { - return key.newBlockModeCloser(key.Cipher.CBCMech, modeEncrypt, iv) +func (key *PKCS11SecretKey) NewCBCEncrypterCloser(iv []byte) (bmc BlockModeCloser, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeEncrypt, iv, false) } -// NewCBCDecrypter returns a BlockModeCloser which decrypts in cipher block chaining mode, using the given key. +// NewCBCDecrypterCloser returns a BlockModeCloser which decrypts in cipher block chaining mode, using the given key. // The length of iv must be the same as the key's block size and must match the iv used to encrypt the data. -func (key *PKCS11SecretKey) NewCBCDecrypter(iv []byte) (bmc BlockModeCloser, err error) { - return key.newBlockModeCloser(key.Cipher.CBCMech, modeDecrypt, iv) +func (key *PKCS11SecretKey) NewCBCDecrypterCloser(iv []byte) (bmc BlockModeCloser, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeDecrypt, iv, false) } // blockModeCloser is a concrete implementation of BlockModeCloser supporting CBC. @@ -494,7 +524,7 @@ type blockModeCloser struct { } // newBlockModeCloser creates a new blockModeCloser for the chosen mechanism and mode. -func (key *PKCS11SecretKey) newBlockModeCloser(mech uint, mode int, iv []byte) (bmc blockModeCloser, err error) { +func (key *PKCS11SecretKey) newBlockModeCloser(mech uint, mode int, iv []byte, setFinalizer bool) (bmc *blockModeCloser, err error) { // TODO maybe refactor with withSession() sessionPool := pool.Get(key.Slot) if sessionPool == nil { @@ -507,11 +537,13 @@ func (key *PKCS11SecretKey) newBlockModeCloser(mech uint, mode int, iv []byte) ( if session, err = sessionPool.Get(ctx); err != nil { return } - bmc.session = session.(*PKCS11Session) - bmc.blockSize = key.Cipher.BlockSize - bmc.mode = mode - bmc.cleanup = func() { - sessionPool.Put(session) + bmc = &blockModeCloser{ + session: session.(*PKCS11Session), + blockSize: key.Cipher.BlockSize, + mode: mode, + cleanup: func() { + sessionPool.Put(session) + }, } mechDescription := []*pkcs11.Mechanism{pkcs11.NewMechanism(mech, iv)} switch mode { @@ -526,14 +558,21 @@ func (key *PKCS11SecretKey) newBlockModeCloser(mech uint, mode int, iv []byte) ( bmc.cleanup() return } + if setFinalizer { + runtime.SetFinalizer(bmc, finalizeBlockModeCloser) + } return } -func (bmc blockModeCloser) BlockSize() int { +func finalizeBlockModeCloser(obj interface{}) { + obj.(*blockModeCloser).Close() +} + +func (bmc *blockModeCloser) BlockSize() int { return bmc.blockSize } -func (bmc blockModeCloser) CryptBlocks(dst, src []byte) { +func (bmc *blockModeCloser) CryptBlocks(dst, src []byte) { if len(dst) < len(src) { panic("destination buffer too small") } @@ -560,7 +599,10 @@ func (bmc blockModeCloser) CryptBlocks(dst, src []byte) { copy(dst[:len(result)], result) } -func (bmc blockModeCloser) Close() { +func (bmc *blockModeCloser) Close() { + if bmc.session == nil { + return + } var result []byte var err error switch bmc.mode { @@ -569,6 +611,7 @@ func (bmc blockModeCloser) Close() { case modeEncrypt: result, err = bmc.session.Ctx.EncryptFinal(bmc.session.Handle) } + bmc.session = nil bmc.cleanup() if err != nil { panic(err) diff --git a/symmetric_test.go b/symmetric_test.go index e967e6b..7d2c82e 100644 --- a/symmetric_test.go +++ b/symmetric_test.go @@ -26,6 +26,7 @@ import ( "crypto" "crypto/cipher" "github.com/miekg/pkcs11" + "runtime" "testing" ) @@ -74,11 +75,11 @@ func testHardSymmetric(t *testing.T, keytype int, bits int) { }) t.Run("CBCClose", func(t *testing.T) { var enc, dec BlockModeCloser - if enc, err = key2.NewCBCEncrypter(iv); err != nil { + if enc, err = key2.NewCBCEncrypterCloser(iv); err != nil { t.Errorf("NewCBCEncrypter: %v", err) return } - if dec, err = key2.NewCBCDecrypter(iv); err != nil { + if dec, err = key2.NewCBCDecrypterCloser(iv); err != nil { t.Errorf("NewCBCDecrypter: %v", err) return } @@ -86,6 +87,21 @@ func testHardSymmetric(t *testing.T, keytype int, bits int) { enc.Close() dec.Close() }) + t.Run("CBCNoClose", func(t *testing.T) { + var enc, dec cipher.BlockMode + if enc, err = key2.NewCBCEncrypter(iv); err != nil { + t.Errorf("NewCBCEncrypter: %v", err) + return + } + if dec, err = key2.NewCBCDecrypter(iv); err != nil { + t.Errorf("NewCBCDecrypter: %v", err) + return + } + testSymmetricMode(t, enc, dec) + + // See discussion at BlockModeCloser. + runtime.GC() + }) if bits == 128 { t.Run("GCMSoft", func(t *testing.T) { aead, err := cipher.NewGCM(key2) @@ -116,6 +132,7 @@ func testHardSymmetric(t *testing.T, keytype int, bits int) { // TODO CFB // TODO OFB // TODO CTR + } func testSymmetricBlock(t *testing.T, encryptKey cipher.Block, decryptKey cipher.Block) { @@ -244,6 +261,16 @@ func BenchmarkCBC(b *testing.B) { mode.CryptBlocks(ciphertext, plaintext) } }) + b.Run("IdiomaticClose", func(b *testing.B) { + for i := 0; i < b.N; i++ { + mode, err := key.NewCBCEncrypterCloser(iv) + if err != nil { + panic(err) + } + mode.CryptBlocks(ciphertext, plaintext) + mode.Close() + } + }) b.Run("Idiomatic", func(b *testing.B) { for i := 0; i < b.N; i++ { mode, err := key.NewCBCEncrypter(iv) @@ -251,9 +278,8 @@ func BenchmarkCBC(b *testing.B) { panic(err) } mode.CryptBlocks(ciphertext, plaintext) - mode.Close() } - + runtime.GC() }) Close() }