Skip to content

Commit

Permalink
Finalized symmetric crypto interface
Browse files Browse the repository at this point in the history
You can now have a crypto11.BlockModeCloser, and must call Close(),
or a cipher.BlockMode, but it has a finalizer.

re #6
  • Loading branch information
Richard Kettlewell authored and Richard Kettlewell committed Aug 6, 2018
1 parent 77286f7 commit d5db225
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 19 deletions.
73 changes: 58 additions & 15 deletions symmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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")
}
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
34 changes: 30 additions & 4 deletions symmetric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"crypto"
"crypto/cipher"
"github.com/miekg/pkcs11"
"runtime"
"testing"
)

Expand Down Expand Up @@ -74,18 +75,33 @@ 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
}
testSymmetricMode(t, enc, dec)
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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -244,16 +261,25 @@ 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)
if err != nil {
panic(err)
}
mode.CryptBlocks(ciphertext, plaintext)
mode.Close()
}

runtime.GC()
})
Close()
}
Expand Down

0 comments on commit d5db225

Please sign in to comment.