symm/ccm.h, symm/ccm-def.h: Implement the CCM authenticated encryption mode.
[catacomb] / symm / ccm.c
diff --git a/symm/ccm.c b/symm/ccm.c
new file mode 100644 (file)
index 0000000..d65e049
--- /dev/null
@@ -0,0 +1,201 @@
+/* -*-c-*-
+ *
+ * CCM common code
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Catacomb 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "ccm.h"
+#include "ccm-def.h"
+
+/*----- MAC header and counter formatting ---------------------------------*
+ *
+ * CCM's most endearing feature is its complex formatting of the MAC
+ * initialization vector and counter blocks.  This is only specified for
+ * 128-bit blocks, so I've made up formats for 64-, 192-, and 256-bit blocks.
+ * For completeness and ease of comparison, all of the encodings are defined
+ * here.  Encoding of the rest of the MAC input data is common.
+ *
+ * The notation is taken from the definition of CCM in NIST SP800-38C.
+ *
+ *   * [x]_w = encoding of x, as w bits
+ *   * P = plaintext/ciphertext
+ *   * p = length of P in octets
+ *   * Q = [p]_{8q} = encoding of p
+ *   * q = length of Q in octets
+ *   * N = nonce
+ *   * H = B_0 = MAC header block
+ *   * C_i = the i-th counter block
+ *   * t = length of tag in octets
+ *
+ * 128-bit blocks (SP800-38C, appendix A):
+ *
+ * Let F be an octet:
+ *
+ *     Bits            7       6       6:3             3:0
+ *     Meaning         0       AAD?    [t/2 - 1]_3     [q - 1]_3
+ *
+ * Then H = F || N || Q and C_i = 0^5 || [q - 1]_3 || N || [i]_{8q}.
+ *
+ * 64-bit blocks (new):
+ *
+ * Let F be an octet:
+ *
+ *     Bits            7       6       6:3             3:0
+ *     Meaning         0       AAD?    [t - 1]_3       [q - 1]_3
+ *
+ * Then H = F || N || Q and C_i = 0^5 || [q - 1]_3 || N || [i]_{8q}.
+ *
+ * (i.e., almost exactly the same, except that the tag no longer needs to be
+ * an even number of octets).
+ *
+ * n-bit blocks, for n > 128
+ *
+ * Let F be a 16-bit flags word:
+ *
+ *     Bits            15      15:8            7       7:0
+ *     Meaning         0       [t]_7           AAD?    [q]_7
+ *
+ * Then H = F || N || Q and C_i = 0^9 || [q - 1]_7 || N || [i]_{8q}.
+ */
+
+/* --- @ccm_paramsok@ --- *
+ *
+ * Arguments:  @const ccm_params *p@ = pointer to parameters
+ *
+ * Returns:    True (nonzero) if the parameters are OK; false (zero) if
+ *             there's a problem.
+ *
+ * Use:                Verify that the CCM parameters are acceptable.
+ */
+
+int ccm_check(const ccm_params *p)
+{
+  unsigned fsz = p->bsz <= 16 ? 1 : 2, q = p->bsz - p->nsz - fsz, i;
+  size_t msz;
+
+  /* Check that the block size hasn't been bungled. */
+  if (p->bsz < 16 && p->bsz != 8) return (0);
+
+  /* The length-of-the-length must be representable, and its encoding must
+   * not be zero, since this is `reserved'.  The small-block encoding stores
+   * %$q - 1$%, so we must have %$q \ge 2$%; the large-block encoding stores
+   * %$q$% directly, so only %$q = 0$% is forbidden.
+   */
+  if (p->nsz > p->bsz - fsz - (p->bsz <= 16 ? 2 : 1)) return (0);
+  if (q > (p->bsz <= 16 ? 8 : 127)) return (0);
+
+  /* Verify that the message length will fit in the space allotted. */
+  for (i = 1, msz = p->msz >> 8; msz; i++, msz >>= 8);
+  if (i > q) return (0);
+
+  /* The tag can't be larger than the block size.  Also, it must be
+   * representable, and its encoding must not be zero, because otherwise it'd
+   * be possible for a MAC header block to look like a counter block.  The
+   * tag encoding is fiddly: for 64-bit blocks, we store %$t - 1$%, so we
+   * must have %$t \ge 2$%; for 128-bit blocks, we store %$t/2 - 2$%, so
+   * we must have %$t \ge 4$% with %$t$% even; otherwise, we store %$t$%
+   * directly, so the only requirement is that %$t \ge 1$%.
+   */
+  if (p->tsz > p->bsz) return (0);
+  if (p->tsz < (p->bsz == 8 ? 2 : p->bsz == 16 ? 4 : 1)) return (0);
+  if (p->bsz == 16 && p->tsz%2 != 0) return (0);
+
+  /* All looks good. */
+  return (1);
+}
+
+/* --- @ccm_fmthdr@ --- *
+ *
+ * Arguments:  @const ccm_params *p@ = pointer to parameters
+ *             @octet *b@ = block-size buffer to write header
+ *             @const void *n@ = pointer to nonce
+ *
+ * Returns:    ---
+ *
+ * Use:                Format a MAC header block.
+ */
+
+void ccm_fmthdr(const ccm_params *p, octet *b, const void *n)
+{
+  size_t fsz = p->bsz <= 16 ? 1 : 2, q = p->bsz - p->nsz - fsz;
+  unsigned f0 = 0, f1 = 0;
+  size_t i, t;
+
+  /* Encode whether the AAD is empty. */
+  if (!p->hsz) /* do nothing */;
+  else if (p->bsz <= 16) f0 |= 0x40;
+  else f1 |= 0x80;
+
+  /* Encode the tag size. */
+  switch (p->bsz) {
+    case  8: f0 |= (p->tsz - 1) << 3; break;
+    case 16: f0 |= (p->tsz - 2) << 2; break;
+    default: f0 |=  p->tsz;          break;
+  }
+
+  /* Encode the length-of-the-length.  (This is the most bletcherous part of
+   * CCM.)
+   */
+  if (p->bsz <= 16) f0 |= q - 1;
+  else f1 |= q;
+
+  /* Insert the flags and nonce. */
+  b[0] = f0; memcpy(b + fsz, n, p->nsz);
+  if (p->bsz > 16) b[1] = f1;
+
+  /* Write the message length. */
+  for (i = 0, t = p->msz; i < q; i++, t >>= 8) b[p->bsz - i - 1] = U8(t);
+}
+
+/* --- @ccm_fmtctr@ --- *
+ *
+ * Arguments:  @const ccm_params *p@ = pointer to parameters
+ *             @octet *b@ = block-size buffer to write header
+ *             @const void *n@ = pointer to nonce
+ *
+ * Returns:    ---
+ *
+ * Use:                Format an initial counter block.
+ */
+
+void ccm_fmtctr(const ccm_params *p, octet *b, const void *n)
+{
+  size_t fsz = p->bsz <= 16 ? 1 : 2, q = p->bsz - p->nsz - fsz;
+  unsigned f0 = 0, f1 = 0;
+
+  /* Encode the message length length.  (Did I complain about this?) */
+  if (p->bsz <= 16) f0 |= q - 1;
+  else f1 |= q;
+
+  /* Insert the flags and nonce. */
+  b[0] = f0; memcpy(b + fsz, n, p->nsz);
+  if (p->bsz > 16) b[1] = f1;
+
+  /* Zero out the initial counter. */
+  memset(b + fsz + p->nsz, 0, q);
+}
+
+/*----- That's all, folks -------------------------------------------------*/