diff options
Diffstat (limited to 'crypto/aesgcm-footer.h')
-rw-r--r-- | crypto/aesgcm-footer.h | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/crypto/aesgcm-footer.h b/crypto/aesgcm-footer.h new file mode 100644 index 00000000..981905da --- /dev/null +++ b/crypto/aesgcm-footer.h @@ -0,0 +1,368 @@ +/* + * Common footer included by every implementation of the AES-GCM MAC. + * + * The difficult part of AES-GCM, which is done differently depending + * on what hardware acceleration is available, is the actual + * evaluation of a polynomial over GF(2^128) whose coefficients are + * 128-bit chunks of data. But preparing those chunks in the first + * place (out of the ciphertext, associated data, and an + * administrative block containing the lengths of both) is done in the + * same way no matter what technique is used for the evaluation, so + * that's centralised into this file, along with as much of the other + * functionality as posible. + * + * This footer file is #included by each implementation, but each one + * will define its own struct type for the state, so that each alloc + * function will test sizeof() a different structure, and similarly + * for free when it zeroes out the state on cleanup. + * + * The functions in the source file may be defined as 'inline' so that + * the functions in here can inline them. The 'coeff' function in + * particular probably should be, because that's called once per + * 16-byte block, so eliminating function call overheads is especially + * useful there. + * + * This footer has the following expectations from the source file + * that #includes it: + * + * - define AESGCM_FLAVOUR to be a fragment of a C identifier that + * will be included in all the function names (both the ones + * defined in the implementation source file and those in here). + * For example purposes below I'll suppose that this is 'foo'. + * + * - define AESGCM_NAME to be a string literal that will be included + * in the display name of the implementation. + * + * - define a typedef 'aesgcm_foo' to be the state structure for the + * implementation, and inside that structure, expand the macro + * AESGCM_COMMON_FIELDS defined in aesgcm.h + * + * - define the following functions: + * + * // Determine whether this implementation is available at run time + * static bool aesgcm_foo_available(void); + * + * // Set up the 'key' of the polynomial part of the MAC, that is, + * // the value at which the polynomial will be evaluated. 'var' is + * // a 16-byte data block in the byte order it comes out of AES. + * static void aesgcm_foo_setkey_impl(aesgcm_foo *ctx, + * const unsigned char *var); + * + * // Set up at the start of evaluating an individual polynomial. + * // 'mask' is the 16-byte data block that will be XORed into the + * // output value of the polynomial, also in AES byte order. This + * // function should store 'mask' in whatever form is most + * // convenient, and initialise an accumulator to zero. + * static void aesgcm_foo_setup(aesgcm_foo *ctx, + * const unsigned char *mask); + * + * // Fold in a coefficient of the polynomial, by means of XORing + * // it into the accumulator and then multiplying the accumulator + * // by the variable passed to setkey_impl() above. + * // + * // 'coeff' points to the 16-byte block of data that the + * // polynomial coefficient will be made out of. + * // + * // You probably want to mark this function 'inline'. + * static void aesgcm_foo_coeff(aesgcm_foo *ctx, + * const unsigned char *coeff); + * + * // Generate the output MAC, by XORing the accumulator's final + * // value with the mask passed to setup() above. + * // + * // 'output' points to a 16-byte region of memory to write the + * // result to. + * static void aesgcm_foo_output(aesgcm_foo *ctx, + * unsigned char *output); + * + * - if allocation of the state structure must be done in a + * non-standard way (e.g. x86 needs this to force greater alignment + * than standard malloc provides), then #define SPECIAL_ALLOC and + * define this additional function: + * + * // Allocate a state structure, zero out its contents, and return it. + * static aesgcm_foo *aesgcm_foo_alloc(void); + * + * - if freeing must also be done in an unusual way, #define + * SPECIAL_FREE and define this function: + * + * // Zero out the state structure to avoid information leaks if the + * // memory is reused, and then free it. + * static void aesgcm_foo_free(aesgcm_foo *ctx); + */ + +#ifndef AESGCM_FLAVOUR +#error AESGCM_FLAVOUR must be defined by any module including this footer +#endif +#ifndef AESGCM_NAME +#error AESGCM_NAME must be defined by any module including this footer +#endif + +#define CONTEXT CAT(aesgcm_, AESGCM_FLAVOUR) +#define PREFIX(name) CAT(CAT(aesgcm_, AESGCM_FLAVOUR), CAT(_, name)) + +#include "aes.h" // for aes_encrypt_ecb_block + +static const char *PREFIX(mac_text_name)(ssh2_mac *mac) +{ + return "AES-GCM (" AESGCM_NAME ")"; +} + +static void PREFIX(mac_next_message)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Make the mask value for a single MAC instance, by encrypting + * the all-zeroes word using the associated AES instance in its + * ordinary GCM fashion. This consumes the first block of + * keystream (with per-block counter equal to 1), leaving the + * second block of keystream ready to be used on the first block + * of plaintext. + */ + unsigned char buf[16]; + memset(buf, 0, 16); + ssh_cipher_encrypt(ctx->cipher, buf, 16); + PREFIX(setup)(ctx, buf); /* give it to the implementation to store */ + smemclr(buf, sizeof(buf)); +} + +static void PREFIX(mac_setkey)(ssh2_mac *mac, ptrlen key) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Make the value of the polynomial variable, by encrypting the + * all-zeroes word using the associated AES instance in the + * special ECB mode. This is done via the special AES-specific API + * function encrypt_ecb_block, which doesn't touch the counter + * state at all. + */ + unsigned char var[16]; + memset(var, 0, 16); + aes_encrypt_ecb_block(ctx->cipher, var); + PREFIX(setkey_impl)(ctx, var); + smemclr(var, sizeof(var)); + + PREFIX(mac_next_message)(mac); /* set up mask */ +} + +static void PREFIX(mac_start)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + ctx->skipgot = ctx->aadgot = ctx->ciphertextlen = ctx->partlen = 0; +} + +/* + * Handle receiving data via the BinarySink API and turning it into a + * collection of 16-byte blocks to use as polynomial coefficients. + * + * This code is written in a fully general way, which is able to + * handle an arbitrary number of bytes at the start of the data to + * ignore completely (necessary for PuTTY integration), and an + * arbitrary number to treat as associated data, and the rest will be + * regarded as ciphertext. The stream can be interrupted at any byte + * position and resumed later; a partial block will be stored as + * necessary. + * + * At the time of writing this comment, in live use most of that + * generality isn't required: the full data is passed to this function + * in just one call. But there's no guarantee of that staying true in + * future, so we do the full deal here just in case, and the test + * vectors in cryptsuite.py will test it. (And they'll use + * set_prefix_lengths to set up different configurations from the SSH + * usage.) + */ +static void PREFIX(mac_BinarySink_write)( + BinarySink *bs, const void *blkv, size_t len) +{ + CONTEXT *ctx = BinarySink_DOWNCAST(bs, CONTEXT); + const unsigned char *blk = (const unsigned char *)blkv; + + /* + * Skip the prefix sequence number used as implicit extra data in + * SSH MACs. This is not included in the associated data field for + * GCM, because the IV incrementation policy provides its own + * sequence numbering. + */ + if (ctx->skipgot < ctx->skiplen) { + size_t n = ctx->skiplen - ctx->skipgot; + if (n > len) + n = len; + blk += n; + len -= n; + ctx->skipgot += n; + + if (len == 0) + return; + } + + /* + * Read additional authenticated data and fold it in to the MAC. + */ + while (ctx->aadgot < ctx->aadlen) { + size_t n = ctx->aadlen - ctx->aadgot; + if (n > len) + n = len; + + if (ctx->partlen || n < 16) { + /* + * Fold data into the partial block. + */ + if (n > 16 - ctx->partlen) + n = 16 - ctx->partlen; + memcpy(ctx->partblk + ctx->partlen, blk, n); + ctx->partlen += n; + } else if (n >= 16) { + /* + * Consume a whole block of AAD. + */ + PREFIX(coeff)(ctx, blk); + n = 16; + } + blk += n; + len -= n; + ctx->aadgot += n; + + if (ctx->partlen == 16) { + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + + if (ctx->aadgot == ctx->aadlen && ctx->partlen) { + memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + + if (len == 0) + return; + } + + /* + * Read the main ciphertext and fold it in to the MAC. + */ + while (len > 0) { + size_t n = len; + + if (ctx->partlen || n < 16) { + /* + * Fold data into the partial block. + */ + if (n > 16 - ctx->partlen) + n = 16 - ctx->partlen; + memcpy(ctx->partblk + ctx->partlen, blk, n); + ctx->partlen += n; + } else if (n >= 16) { + /* + * Consume a whole block of ciphertext. + */ + PREFIX(coeff)(ctx, blk); + n = 16; + } + blk += n; + len -= n; + ctx->ciphertextlen += n; + + if (ctx->partlen == 16) { + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + } +} + +static void PREFIX(mac_genresult)(ssh2_mac *mac, unsigned char *output) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Consume any partial block of ciphertext remaining. + */ + if (ctx->partlen) { + memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); + PREFIX(coeff)(ctx, ctx->partblk); + } + + /* + * Consume the final block giving the lengths of the AAD and ciphertext. + */ + unsigned char blk[16]; + memset(blk, 0, 16); + PUT_64BIT_MSB_FIRST(blk, ctx->aadlen * 8); + PUT_64BIT_MSB_FIRST(blk + 8, ctx->ciphertextlen * 8); + PREFIX(coeff)(ctx, blk); + + /* + * And call the implementation's output function. + */ + PREFIX(output)(ctx, output); + + smemclr(blk, sizeof(blk)); + smemclr(ctx->partblk, 16); +} + +static ssh2_mac *PREFIX(mac_new)(const ssh2_macalg *alg, ssh_cipher *cipher) +{ + const struct aesgcm_extra *extra = alg->extra; + if (!check_aesgcm_availability(extra)) + return NULL; + +#ifdef SPECIAL_ALLOC + CONTEXT *ctx = PREFIX(alloc)(); +#else + CONTEXT *ctx = snew(CONTEXT); + memset(ctx, 0, sizeof(CONTEXT)); +#endif + + ctx->mac.vt = alg; + ctx->cipher = cipher; + /* Default values for SSH-2, overridable by set_prefix_lengths for + * testcrypt purposes */ + ctx->skiplen = 4; + ctx->aadlen = 4; + BinarySink_INIT(ctx, PREFIX(mac_BinarySink_write)); + BinarySink_DELEGATE_INIT(&ctx->mac, ctx); + return &ctx->mac; +} + +static void PREFIX(set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + ctx->skiplen = skip; + ctx->aadlen = aad; +} + +static void PREFIX(mac_free)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); +#ifdef SPECIAL_FREE + PREFIX(free)(ctx); +#else + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +#endif +} + +static struct aesgcm_extra_mutable PREFIX(extra_mut); + +static const struct aesgcm_extra PREFIX(extra) = { + .check_available = PREFIX(available), + .mut = &PREFIX(extra_mut), + .set_prefix_lengths = PREFIX(set_prefix_lengths), +}; + +const ssh2_macalg CAT(ssh2_aesgcm_mac_, AESGCM_FLAVOUR) = { + .new = PREFIX(mac_new), + .free = PREFIX(mac_free), + .setkey = PREFIX(mac_setkey), + .start = PREFIX(mac_start), + .genresult = PREFIX(mac_genresult), + .next_message = PREFIX(mac_next_message), + .text_name = PREFIX(mac_text_name), + .name = "", + .etm_name = "", /* Not selectable independently */ + .len = 16, + .keylen = 0, + .extra = &PREFIX(extra), +}; |