diff --git a/Gopkg.lock b/Gopkg.lock index de7da02..9030a39 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -5,7 +5,7 @@ branch = "master" name = "github.com/miekg/pkcs11" packages = ["."] - revision = "7283ca79f35edb89bc1b4ecae7f86a3680ce737f" + revision = "c6d6ee821fb161c8022ceb8ba93ce3b815d8d62e" [[projects]] name = "github.com/youtube/vitess" @@ -17,7 +17,7 @@ branch = "master" name = "golang.org/x/net" packages = ["context"] - revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9" + revision = "4dfa2610cdf3b287375bbba5b8f2a14d3b01d8de" [[projects]] name = "vitess.io/vitess" diff --git a/README.md b/README.md index 82ffda7..17d5cf0 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ uses [PKCS#11](http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/p * ECDSA signing. * DSA signing. * Random number generation. +* (Experimental) AES and DES3 encryption and decryption. +* (Experimental) HMAC support. Signing is done through the [crypto.Signer](https://golang.org/pkg/crypto/#Signer) interface and @@ -89,6 +91,8 @@ To protect keys with the module only, use the 'accelerator' token: "Pin" : "password" } +(At time of writing) GCM is not implemented, so expect test skips. + Testing with SoftHSM -------------------- @@ -147,7 +151,7 @@ The configuration looks like this: "Pin" : "password" } -(At time of writing) OAEP is only partial, so expect test skips. +(At time of writing) OAEP is only partial and HMAC is unsupported, so expect test skips. Limitations =========== @@ -156,6 +160,12 @@ Limitations is not implemented and an error is returned if it is nonzero. The reason for this is that it is not possible for crypto11 to guarantee the constant-time behavior in the specification. See [issue #5](https://github.com/ThalesIgnite/crypto11/issues/5) for further discussion. + * Symmetric crypto support via [cipher.Block](https://golang.org/pkg/crypto/cipher/#Block) is very slow. +You can use the `BlockModeCloser` API +(over 400 times as fast on my computer) +but you must call the Close() +interface (not found in [cipher.BlockMode](https://golang.org/pkg/crypto/cipher/#BlockMode)). +See [issue #6](https://github.com/ThalesIgnite/crypto11/issues/6) for further discussion. Wishlist ======== @@ -168,7 +178,7 @@ Copyright MIT License. -Copyright 2016, 2017 Thales e-Security, Inc +Copyright 2016-2018 Thales e-Security, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/aead.go b/aead.go new file mode 100644 index 0000000..9b3674e --- /dev/null +++ b/aead.go @@ -0,0 +1,162 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "crypto/cipher" + "errors" + "fmt" + "github.com/miekg/pkcs11" +) + +// cipher.AEAD ---------------------------------------------------------- + +const ( + // PaddingNone represents a block cipher with no padding. (See NewCBC.) + PaddingNone = iota + + // PaddingPKCS represents a block cipher used with PKCS#7 padding. (See NewCBC.) + PaddingPKCS +) + +type genericAead struct { + key *PKCS11SecretKey + + overhead int + + nonceSize int + + makeMech func(nonce []byte, additionalData []byte) ([]*pkcs11.Mechanism, error) +} + +// NewGCM returns a given cipher wrapped in Galois Counter Mode, with the standard +// nonce length. +// +// This depends on the HSM supporting the CKM_*_GCM mechanism. If it is not supported +// then you must use cipher.NewGCM; it will be slow. +func (key *PKCS11SecretKey) NewGCM() (g cipher.AEAD, err error) { + if key.Cipher.GCMMech == 0 { + err = fmt.Errorf("GCM not implemented for key type %#x", key.Cipher.GenParams[0].KeyType) + return + } + g = genericAead{ + key: key, + overhead: 16, + nonceSize: 12, + makeMech: func(nonce []byte, additionalData []byte) (mech []*pkcs11.Mechanism, error error) { + params := pkcs11.NewGCMParams(nonce, additionalData, 16) + mech = []*pkcs11.Mechanism{pkcs11.NewMechanism(key.Cipher.GCMMech, params)} + return + }, + } + return +} + +// NewCBC returns a given cipher wrapped in CBC mode. +// +// Despite the cipher.AEAD return type, there is no support for additional data and no authentication. +// This method exists to provide a convenient way to do bulk (possibly padded) CBC encryption. +// Think carefully before passing the cipher.AEAD to any consumer that expects authentication. +func (key *PKCS11SecretKey) NewCBC(paddingMode int) (g cipher.AEAD, err error) { + g = genericAead{ + key: key, + overhead: 0, + nonceSize: key.BlockSize(), + makeMech: func(nonce []byte, additionalData []byte) (mech []*pkcs11.Mechanism, error error) { + if len(additionalData) > 0 { + err = errors.New("additional data not supported for CBC mode") + } + var pkcsMech uint + switch paddingMode { + case PaddingNone: + pkcsMech = key.Cipher.CBCMech + case PaddingPKCS: + pkcsMech = key.Cipher.CBCPKCSMech + default: + err = errors.New("unrecognized padding mode") + return + } + if pkcsMech == 0 { + err = errors.New("unsupported padding mode") + return + } + mech = []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcsMech, nonce)} + return + }, + } + return +} + +func (g genericAead) NonceSize() int { + return g.nonceSize +} + +func (g genericAead) Overhead() int { + return g.overhead +} + +func (g genericAead) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + var result []byte + if err := withSession(g.key.Slot, func(session *PKCS11Session) (err error) { + var mech []*pkcs11.Mechanism + if mech, err = g.makeMech(nonce, additionalData); err != nil { + return + } + if err = session.Ctx.EncryptInit(session.Handle, mech, g.key.Handle); err != nil { + err = fmt.Errorf("C_EncryptInit: %v", err) + return + } + if result, err = session.Ctx.Encrypt(session.Handle, plaintext); err != nil { + err = fmt.Errorf("C_Encrypt: %v", err) + return + } + return + }); err != nil { + panic(err) + } else { + dst = append(dst, result...) + } + return dst +} + +func (g genericAead) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + var result []byte + if err := withSession(g.key.Slot, func(session *PKCS11Session) (err error) { + var mech []*pkcs11.Mechanism + if mech, err = g.makeMech(nonce, additionalData); err != nil { + return + } + if err = session.Ctx.DecryptInit(session.Handle, mech, g.key.Handle); err != nil { + err = fmt.Errorf("C_DecryptInit: %v", err) + return + } + if result, err = session.Ctx.Decrypt(session.Handle, ciphertext); err != nil { + err = fmt.Errorf("C_Decrypt: %v", err) + return + } + return + }); err != nil { + return nil, err + } + dst = append(dst, result...) + return dst, nil +} diff --git a/block.go b/block.go new file mode 100644 index 0000000..d3af6fd --- /dev/null +++ b/block.go @@ -0,0 +1,90 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "fmt" + "github.com/miekg/pkcs11" +) + +// cipher.Block --------------------------------------------------------- + +// BlockSize returns the cipher's block size in bytes. +func (key *PKCS11SecretKey) BlockSize() int { + return key.Cipher.BlockSize +} + +// Decrypt decrypts the first block in src into dst. +// Dst and src must overlap entirely or not at all. +// +// Using this method for bulk operation is very inefficient, as it makes a round trip to the HSM +// (which may be network-connected) for each block. +// For more efficient operation, see NewCBCDecrypterCloser, NewCBCDecrypter or NewCBC. +func (key *PKCS11SecretKey) Decrypt(dst, src []byte) { + var result []byte + if err := withSession(key.Slot, func(session *PKCS11Session) (err error) { + mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(key.Cipher.ECBMech, nil)} + if err = session.Ctx.DecryptInit(session.Handle, mech, key.Handle); err != nil { + return + } + if result, err = session.Ctx.Decrypt(session.Handle, src[:key.Cipher.BlockSize]); err != nil { + return + } + if len(result) != key.Cipher.BlockSize { + err = fmt.Errorf("C_Decrypt: returned %v bytes, wanted %v", len(result), key.Cipher.BlockSize) + return + } + return + }); err != nil { + panic(err) + } else { + copy(dst[:key.Cipher.BlockSize], result) + } +} + +// Encrypt encrypts the first block in src into dst. +// Dst and src must overlap entirely or not at all. +// +// Using this method for bulk operation is very inefficient, as it makes a round trip to the HSM +// (which may be network-connected) for each block. +// For more efficient operation, see NewCBCEncrypterCloser, NewCBCEncrypter or NewCBC. +func (key *PKCS11SecretKey) Encrypt(dst, src []byte) { + var result []byte + if err := withSession(key.Slot, func(session *PKCS11Session) (err error) { + mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(key.Cipher.ECBMech, nil)} + if err = session.Ctx.EncryptInit(session.Handle, mech, key.Handle); err != nil { + return + } + if result, err = session.Ctx.Encrypt(session.Handle, src[:key.Cipher.BlockSize]); err != nil { + return + } + if len(result) != key.Cipher.BlockSize { + err = fmt.Errorf("C_Encrypt: unexpectedly returned %v bytes, wanted %v", len(result), key.Cipher.BlockSize) + return + } + return + }); err != nil { + panic(err) + } else { + copy(dst[:key.Cipher.BlockSize], result) + } +} diff --git a/blockmode.go b/blockmode.go new file mode 100644 index 0000000..182c2d6 --- /dev/null +++ b/blockmode.go @@ -0,0 +1,212 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "context" + "crypto/cipher" + "fmt" + "github.com/miekg/pkcs11" + "github.com/youtube/vitess/go/pools" + "runtime" +) + +// cipher.BlockMode ----------------------------------------------------- + +// BlockModeCloser represents a block cipher running in a block-based mode (e.g. CBC). +// +// BlockModeCloser embeds 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 indefinitely. +type BlockModeCloser interface { + cipher.BlockMode + + // Close() releases resources associated with the block mode. + Close() +} + +const ( + modeEncrypt = iota // blockModeCloser is in encrypt mode + modeDecrypt // blockModeCloser is in decrypt mode +) + +// 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. +// +// If that is not possible then adding calls to runtime.GC() may help. +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. +// +// If that is not possible then adding calls to runtime.GC() may help. +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. +// +// Use of NewCBCEncrypterCloser rather than NewCBCEncrypter represents a commitment to call the Close() method +// of the returned BlockModeCloser. +func (key *PKCS11SecretKey) NewCBCEncrypterCloser(iv []byte) (bmc BlockModeCloser, err error) { + return key.newBlockModeCloser(key.Cipher.CBCMech, modeEncrypt, iv, false) +} + +// 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. +// +// Use of NewCBCDecrypterCloser rather than NewCBCEncrypter represents a commitment to call the Close() method +// of the returned BlockModeCloser. +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. +type blockModeCloser struct { + // PKCS#11 session to use + session *PKCS11Session + + // Cipher block size + blockSize int + + // modeDecrypt or modeEncrypt + mode int + + // Cleanup function + cleanup func() +} + +// newBlockModeCloser creates a new blockModeCloser for the chosen mechanism and mode. +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 { + err = fmt.Errorf("crypto11: no session for slot %d", key.Slot) + return + } + ctx := context.Background() + if instance.cfg.PoolWaitTimeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), instance.cfg.PoolWaitTimeout) + defer cancel() + } + var session pools.Resource + if session, err = sessionPool.Get(ctx); err != nil { + return + } + 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 { + case modeDecrypt: + err = bmc.session.Ctx.DecryptInit(bmc.session.Handle, mechDescription, key.Handle) + case modeEncrypt: + err = bmc.session.Ctx.EncryptInit(bmc.session.Handle, mechDescription, key.Handle) + default: + panic("unexpected mode") + } + if err != nil { + bmc.cleanup() + return + } + if setFinalizer { + runtime.SetFinalizer(bmc, finalizeBlockModeCloser) + } + return +} + +func finalizeBlockModeCloser(obj interface{}) { + obj.(*blockModeCloser).Close() +} + +func (bmc *blockModeCloser) BlockSize() int { + return bmc.blockSize +} + +func (bmc *blockModeCloser) CryptBlocks(dst, src []byte) { + if len(dst) < len(src) { + panic("destination buffer too small") + } + if len(src)%bmc.blockSize != 0 { + panic("input is not a whole number of blocks") + } + var result []byte + var err error + switch bmc.mode { + case modeDecrypt: + result, err = bmc.session.Ctx.DecryptUpdate(bmc.session.Handle, src) + case modeEncrypt: + result, err = bmc.session.Ctx.EncryptUpdate(bmc.session.Handle, src) + } + if err != nil { + panic(err) + } + // PKCS#11 2.40 s5.2 says that the operation must produce as much output + // as possible, so we should never have less than we submitted for CBC. + // This could be different for other modes but we don't implement any yet. + if len(result) != len(src) { + panic("nontrivial result from *Final operation") + } + copy(dst[:len(result)], result) + runtime.KeepAlive(bmc) +} + +func (bmc *blockModeCloser) Close() { + if bmc.session == nil { + return + } + var result []byte + var err error + switch bmc.mode { + case modeDecrypt: + result, err = bmc.session.Ctx.DecryptFinal(bmc.session.Handle) + case modeEncrypt: + result, err = bmc.session.Ctx.EncryptFinal(bmc.session.Handle) + } + bmc.session = nil + bmc.cleanup() + if err != nil { + panic(err) + } + // PKCS#11 2.40 s5.2 says that the operation must produce as much output + // as possible, so we should never have any left over for CBC. + // This could be different for other modes but we don't implement any yet. + if len(result) > 0 { + panic("nontrivial result from *Final operation") + } +} diff --git a/crypto11.go b/crypto11.go index 7b5b37c..88251aa 100644 --- a/crypto11.go +++ b/crypto11.go @@ -69,7 +69,12 @@ // The PKCS1v15DecryptOptions SessionKeyLen field is not implemented // and an error is returned if it is nonzero. // The reason for this is that it is not possible for crypto11 to guarantee the constant-time behavior in the specification. -// See https://github.com/ThalesIgnite/crypto11/issues/5 for further discussion. +// See https://github.com/thalesignite/crypto11/issues/5 for further discussion. +// +// Symmetric crypto support via cipher.Block is very slow. +// You can use the BlockModeCloser API +// but you must call the Close() interface (not found in cipher.BlockMode). +// See https://github.com/ThalesIgnite/crypto11/issues/6 for further discussion. package crypto11 import ( @@ -136,8 +141,8 @@ type PKCS11PrivateKey struct { /* Nasty globals */ var instance = &libCtx{ cfg: &PKCS11Config{ - MaxSessions: DefaultMaxSessions, - IdleTimeout: 0, + MaxSessions: DefaultMaxSessions, + IdleTimeout: 0, PoolWaitTimeout: 0, }, } diff --git a/hmac.go b/hmac.go new file mode 100644 index 0000000..3a00e33 --- /dev/null +++ b/hmac.go @@ -0,0 +1,223 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "context" + "errors" + "fmt" + "github.com/miekg/pkcs11" + "github.com/youtube/vitess/go/pools" + "hash" +) + +const ( + NFCK_VENDOR_NCIPHER = 0xde436972 + CKM_NCIPHER = (pkcs11.CKM_VENDOR_DEFINED | NFCK_VENDOR_NCIPHER) + + // CKM_NC_MD5_HMAC_KEY_GEN is the nShield-specific HMACMD5 key-generation mechanism + CKM_NC_MD5_HMAC_KEY_GEN = (CKM_NCIPHER + 0x6) + + // CKM_NC_SHA_1_HMAC_KEY_GEN is the nShield-specific HMACSHA1 key-generation mechanism + CKM_NC_SHA_1_HMAC_KEY_GEN = (CKM_NCIPHER + 0x3) + + // CKM_NC_SHA224_HMAC_KEY_GEN is the nShield-specific HMACSHA224 key-generation mechanism + CKM_NC_SHA224_HMAC_KEY_GEN = (CKM_NCIPHER + 0x24) + + // CKM_NC_SHA256_HMAC_KEY_GEN is the nShield-specific HMACSHA256 key-generation mechanism + CKM_NC_SHA256_HMAC_KEY_GEN = (CKM_NCIPHER + 0x25) + + // CKM_NC_SHA384_HMAC_KEY_GEN is the nShield-specific HMACSHA384 key-generation mechanism + CKM_NC_SHA384_HMAC_KEY_GEN = (CKM_NCIPHER + 0x26) + + // CKM_NC_SHA512_HMAC_KEY_GEN is the nShield-specific HMACSHA512 key-generation mechanism + CKM_NC_SHA512_HMAC_KEY_GEN = (CKM_NCIPHER + 0x27) +) + +type hmacImplementation struct { + // PKCS#11 session to use + session *PKCS11Session + + // Signing key + key *PKCS11SecretKey + + // Hash size + size int + + // Block size + blockSize int + + // PKCS#11 mechanism information + mechDescription []*pkcs11.Mechanism + + // Cleanup function + cleanup func() + + // Count of updates + updates uint64 + + // Result, or nil if we don't have the answer yet + result []byte +} + +type hmacInfo struct { + size int + blockSize int + general bool +} + +var hmacInfos = map[int]*hmacInfo{ + pkcs11.CKM_MD5_HMAC: {20, 64, false}, + pkcs11.CKM_MD5_HMAC_GENERAL: {20, 64, true}, + pkcs11.CKM_SHA_1_HMAC: {20, 64, false}, + pkcs11.CKM_SHA_1_HMAC_GENERAL: {20, 64, true}, + pkcs11.CKM_SHA224_HMAC: {28, 64, false}, + pkcs11.CKM_SHA224_HMAC_GENERAL: {28, 64, true}, + pkcs11.CKM_SHA256_HMAC: {32, 64, false}, + pkcs11.CKM_SHA256_HMAC_GENERAL: {32, 64, true}, + pkcs11.CKM_SHA384_HMAC: {48, 64, false}, + pkcs11.CKM_SHA384_HMAC_GENERAL: {48, 64, true}, + pkcs11.CKM_SHA512_HMAC: {64, 128, false}, + pkcs11.CKM_SHA512_HMAC_GENERAL: {64, 128, true}, + pkcs11.CKM_SHA512_224_HMAC: {28, 128, false}, + pkcs11.CKM_SHA512_224_HMAC_GENERAL: {28, 128, true}, + pkcs11.CKM_SHA512_256_HMAC: {32, 128, false}, + pkcs11.CKM_SHA512_256_HMAC_GENERAL: {32, 128, true}, + pkcs11.CKM_RIPEMD160_HMAC: {20, 64, false}, + pkcs11.CKM_RIPEMD160_HMAC_GENERAL: {20, 64, true}, +} + +// ErrHmacClosed is called if an HMAC is updated after it has finished. +var ErrHmacClosed = errors.New("already called Sum()") + +// NewHMAC returns a new HMAC hash using the given PKCS#11 mechanism +// and key. +// length specifies the output size, for _GENERAL mechanisms. +// +// If the mechanism is not in the built-in list of known mechanisms then the +// Size() function will return whatever length was, even if it is wrong. +// BlockSize() will always return 0 in this case. +// +// The Reset() method is not implemented. +// After Sum() is called no new data may be added. +func (key *PKCS11SecretKey) NewHMAC(mech int, length int) (h hash.Hash, err error) { + var hi hmacImplementation + hi = hmacImplementation{ + key: key, + } + var params []byte + if info, ok := hmacInfos[mech]; ok { + hi.blockSize = info.blockSize + if info.general { + hi.size = length + params = ulongToBytes(uint(length)) + } else { + hi.size = info.size + } + } else { + hi.size = length + } + hi.mechDescription = []*pkcs11.Mechanism{pkcs11.NewMechanism(uint(mech), params)} + if err = hi.initialize(); err != nil { + return + } + h = &hi + return +} + +func (hi *hmacImplementation) initialize() (err error) { + // TODO refactor with newBlockModeCloser + sessionPool := pool.Get(hi.key.Slot) + if sessionPool == nil { + err = fmt.Errorf("crypto11: no session for slot %d", hi.key.Slot) + return + } + ctx := context.Background() + if instance.cfg.PoolWaitTimeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), instance.cfg.PoolWaitTimeout) + defer cancel() + } + var session pools.Resource + if session, err = sessionPool.Get(ctx); err != nil { + return + } + hi.session = session.(*PKCS11Session) + hi.cleanup = func() { + sessionPool.Put(session) + hi.session = nil + } + if err = hi.session.Ctx.SignInit(hi.session.Handle, hi.mechDescription, hi.key.Handle); err != nil { + hi.cleanup() + return + } + hi.updates = 0 + hi.result = nil + return +} + +func (hi *hmacImplementation) Write(p []byte) (n int, err error) { + if hi.result != nil { + if len(p) > 0 { + err = ErrHmacClosed + } + return + } + if err = hi.session.Ctx.SignUpdate(hi.session.Handle, p); err != nil { + return + } + hi.updates++ + n = len(p) + return +} + +func (hi *hmacImplementation) Sum(b []byte) []byte { + if hi.result == nil { + var err error + if hi.updates == 0 { + // http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html#_Toc322855304 + // We must ensure that C_SignUpdate is called _at least once_. + if err = hi.session.Ctx.SignUpdate(hi.session.Handle, []byte{}); err != nil { + panic(err) + } + } + hi.result, err = hi.session.Ctx.SignFinal(hi.session.Handle) + hi.cleanup() + if err != nil { + panic(err) + } + } + return append(b, hi.result...) +} + +func (hi *hmacImplementation) Reset() { + hi.Sum(nil) // Clean up + hi.initialize() +} + +func (hi *hmacImplementation) Size() int { + return hi.size +} + +func (hi *hmacImplementation) BlockSize() int { + return hi.blockSize +} diff --git a/hmac_test.go b/hmac_test.go new file mode 100644 index 0000000..9baa766 --- /dev/null +++ b/hmac_test.go @@ -0,0 +1,185 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "bytes" + "github.com/miekg/pkcs11" + "hash" + "testing" +) + +func TestHmac(t *testing.T) { + ConfigureFromFile("config") + var info pkcs11.Info + var err error + if info, err = instance.ctx.GetInfo(); err != nil { + t.Errorf("GetInfo: %v", err) + return + } + if info.ManufacturerID == "SoftHSM" { + t.Skipf("HMAC not implemented on SoftHSM") + } + t.Run("HMACSHA1", func(t *testing.T) { + testHmac(t, pkcs11.CKK_SHA_1_HMAC, pkcs11.CKM_SHA_1_HMAC, 0, 20, false) + }) + t.Run("HMACSHA1General", func(t *testing.T) { + testHmac(t, pkcs11.CKK_SHA_1_HMAC, pkcs11.CKM_SHA_1_HMAC_GENERAL, 10, 10, true) + }) + t.Run("HMACSHA256", func(t *testing.T) { + testHmac(t, pkcs11.CKK_SHA256_HMAC, pkcs11.CKM_SHA256_HMAC, 0, 32, false) + }) + Close() +} + +func testHmac(t *testing.T, keytype int, mech int, length int, xlength int, full bool) { + var err error + var key *PKCS11SecretKey + t.Run("Generate", func(t *testing.T) { + if key, err = GenerateSecretKey(256, Ciphers[keytype]); err != nil { + t.Errorf("crypto11.GenerateSecretKey: %v", err) + return + } + if key == nil { + t.Errorf("crypto11.GenerateSecretKey: returned nil but no error") + return + } + }) + if key == nil { + return + } + t.Run("Short", func(t *testing.T) { + input := []byte("a short string") + var h1, h2 hash.Hash + if h1, err = key.NewHMAC(mech, length); err != nil { + t.Errorf("key.NewHMAC: %v", err) + return + } + if n, err := h1.Write(input); err != nil || n != len(input) { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + r1 := h1.Sum([]byte{}) + if h2, err = key.NewHMAC(mech, length); err != nil { + t.Errorf("key.NewHMAC: %v", err) + return + } + if n, err := h2.Write(input); err != nil || n != len(input) { + t.Errorf("h2.Write: %v/%d", err, n) + return + } + r2 := h2.Sum([]byte{}) + if bytes.Compare(r1, r2) != 0 { + t.Errorf("h1/h2 inconsistent") + return + } + if len(r1) != xlength { + t.Errorf("r1 wrong length (want %v got %v)", xlength, len(r1)) + return + } + }) + if full { // Independent of hash, only do these once + t.Run("Empty", func(t *testing.T) { + // Must be able to MAC empty inputs without panicing + var h1 hash.Hash + if h1, err = key.NewHMAC(mech, length); err != nil { + t.Errorf("key.NewHMAC: %v", err) + return + } + h1.Sum([]byte{}) + }) + t.Run("MultiSum", func(t *testing.T) { + input := []byte("a different short string") + var h1 hash.Hash + if h1, err = key.NewHMAC(mech, length); err != nil { + t.Errorf("key.NewHMAC: %v", err) + return + } + if n, err := h1.Write(input); err != nil || n != len(input) { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + r1 := h1.Sum([]byte{}) + r2 := h1.Sum([]byte{}) + if bytes.Compare(r1, r2) != 0 { + t.Errorf("r1/r2 inconsistent") + return + } + // Can't add more after Sum() + if n, err := h1.Write(input); err != ErrHmacClosed { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + // 0-length is special + if n, err := h1.Write([]byte{}); err != nil || n != 0 { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + }) + t.Run("Reset", func(t *testing.T) { + var h1 hash.Hash + if h1, err = key.NewHMAC(mech, length); err != nil { + t.Errorf("key.NewHMAC: %v", err) + return + } + if n, err := h1.Write([]byte{1}); err != nil || n != 1 { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + r1 := h1.Sum([]byte{}) + h1.Reset() + if n, err := h1.Write([]byte{2}); err != nil || n != 1 { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + r2 := h1.Sum([]byte{}) + h1.Reset() + if n, err := h1.Write([]byte{1}); err != nil || n != 1 { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + r3 := h1.Sum([]byte{}) + if bytes.Compare(r1, r3) != 0 { + t.Errorf("r1/r3 inconsistent") + return + } + if bytes.Compare(r1, r2) == 0 { + t.Errorf("r1/r2 unexpectedly equal") + return + } + }) + t.Run("ResetFast", func(t *testing.T) { + // Reset() immediately after creation should be safe + var h1 hash.Hash + if h1, err = key.NewHMAC(mech, length); err != nil { + t.Errorf("key.NewHMAC: %v", err) + return + } + h1.Reset() + if n, err := h1.Write([]byte{2}); err != nil || n != 1 { + t.Errorf("h1.Write: %v/%d", err, n) + return + } + h1.Sum([]byte{}) + }) + } +} diff --git a/keys.go b/keys.go index 4632e22..6df9906 100644 --- a/keys.go +++ b/keys.go @@ -148,3 +148,49 @@ func FindKeyPairOnSession(session *PKCS11Session, slot uint, id []byte, label [] func (signer PKCS11PrivateKey) Public() crypto.PublicKey { return signer.PubKey } + +// FindKey retrieves a previously created symmetric key. +// +// Either (but not both) of id and label may be nil, in which case they are ignored. +func FindKey(id []byte, label []byte) (*PKCS11SecretKey, error) { + return FindKeyOnSlot(instance.slot, id, label) +} + +// FindKeyOnSlot retrieves a previously created symmetric key, using a specified slot. +// +// Either (but not both) of id and label may be nil, in which case they are ignored. +func FindKeyOnSlot(slot uint, id []byte, label []byte) (*PKCS11SecretKey, error) { + var err error + var k *PKCS11SecretKey + if err = ensureSessions(instance, slot); err != nil { + return nil, err + } + err = withSession(slot, func(session *PKCS11Session) error { + k, err = FindKeyOnSession(session, slot, id, label) + return err + }) + return k, err +} + +// FindKeyOnSession retrieves a previously created symmetric key, using a specified session. +// +// Either (but not both) of id and label may be nil, in which case they are ignored. +func FindKeyOnSession(session *PKCS11Session, slot uint, id []byte, label []byte) (key *PKCS11SecretKey, err error) { + var privHandle pkcs11.ObjectHandle + if privHandle, err = findKey(session, id, label, pkcs11.CKO_SECRET_KEY, ^uint(0)); err != nil { + return + } + attributes := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, 0), + } + if attributes, err = session.Ctx.GetAttributeValue(session.Handle, privHandle, attributes); err != nil { + return + } + if cipher, ok := Ciphers[int(bytesToUlong(attributes[0].Value))]; ok { + key = &PKCS11SecretKey{PKCS11Object{privHandle, slot}, cipher} + } else { + err = ErrUnsupportedKeyType + return + } + return +} diff --git a/symmetric.go b/symmetric.go new file mode 100644 index 0000000..668f1d4 --- /dev/null +++ b/symmetric.go @@ -0,0 +1,333 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "github.com/miekg/pkcs11" +) + +// SymmetricGenParams holds a consistent (key type, mechanism) key generation pair. +type SymmetricGenParams struct { + // Key type (CKK_...) + KeyType uint + + // Key generation mechanism (CKM_..._KEY_GEN) + GenMech uint +} + +// SymmetricCipher represents information about a symmetric cipher. +type SymmetricCipher struct { + // Possible key generation parameters + // (For HMAC this varies between PKCS#11 implementations.) + GenParams []SymmetricGenParams + + // Block size in bytes + BlockSize int + + // True if encryption supported + Encrypt bool + + // True if MAC supported + MAC bool + + // ECB mechanism (CKM_..._ECB) + ECBMech uint + + // CBC mechanism (CKM_..._CBC) + CBCMech uint + + // CBC mechanism with PKCS#7 padding (CKM_..._CBC) + CBCPKCSMech uint + + // GCM mechanism (CKM_..._GCM) + GCMMech uint +} + +// CipherAES describes the AES cipher. Use this with the +// GenerateSecretKey... functions. +var CipherAES = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_AES, + GenMech: pkcs11.CKM_AES_KEY_GEN, + }, + }, + BlockSize: 16, + Encrypt: true, + MAC: false, + ECBMech: pkcs11.CKM_AES_ECB, + CBCMech: pkcs11.CKM_AES_CBC, + CBCPKCSMech: pkcs11.CKM_AES_CBC_PAD, + GCMMech: pkcs11.CKM_AES_GCM, +} + +// CipherDES3 describes the three-key triple-DES cipher. Use this with the +// GenerateSecretKey... functions. +var CipherDES3 = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_DES3, + GenMech: pkcs11.CKM_DES3_KEY_GEN, + }, + }, + BlockSize: 8, + Encrypt: true, + MAC: false, + ECBMech: pkcs11.CKM_DES3_ECB, + CBCMech: pkcs11.CKM_DES3_CBC, + CBCPKCSMech: pkcs11.CKM_DES3_CBC_PAD, + GCMMech: 0, +} + +// CipherGeneric describes the CKK_GENERIC_SECRET key type. Use this with the +// GenerateSecretKey... functions. +// +// The spec promises that this mechanism can be used to perform HMAC +// operations, although implementations vary; +// CipherHMACSHA1 and so on may give better results. +var CipherGeneric = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_GENERIC_SECRET, + GenMech: pkcs11.CKM_GENERIC_SECRET_KEY_GEN, + }, + }, + BlockSize: 64, + Encrypt: false, + MAC: true, + ECBMech: 0, + CBCMech: 0, + GCMMech: 0, +} + +// CipherHMACSHA1 describes the CKK_SHA_1_HMAC key type. Use this with the +// GenerateSecretKey... functions. +var CipherHMACSHA1 = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_SHA_1_HMAC, + GenMech: CKM_NC_SHA_1_HMAC_KEY_GEN, + }, + { + KeyType: pkcs11.CKK_GENERIC_SECRET, + GenMech: pkcs11.CKM_GENERIC_SECRET_KEY_GEN, + }, + }, + BlockSize: 64, + Encrypt: false, + MAC: true, + ECBMech: 0, + CBCMech: 0, + GCMMech: 0, +} + +// CipherHMACSHA224 describes the CKK_SHA224_HMAC key type. Use this with the +// GenerateSecretKey... functions. +var CipherHMACSHA224 = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_SHA224_HMAC, + GenMech: CKM_NC_SHA224_HMAC_KEY_GEN, + }, + { + KeyType: pkcs11.CKK_GENERIC_SECRET, + GenMech: pkcs11.CKM_GENERIC_SECRET_KEY_GEN, + }, + }, + BlockSize: 64, + Encrypt: false, + MAC: true, + ECBMech: 0, + CBCMech: 0, + GCMMech: 0, +} + +// CipherHMACSHA256 describes the CKK_SHA256_HMAC key type. Use this with the +// GenerateSecretKey... functions. +var CipherHMACSHA256 = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_SHA256_HMAC, + GenMech: CKM_NC_SHA256_HMAC_KEY_GEN, + }, + { + KeyType: pkcs11.CKK_GENERIC_SECRET, + GenMech: pkcs11.CKM_GENERIC_SECRET_KEY_GEN, + }, + }, + BlockSize: 64, + Encrypt: false, + MAC: true, + ECBMech: 0, + CBCMech: 0, + GCMMech: 0, +} + +// CipherHMACSHA384 describes the CKK_SHA384_HMAC key type. Use this with the +// GenerateSecretKey... functions. +var CipherHMACSHA384 = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_SHA384_HMAC, + GenMech: CKM_NC_SHA384_HMAC_KEY_GEN, + }, + { + KeyType: pkcs11.CKK_GENERIC_SECRET, + GenMech: pkcs11.CKM_GENERIC_SECRET_KEY_GEN, + }, + }, + BlockSize: 64, + Encrypt: false, + MAC: true, + ECBMech: 0, + CBCMech: 0, + GCMMech: 0, +} + +// CipherHMACSHA512 describes the CKK_SHA512_HMAC key type. Use this with the +// GenerateSecretKey... functions. +var CipherHMACSHA512 = SymmetricCipher{ + GenParams: []SymmetricGenParams{ + { + KeyType: pkcs11.CKK_SHA512_HMAC, + GenMech: CKM_NC_SHA512_HMAC_KEY_GEN, + }, + { + KeyType: pkcs11.CKK_GENERIC_SECRET, + GenMech: pkcs11.CKM_GENERIC_SECRET_KEY_GEN, + }, + }, + BlockSize: 128, + Encrypt: false, + MAC: true, + ECBMech: 0, + CBCMech: 0, + GCMMech: 0, +} + +// Ciphers is a map of PKCS#11 key types (CKK_...) to symmetric cipher information. +var Ciphers = map[int]*SymmetricCipher{ + pkcs11.CKK_AES: &CipherAES, + pkcs11.CKK_DES3: &CipherDES3, + pkcs11.CKK_GENERIC_SECRET: &CipherGeneric, + pkcs11.CKK_SHA_1_HMAC: &CipherHMACSHA1, + pkcs11.CKK_SHA224_HMAC: &CipherHMACSHA224, + pkcs11.CKK_SHA256_HMAC: &CipherHMACSHA256, + pkcs11.CKK_SHA384_HMAC: &CipherHMACSHA384, + pkcs11.CKK_SHA512_HMAC: &CipherHMACSHA512, +} + +// PKCS11SecretKey contains a reference to a loaded PKCS#11 symmetric key object. +// +// A *PKCS11SecretKey implements the cipher.Block interface, allowing it be used +// as the argument to cipher.NewCBCEncrypter and similar methods. +// For bulk operation this is very inefficient; +// using NewCBCEncrypterCloser, NewCBCEncrypter or NewCBC from this package is +// much faster. +type PKCS11SecretKey struct { + PKCS11Object + + // Symmetric cipher information + Cipher *SymmetricCipher +} + +// Key generation ------------------------------------------------------------- + +// GenerateSecretKey creates an secret key of given length and type. +// +// The key will have a random label and ID. +func GenerateSecretKey(bits int, cipher *SymmetricCipher) (*PKCS11SecretKey, error) { + return GenerateSecretKeyOnSlot(instance.slot, nil, nil, bits, cipher) +} + +// GenerateSecretKeyOnSlot creates as symmetric key on a specified slot +// +// Either or both label and/or id can be nil, in which case random values will be generated. +func GenerateSecretKeyOnSlot(slot uint, id []byte, label []byte, bits int, cipher *SymmetricCipher) (*PKCS11SecretKey, error) { + var k *PKCS11SecretKey + var err error + if err = ensureSessions(instance, slot); err != nil { + return nil, err + } + err = withSession(slot, func(session *PKCS11Session) error { + k, err = GenerateSecretKeyOnSession(session, slot, id, label, bits, cipher) + return err + }) + return k, err +} + +// GenerateSecretKeyOnSession creates a symmetric key of given type and +// length, on a specified session. +// +// Either or both label and/or id can be nil, in which case random values will be generated. +func GenerateSecretKeyOnSession(session *PKCS11Session, slot uint, id []byte, label []byte, bits int, cipher *SymmetricCipher) (key *PKCS11SecretKey, err error) { + // TODO refactor with the other key generation implementations + if label == nil { + if label, err = generateKeyLabel(); err != nil { + return nil, err + } + } + if id == nil { + if id, err = generateKeyLabel(); err != nil { + return nil, err + } + } + var privHandle pkcs11.ObjectHandle + // CKK_*_HMAC exists but there is no specific corresponding CKM_*_KEY_GEN + // mechanism. Therefore we attempt both CKM_GENERIC_SECRET_KEY_GEN and + // vendor-specific mechanisms. + for _, genMech := range cipher.GenParams { + secretKeyTemplate := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_SECRET_KEY), + pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, genMech.KeyType), + pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), + pkcs11.NewAttribute(pkcs11.CKA_SIGN, cipher.MAC), + pkcs11.NewAttribute(pkcs11.CKA_VERIFY, cipher.MAC), + pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, cipher.Encrypt), + pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, cipher.Encrypt), + pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true), + pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false), + pkcs11.NewAttribute(pkcs11.CKA_LABEL, label), + pkcs11.NewAttribute(pkcs11.CKA_ID, id), + } + if bits > 0 { + secretKeyTemplate = append(secretKeyTemplate, pkcs11.NewAttribute(pkcs11.CKA_VALUE_LEN, bits/8)) + } + mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(genMech.GenMech, nil)} + privHandle, err = session.Ctx.GenerateKey(session.Handle, mech, secretKeyTemplate) + if err == nil { + break + } + // nShield returns this if if doesn't like the CKK/CKM combination. + if e, ok := err.(pkcs11.Error); ok && e == pkcs11.CKR_TEMPLATE_INCONSISTENT { + continue + } + if err != nil { + return + } + } + if err != nil { + return + } + key = &PKCS11SecretKey{PKCS11Object{privHandle, slot}, cipher} + return +} diff --git a/symmetric_test.go b/symmetric_test.go new file mode 100644 index 0000000..113555c --- /dev/null +++ b/symmetric_test.go @@ -0,0 +1,296 @@ +// Copyright 2018 Thales e-Security, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package crypto11 + +import ( + "bytes" + "crypto" + "crypto/cipher" + "github.com/miekg/pkcs11" + "runtime" + "testing" +) + +func TestHardSymmetric(t *testing.T) { + ConfigureFromFile("config") + t.Run("AES128", func(t *testing.T) { testHardSymmetric(t, pkcs11.CKK_AES, 128) }) + t.Run("AES192", func(t *testing.T) { testHardSymmetric(t, pkcs11.CKK_AES, 192) }) + t.Run("AES256", func(t *testing.T) { testHardSymmetric(t, pkcs11.CKK_AES, 256) }) + t.Run("DES3", func(t *testing.T) { testHardSymmetric(t, pkcs11.CKK_DES3, 0) }) + Close() +} + +func testHardSymmetric(t *testing.T, keytype int, bits int) { + var err error + var key, key2 *PKCS11SecretKey + var id []byte + t.Run("Generate", func(t *testing.T) { + if key, err = GenerateSecretKey(bits, Ciphers[keytype]); err != nil { + t.Errorf("crypto11.GenerateSecretKey: %v", err) + return + } + if key == nil { + t.Errorf("crypto11.GenerateSecretKey: returned nil but no error") + return + } + if id, _, err = key.Identify(); err != nil { + t.Errorf("crypto11.PKCS11SecretKey.Identify: %v", err) + return + } + }) + var key2gen crypto.PrivateKey + t.Run("Find", func(t *testing.T) { + if key2gen, err = FindKey(id, nil); err != nil { + t.Errorf("crypto11.FindKey by id: %v", err) + return + } + key2 = key2gen.(*PKCS11SecretKey) + }) + t.Run("Block", func(t *testing.T) { testSymmetricBlock(t, key, key2) }) + iv := make([]byte, key.BlockSize()) + for i := 0; i < len(iv); i++ { + iv[i] = 0xF0 + } + t.Run("CBC", func(t *testing.T) { + testSymmetricMode(t, cipher.NewCBCEncrypter(key2, iv), cipher.NewCBCDecrypter(key2, iv)) + }) + t.Run("CBCClose", func(t *testing.T) { + var enc, dec BlockModeCloser + if enc, err = key2.NewCBCEncrypterCloser(iv); err != nil { + t.Errorf("NewCBCEncrypter: %v", err) + return + } + 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() + }) + t.Run("CBCSealOpen", func(t *testing.T) { + aead, err := key2.NewCBC(PaddingNone) + if err != nil { + t.Errorf("cipher.NewCBC: %v", err) + return + } + testAEADMode(t, aead, 128, 0) + }) + t.Run("CBCPKCSSealOpen", func(t *testing.T) { + aead, err := key2.NewCBC(PaddingPKCS) + if err != nil { + t.Errorf("cipher.NewCBC: %v", err) + return + } + testAEADMode(t, aead, 127, 0) + }) + if bits == 128 { + t.Run("GCMSoft", func(t *testing.T) { + aead, err := cipher.NewGCM(key2) + if err != nil { + t.Errorf("cipher.NewGCM: %v", err) + return + } + testAEADMode(t, aead, 127, 129) + }) + t.Run("GCMHard", func(t *testing.T) { + aead, err := key2.NewGCM() + if err != nil { + t.Errorf("key2.NewGCM: %v", err) + return + } + needMechanism(t, key2.Slot, pkcs11.CKM_AES_GCM) + testAEADMode(t, aead, 127, 129) + }) + // TODO check that hard/soft is consistent! + } + // TODO CFB + // TODO OFB + // TODO CTR + +} + +func testSymmetricBlock(t *testing.T, encryptKey cipher.Block, decryptKey cipher.Block) { + b := encryptKey.BlockSize() + input := make([]byte, 3*b) + middle := make([]byte, 3*b) + output := make([]byte, 3*b) + // Set a recognizable pattern in the buffers + for i := 0; i < 3*b; i++ { + input[i] = byte(i) + middle[i] = byte(i + 3*b) + output[i] = byte(i + 6*b) + } + encryptKey.Encrypt(middle, input) // middle[:b] = encrypt(input[:b]) + if bytes.Compare(input[:b], middle[:b]) == 0 { + t.Errorf("crypto11.PKCSSecretKey.Encrypt: identity transformation") + return + } + matches := 0 + for i := 0; i < b; i++ { + if middle[i] == byte(i+3*b) { + matches++ + } + } + if matches == b { + t.Errorf("crypto11.PKCSSecretKey.Encrypt: didn't modify destination") + return + } + for i := 0; i < 3*b; i++ { + if input[i] != byte(i) { + t.Errorf("crypto11.PKCSSecretKey.Encrypt: corrupted source") + return + } + if i >= b && middle[i] != byte(i+3*b) { + t.Errorf("crypto11.PKCSSecretKey.Encrypt: corrupted destination past blocksize") + return + } + } + decryptKey.Decrypt(output, middle) // output[:b] = decrypt(middle[:b]) + if bytes.Compare(input[:b], output[:b]) != 0 { + t.Errorf("crypto11.PKCSSecretKey.Decrypt: plaintext wrong") + return + } + for i := 0; i < 3*b; i++ { + if i >= b && output[i] != byte(i+6*b) { + t.Errorf("crypto11.PKCSSecretKey.Decrypt: corrupted destination past blocksize") + return + } + } +} + +func testSymmetricMode(t *testing.T, encrypt cipher.BlockMode, decrypt cipher.BlockMode) { + input := make([]byte, 256) + middle := make([]byte, 256) + output := make([]byte, 256) + // Set a recognizable pattern in the buffers + for i := 0; i < 256; i++ { + input[i] = byte(i) + middle[i] = byte(i + 32) + output[i] = byte(i + 64) + } + // Encrypt the first 128 bytes + encrypt.CryptBlocks(middle, input[:128]) + if bytes.Compare(input[:128], middle[:128]) == 0 { + t.Errorf("BlockMode.Encrypt: did not modify destination") + return + } + for i := 0; i < 128; i++ { + if input[i] != byte(i) { + t.Errorf("BlockMode.Encrypt: corrupted source") + return + } + } + for i := 128; i < 256; i++ { + if middle[i] != byte(i+32) { + t.Errorf("BlockMode.Encrypt: corrupted destination past input size") + return + } + } + // Encrypt the rest + encrypt.CryptBlocks(middle[128:], input[128:]) + // Decrypt in a single go + decrypt.CryptBlocks(output, middle) + if bytes.Compare(input, output) != 0 { + t.Errorf("BlockMode.Decrypt: plaintext wrong") + return + } +} + +func testAEADMode(t *testing.T, aead cipher.AEAD, ptlen int, adlen int) { + nonce := make([]byte, aead.NonceSize()) + plaintext := make([]byte, ptlen) + for i := 0; i < len(plaintext); i++ { + plaintext[i] = byte(i) + } + additionalData := make([]byte, adlen) + for i := 0; i < len(additionalData); i++ { + additionalData[i] = byte(i + 16) + } + ciphertext := aead.Seal([]byte{}, nonce, plaintext, additionalData) + decrypted, err := aead.Open([]byte{}, nonce, ciphertext, additionalData) + if err != nil { + t.Errorf("aead.Open: %s", err) + return + } + if bytes.Compare(plaintext, decrypted) != 0 { + t.Errorf("aead.Open: mismatch") + return + } +} + +func BenchmarkCBC(b *testing.B) { + ConfigureFromFile("config") + var err error + var key *PKCS11SecretKey + if key, err = GenerateSecretKey(128, Ciphers[pkcs11.CKK_AES]); err != nil { + b.Errorf("crypto11.GenerateSecretKey: %v", err) + return + } + iv := make([]byte, 16) + plaintext := make([]byte, 65536) + ciphertext := make([]byte, 65536) + b.Run("Native", func(b *testing.B) { + for i := 0; i < b.N; i++ { + mode := cipher.NewCBCEncrypter(key, iv) + 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) + } + runtime.GC() + }) + Close() +} + +// TODO BenchmarkGCM along the same lines as above