1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 01:18:00 +00:00
putty-source/crypto/aesgcm-footer.h
2023-08-22 19:36:03 +01:00

369 lines
12 KiB
C

/*
* 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 possible.
*
* 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),
};