diff options
Diffstat (limited to 'ocb.c')
-rw-r--r-- | ocb.c | 432 |
1 files changed, 432 insertions, 0 deletions
@@ -0,0 +1,432 @@ +/* ocb.c + + OCB AEAD mode, RFC 7253 + + Copyright (C) 2021 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include <string.h> + +#include "ocb.h" +#include "block-internal.h" +#include "bswap-internal.h" +#include "memops.h" + +#define OCB_MAX_BLOCKS 16 + +/* Returns 64 bits from the concatenation (u0, u1), starting from bit offset. */ +static inline uint64_t +extract(uint64_t u0, uint64_t u1, unsigned offset) +{ + if (offset == 0) + return u0; + u0 = bswap64_if_le(u0); + u1 = bswap64_if_le(u1); + return bswap64_if_le((u0 << offset) | (u1 >> (64 - offset))); +} + +void +ocb_set_key (struct ocb_key *key, const void *cipher, nettle_cipher_func *f) +{ + static const union nettle_block16 zero_block; + f (cipher, OCB_BLOCK_SIZE, key->L[0].b, zero_block.b); + block16_mulx_be (&key->L[1], &key->L[0]); + block16_mulx_be (&key->L[2], &key->L[1]); +} + +/* Add x^k L[2], where k is the number of trailing zero bits in i. */ +static void +update_offset(const struct ocb_key *key, + union nettle_block16 *offset, size_t i) +{ + if (i & 1) + block16_xor (offset, &key->L[2]); + else + { + assert (i > 0); + union nettle_block16 diff; + block16_mulx_be (&diff, &key->L[2]); + for (i >>= 1; !(i&1); i >>= 1) + block16_mulx_be (&diff, &diff); + + block16_xor (offset, &diff); + } +} + +static void +pad_block (union nettle_block16 *block, size_t length, const uint8_t *data) +{ + memcpy (block->b, data, length); + block->b[length] = 0x80; + memset (block->b + length + 1, 0, OCB_BLOCK_SIZE - 1 - length); +} + +void +ocb_set_nonce (struct ocb_ctx *ctx, + const void *cipher, nettle_cipher_func *f, + size_t tag_length, + size_t nonce_length, const uint8_t *nonce) +{ + union nettle_block16 top; + uint64_t stretch; + + unsigned bottom; + assert (nonce_length < 16); + assert (tag_length > 0); + assert (tag_length <= 16); + + /* Bit size, or zero for tag_length == 16 */ + top.b[0] = (tag_length & 15) << 4; + memset (top.b + 1, 0, 15 - nonce_length); + top.b[15 - nonce_length] |= 1; + memcpy (top.b + 16 - nonce_length, nonce, nonce_length); + bottom = top.b[15] & 0x3f; + top.b[15] &= 0xc0; + + f (cipher, OCB_BLOCK_SIZE, top.b, top.b); + + stretch = top.u64[0]; +#if WORDS_BIGENDIAN + stretch ^= (top.u64[0] << 8) | (top.u64[1] >> 56); +#else + stretch ^= (top.u64[0] >> 8) | (top.u64[1] << 56); +#endif + + ctx->initial.u64[0] = extract(top.u64[0], top.u64[1], bottom); + ctx->initial.u64[1] = extract(top.u64[1], stretch, bottom); + ctx->sum.u64[0] = ctx->sum.u64[1] = 0; + ctx->checksum.u64[0] = ctx->checksum.u64[1] = 0; + + ctx->data_count = ctx->message_count = 0; +} + +static void +ocb_fill_n (const struct ocb_key *key, + union nettle_block16 *offset, size_t count, + size_t n, union nettle_block16 *o) +{ + assert (n > 0); + union nettle_block16 *prev; + if (count & 1) + prev = offset; + else + { + /* Do a single block to align block count. */ + count++; /* Always odd. */ + block16_xor (offset, &key->L[2]); + block16_set (&o[0], offset); + prev = o; + n--; o++; + } + + for (; n >= 2; n -= 2, o += 2) + { + size_t i; + count += 2; /* Always odd. */ + + /* Based on trailing zeros of ctx->message_count - 1, the + initial shift below discards a one bit. */ + block16_mulx_be (&o[0], &key->L[2]); + for (i = count >> 1; !(i&1); i >>= 1) + block16_mulx_be (&o[0], &o[0]); + + block16_xor (&o[0], prev); + block16_xor3 (&o[1], &o[0], &key->L[2]); + prev = &o[1]; + } + block16_set(offset, prev); + + if (n > 0) + { + update_offset (key, offset, ++count); + block16_set (o, offset); + } +} + +void +ocb_update (struct ocb_ctx *ctx, const struct ocb_key *key, + const void *cipher, nettle_cipher_func *f, + size_t length, const uint8_t *data) +{ + union nettle_block16 block[OCB_MAX_BLOCKS]; + size_t n = length / OCB_BLOCK_SIZE; + assert (ctx->message_count == 0); + + if (ctx->data_count == 0) + ctx->offset.u64[0] = ctx->offset.u64[1] = 0; + + while (n > 0) + { + size_t size, i; + size_t blocks = (n <= OCB_MAX_BLOCKS) ? n + : OCB_MAX_BLOCKS - 1 + (ctx->data_count & 1); + + ocb_fill_n (key, &ctx->offset, ctx->data_count, blocks, block); + ctx->data_count += blocks; + + size = blocks * OCB_BLOCK_SIZE; + memxor (block[0].b, data, size); + f (cipher, size, block[0].b, block[0].b); + for (i = 0; i < blocks; i++) + block16_xor(&ctx->sum, &block[i]); + + n -= blocks; data += size; + } + + length &= 15; + if (length > 0) + { + union nettle_block16 block; + pad_block (&block, length, data); + block16_xor (&ctx->offset, &key->L[0]); + block16_xor (&block, &ctx->offset); + + f (cipher, OCB_BLOCK_SIZE, block.b, block.b); + block16_xor (&ctx->sum, &block); + } +} + +static void +ocb_crypt_n (struct ocb_ctx *ctx, const struct ocb_key *key, + const void *cipher, nettle_cipher_func *f, + size_t n, uint8_t *dst, const uint8_t *src) +{ + union nettle_block16 o[OCB_MAX_BLOCKS], block[OCB_MAX_BLOCKS]; + size_t size; + + while (n > 0) + { + size_t blocks = (n <= OCB_MAX_BLOCKS) ? n + : OCB_MAX_BLOCKS - 1 + (ctx->message_count & 1); + + ocb_fill_n (key, &ctx->offset, ctx->message_count, blocks, o); + ctx->message_count += n; + + size = blocks * OCB_BLOCK_SIZE; + memxor3 (block[0].b, o[0].b, src, size); + f (cipher, size, block[0].b, block[0].b); + memxor3 (dst, block[0].b, o[0].b, size); + + n -= blocks; src += size; dst -= size; + } +} + +/* Rotate bytes c positions to the right, in memory order. */ +#if WORDS_BIGENDIAN +# define MEM_ROTATE_RIGHT(c, s0, s1) do { \ + uint64_t __rotate_t = ((s0) >> (8*(c))) | ((s1) << (64-8*(c))); \ + (s1) = ((s1) >> (8*(c))) | ((s0) << (64-8*(c))); \ + (s0) = __rotate_t; \ + } while (0) +#else +# define MEM_ROTATE_RIGHT(c, s0, s1) do { \ + uint64_t __rotate_t = ((s0) << (8*(c))) | ((s1) >> (64-8*(c))); \ + (s1) = ((s1) << (8*(c))) | ((s0) >> (64-8*(c))); \ + (s0) = __rotate_t; \ + } while (0) +#endif + +/* Mask for the first c bytes in memory */ +#if WORDS_BIGENDIAN +# define MEM_MASK(c) (-((uint64_t) 1 << (64 - 8*(c)))) +#else +# define MEM_MASK(c) (((uint64_t) 1 << (8*(c))) - 1) +#endif + +/* Checksum of n complete blocks. */ +static void +ocb_checksum_n (union nettle_block16 *checksum, + size_t n, const uint8_t *src) +{ + unsigned initial; + uint64_t edge_word = 0; + uint64_t s0, s1; + + if (n == 1) + { + memxor (checksum->b, src, OCB_BLOCK_SIZE); + return; + } + + /* Initial unaligned bytes. */ + initial = -(uintptr_t) src & 7; + + if (initial > 0) + { + /* Input not 64-bit aligned. Read initial bytes. */ + unsigned i; + /* Edge word is read in big-endian order */ + for (i = initial; i > 0; i--) + edge_word = (edge_word << 8) + *src++; + n--; + } + + /* Now src is 64-bit aligned, so do 64-bit reads. */ + for (s0 = s1 = 0 ; n > 0; n--, src += OCB_BLOCK_SIZE) + { + s0 ^= ((const uint64_t *) src)[0]; + s1 ^= ((const uint64_t *) src)[1]; + } + if (initial > 0) + { + unsigned i; + uint64_t mask; + s0 ^= ((const uint64_t *) src)[0]; + for (i = 8 - initial, src += 8; i > 0; i--) + edge_word = (edge_word << 8) + *src++; + + /* Rotate [s0, s1] right initial bytes. */ + MEM_ROTATE_RIGHT(initial, s0, s1); + /* Add in the edge bytes. */ + mask = MEM_MASK(initial); + edge_word = bswap64_if_le (edge_word); + s0 ^= (edge_word & mask); + s1 ^= (edge_word & ~mask); + } + checksum->u64[0] ^= s0; + checksum->u64[1] ^= s1; +} + +void +ocb_encrypt (struct ocb_ctx *ctx, const struct ocb_key *key, + const void *cipher, nettle_cipher_func *f, + size_t length, uint8_t *dst, const uint8_t *src) +{ + size_t n = length / OCB_BLOCK_SIZE; + + if (ctx->message_count == 0) + ctx->offset = ctx->initial; + + if (n > 0) + { + ocb_checksum_n (&ctx->checksum, n, src); + ocb_crypt_n (ctx, key, cipher, f, n, dst, src); + length &= 15; + } + if (length > 0) + { + union nettle_block16 block; + + src += n*OCB_BLOCK_SIZE; dst += n*OCB_BLOCK_SIZE; + + pad_block (&block, length, src); + block16_xor (&ctx->checksum, &block); + + block16_xor (&ctx->offset, &key->L[0]); + f (cipher, OCB_BLOCK_SIZE, block.b, ctx->offset.b); + memxor3 (dst, block.b, src, length); + ctx->message_count++; + } +} + +void +ocb_decrypt (struct ocb_ctx *ctx, const struct ocb_key *key, + const void *encrypt_ctx, nettle_cipher_func *encrypt, + const void *decrypt_ctx, nettle_cipher_func *decrypt, + size_t length, uint8_t *dst, const uint8_t *src) +{ + size_t n = length / OCB_BLOCK_SIZE; + + if (ctx->message_count == 0) + ctx->offset = ctx->initial; + + if (n > 0) + { + ocb_crypt_n (ctx, key, decrypt_ctx, decrypt, n, dst, src); + ocb_checksum_n (&ctx->checksum, n, dst); + length &= 15; + } + if (length > 0) + { + union nettle_block16 block; + + src += n*OCB_BLOCK_SIZE; dst += n*OCB_BLOCK_SIZE; + + block16_xor (&ctx->offset, &key->L[0]); + encrypt (encrypt_ctx, OCB_BLOCK_SIZE, block.b, ctx->offset.b); + memxor3 (dst, block.b, src, length); + + pad_block (&block, length, dst); + block16_xor (&ctx->checksum, &block); + ctx->message_count++; + } +} + +void +ocb_digest (const struct ocb_ctx *ctx, const struct ocb_key *key, + const void *cipher, nettle_cipher_func *f, + size_t length, uint8_t *digest) +{ + union nettle_block16 block; + assert (length <= OCB_DIGEST_SIZE); + block16_xor3 (&block, &key->L[1], + (ctx->message_count > 0) ? &ctx->offset : &ctx->initial); + block16_xor (&block, &ctx->checksum); + f (cipher, OCB_BLOCK_SIZE, block.b, block.b); + memxor3 (digest, block.b, ctx->sum.b, length); +} + +void +ocb_encrypt_message (const struct ocb_key *key, + const void *cipher, nettle_cipher_func *f, + size_t nlength, const uint8_t *nonce, + size_t alength, const uint8_t *adata, + size_t tlength, + size_t clength, uint8_t *dst, const uint8_t *src) +{ + struct ocb_ctx ctx; + assert (clength >= tlength); + ocb_set_nonce (&ctx, cipher, f, tlength, nlength, nonce); + ocb_update (&ctx, key, cipher, f, alength, adata); + ocb_encrypt (&ctx, key, cipher, f, clength - tlength, dst, src); + ocb_digest (&ctx, key, cipher, f, tlength, dst + clength - tlength); +} + +int +ocb_decrypt_message (const struct ocb_key *key, + const void *encrypt_ctx, nettle_cipher_func *encrypt, + const void *decrypt_ctx, nettle_cipher_func *decrypt, + size_t nlength, const uint8_t *nonce, + size_t alength, const uint8_t *adata, + size_t tlength, + size_t mlength, uint8_t *dst, const uint8_t *src) +{ + struct ocb_ctx ctx; + union nettle_block16 digest; + ocb_set_nonce (&ctx, encrypt_ctx, encrypt, tlength, nlength, nonce); + ocb_update (&ctx, key, encrypt_ctx, encrypt, alength, adata); + ocb_decrypt (&ctx, key, encrypt_ctx, encrypt, decrypt_ctx, decrypt, + mlength, dst, src); + ocb_digest (&ctx, key, encrypt_ctx, encrypt, tlength, digest.b); + return memeql_sec(digest.b, src + mlength, tlength); +} |