--- /dev/null
+/* -*-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 -------------------------------------------------*/