summaryrefslogtreecommitdiff
path: root/cipher/cipher-cmac.c
diff options
context:
space:
mode:
authorJussi Kivilinna <jussi.kivilinna@iki.fi>2013-11-14 14:10:27 +0200
committerJussi Kivilinna <jussi.kivilinna@iki.fi>2013-11-19 19:18:56 +0200
commitb49cd64aaaff2e5488a84665362ef7150683226c (patch)
tree08a9aa2bd510d0231fa63ca481b03c5f42457aee /cipher/cipher-cmac.c
parentfcd6da37d55f248d3558ee0ff385b41b866e7ded (diff)
downloadlibgcrypt-b49cd64aaaff2e5488a84665362ef7150683226c.tar.gz
Add CMAC (Cipher-based MAC) to MAC API
* cipher/Makefile.am: Add 'cipher-cmac.c' and 'mac-cmac.c'. * cipher/cipher-cmac.c: New. * cipher/cipher-internal.h (gcry_cipher_handle.u_mode): Add 'cmac'. * cipher/cipher.c (gcry_cipher_open): Rename to... (_gcry_cipher_open_internal): ...this and add CMAC. (gcry_cipher_open): New wrapper that disallows use of internal modes (CMAC) from outside. (cipher_setkey, cipher_encrypt, cipher_decrypt) (_gcry_cipher_authenticate, _gcry_cipher_gettag) (_gcry_cipher_checktag): Add handling for CMAC mode. (cipher_reset): Do not reset 'marks.key' and do not clear subkeys in 'u_mode' in CMAC mode. * cipher/mac-cmac.c: New. * cipher/mac-internal.h: Add CMAC support and algorithms. * cipher/mac.c: Add CMAC algorithms. * doc/gcrypt.texi: Add documentation for CMAC. * src/cipher.h (gcry_cipher_internal_modes): New. (_gcry_cipher_open_internal, _gcry_cipher_cmac_authenticate) (_gcry_cipher_cmac_get_tag, _gcry_cipher_cmac_check_tag) (_gcry_cipher_cmac_set_subkeys): New prototypes. * src/gcrypt.h.in (gcry_mac_algos): Add CMAC algorithms. * tests/basic.c (check_mac): Add CMAC test vectors. -- Patch adds CMAC (Cipher-based MAC) as defined in RFC 4493 and NIST Special Publication 800-38B. Internally CMAC is added to cipher module, but is available to outside only through MAC API. [v2]: - Add documentation. [v3]: - CMAC algorithm ids start from 201. - Coding style fixes. Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi>
Diffstat (limited to 'cipher/cipher-cmac.c')
-rw-r--r--cipher/cipher-cmac.c238
1 files changed, 238 insertions, 0 deletions
diff --git a/cipher/cipher-cmac.c b/cipher/cipher-cmac.c
new file mode 100644
index 00000000..25d5db26
--- /dev/null
+++ b/cipher/cipher-cmac.c
@@ -0,0 +1,238 @@
+/* cmac.c - CMAC, Cipher-based MAC.
+ * Copyright © 2013 Jussi Kivilinna <jussi.kivilinna@iki.fi>
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "g10lib.h"
+#include "cipher.h"
+#include "cipher-internal.h"
+#include "bufhelp.h"
+
+
+#define set_burn(burn, nburn) do { \
+ unsigned int __nburn = (nburn); \
+ (burn) = (burn) > __nburn ? (burn) : __nburn; } while (0)
+
+
+static void
+cmac_write (gcry_cipher_hd_t c, const byte * inbuf, size_t inlen)
+{
+ gcry_cipher_encrypt_t enc_fn = c->spec->encrypt;
+ const unsigned int blocksize = c->spec->blocksize;
+ byte outbuf[MAX_BLOCKSIZE];
+ unsigned int burn = 0;
+ unsigned int nblocks;
+
+ if (!inlen || !inbuf)
+ return;
+
+ /* Last block is needed for cmac_final. */
+ if (c->unused + inlen <= blocksize)
+ {
+ for (; inlen && c->unused < blocksize; inlen--)
+ c->lastiv[c->unused++] = *inbuf++;
+ return;
+ }
+
+ if (c->unused)
+ {
+ for (; inlen && c->unused < blocksize; inlen--)
+ c->lastiv[c->unused++] = *inbuf++;
+
+ buf_xor (c->u_iv.iv, c->u_iv.iv, c->lastiv, blocksize);
+ set_burn (burn, enc_fn (&c->context.c, c->u_iv.iv, c->u_iv.iv));
+
+ c->unused = 0;
+ }
+
+ if (c->bulk.cbc_enc && inlen > blocksize)
+ {
+ nblocks = inlen / blocksize;
+ nblocks -= (nblocks * blocksize == inlen);
+
+ c->bulk.cbc_enc (&c->context.c, c->u_iv.iv, outbuf, inbuf, nblocks, 1);
+ inbuf += nblocks * blocksize;
+ inlen -= nblocks * blocksize;
+
+ wipememory (outbuf, sizeof (outbuf));
+ }
+ else
+ while (inlen > blocksize)
+ {
+ buf_xor (c->u_iv.iv, c->u_iv.iv, inbuf, blocksize);
+ set_burn (burn, enc_fn (&c->context.c, c->u_iv.iv, c->u_iv.iv));
+ inlen -= blocksize;
+ inbuf += blocksize;
+ }
+
+ /* Make sure that last block is passed to cmac_final. */
+ if (inlen == 0)
+ BUG ();
+
+ for (; inlen && c->unused < blocksize; inlen--)
+ c->lastiv[c->unused++] = *inbuf++;
+
+ if (burn)
+ _gcry_burn_stack (burn + 4 * sizeof (void *));
+}
+
+
+static void
+cmac_generate_subkeys (gcry_cipher_hd_t c)
+{
+ const unsigned int blocksize = c->spec->blocksize;
+ byte rb, carry, t, bi;
+ unsigned int burn;
+ int i, j;
+ union
+ {
+ size_t _aligned;
+ byte buf[MAX_BLOCKSIZE];
+ } u;
+
+ if (MAX_BLOCKSIZE < blocksize)
+ BUG ();
+
+ /* encrypt zero block */
+ memset (u.buf, 0, blocksize);
+ burn = c->spec->encrypt (&c->context.c, u.buf, u.buf);
+
+ /* Currently supported blocksizes are 16 and 8. */
+ rb = blocksize == 16 ? 0x87 : 0x1B /*blocksize == 8 */ ;
+
+ for (j = 0; j < 2; j++)
+ {
+ /* Generate subkeys K1 and K2 */
+ carry = 0;
+ for (i = blocksize - 1; i >= 0; i--)
+ {
+ bi = u.buf[i];
+ t = carry | (bi << 1);
+ carry = bi >> 7;
+ u.buf[i] = t & 0xff;
+ c->u_mode.cmac.subkeys[j][i] = u.buf[i];
+ }
+ u.buf[blocksize - 1] ^= carry ? rb : 0;
+ c->u_mode.cmac.subkeys[j][blocksize - 1] = u.buf[blocksize - 1];
+ }
+
+ wipememory (&u, sizeof (u));
+ if (burn)
+ _gcry_burn_stack (burn + 4 * sizeof (void *));
+}
+
+
+static void
+cmac_final (gcry_cipher_hd_t c)
+{
+ const unsigned int blocksize = c->spec->blocksize;
+ unsigned int count = c->unused;
+ unsigned int burn;
+ byte *subkey;
+
+ if (count == blocksize)
+ subkey = c->u_mode.cmac.subkeys[0]; /* K1 */
+ else
+ {
+ subkey = c->u_mode.cmac.subkeys[1]; /* K2 */
+ c->lastiv[count++] = 0x80;
+ while (count < blocksize)
+ c->lastiv[count++] = 0;
+ }
+
+ buf_xor (c->lastiv, c->lastiv, subkey, blocksize);
+
+ buf_xor (c->u_iv.iv, c->u_iv.iv, c->lastiv, blocksize);
+ burn = c->spec->encrypt (&c->context.c, c->u_iv.iv, c->u_iv.iv);
+ if (burn)
+ _gcry_burn_stack (burn + 4 * sizeof (void *));
+
+ c->unused = 0;
+}
+
+
+static gcry_err_code_t
+cmac_tag (gcry_cipher_hd_t c, unsigned char *tag, size_t taglen, int check)
+{
+ if (!tag || taglen == 0 || taglen > c->spec->blocksize)
+ return GPG_ERR_INV_ARG;
+
+ if (!c->u_mode.cmac.tag)
+ {
+ cmac_final (c);
+ c->u_mode.cmac.tag = 1;
+ }
+
+ if (!check)
+ {
+ memcpy (tag, c->u_iv.iv, taglen);
+ return GPG_ERR_NO_ERROR;
+ }
+ else
+ {
+ return buf_eq_const (tag, c->u_iv.iv, taglen) ?
+ GPG_ERR_NO_ERROR : GPG_ERR_CHECKSUM;
+ }
+}
+
+
+gcry_err_code_t
+_gcry_cipher_cmac_authenticate (gcry_cipher_hd_t c,
+ const unsigned char *abuf, size_t abuflen)
+{
+ if (abuflen > 0 && !abuf)
+ return GPG_ERR_INV_ARG;
+ if (c->u_mode.cmac.tag)
+ return GPG_ERR_INV_STATE;
+ /* To support new blocksize, update cmac_generate_subkeys() then add new
+ blocksize here. */
+ if (c->spec->blocksize != 16 && c->spec->blocksize != 8)
+ return GPG_ERR_INV_CIPHER_MODE;
+
+ cmac_write (c, abuf, abuflen);
+
+ return GPG_ERR_NO_ERROR;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_cmac_get_tag (gcry_cipher_hd_t c,
+ unsigned char *outtag, size_t taglen)
+{
+ return cmac_tag (c, outtag, taglen, 0);
+}
+
+
+gcry_err_code_t
+_gcry_cipher_cmac_check_tag (gcry_cipher_hd_t c,
+ const unsigned char *intag, size_t taglen)
+{
+ return cmac_tag (c, (unsigned char *) intag, taglen, 1);
+}
+
+gcry_err_code_t
+_gcry_cipher_cmac_set_subkeys (gcry_cipher_hd_t c)
+{
+ cmac_generate_subkeys (c);
+
+ return GPG_ERR_NO_ERROR;
+}