blob: 7ef87fc1e62be22436468986a2640c2f10cf3e75 [file] [log] [blame]
/*
* cifra - embedded cryptography library
* Written in 2014 by Joseph Birr-Pixton <jpixton@gmail.com>
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "handy.h"
#include "prp.h"
#include "modes.h"
#include "tassert.h"
#include <string.h>
#define CCM_ADATA_PRESENT 0x40
static void write_be(uint8_t *out, size_t value, size_t bytes)
{
while (bytes)
{
out[bytes - 1] = value & 0xff;
value >>= 8;
bytes--;
}
assert(value == 0); /* or we couldn't encode the value. */
}
static void zero_pad(cf_cbcmac_stream *cm)
{
cf_cbcmac_stream_finish_block_zero(cm);
}
/* nb. block is general workspace. */
static void add_aad(cf_cbcmac_stream *cm, uint8_t block[CF_MAXBLOCK],
const uint8_t *header, size_t nheader)
{
assert(nheader <= 0xffffffff); /* we don't support 64 bit lengths. */
/* Add length using stupidly complicated rules. */
if (nheader < 0xff00)
{
write_be(block, nheader, 2);
cf_cbcmac_stream_update(cm, block, 2);
} else {
write_be(block, 0xfffe, 2);
write_be(block + 2, nheader, 4);
cf_cbcmac_stream_update(cm, block, 6);
}
cf_cbcmac_stream_update(cm, header, nheader);
zero_pad(cm);
}
static void add_block0(cf_cbcmac_stream *cm,
uint8_t block[CF_MAXBLOCK], size_t nblock,
const uint8_t *nonce, size_t nnonce,
size_t L, size_t nplain,
size_t nheader, size_t ntag)
{
/* Construct first block B_0. */
block[0] = ((nheader == 0) ? 0x00 : CCM_ADATA_PRESENT) |
((ntag - 2) / 2) << 3 |
(L - 1);
memcpy(block + 1, nonce, nnonce);
write_be(block + 1 + nnonce, nplain, L);
cf_cbcmac_stream_update(cm, block, nblock);
}
static void build_ctr_nonce(uint8_t ctr_nonce[CF_MAXBLOCK],
size_t L,
const uint8_t *nonce, size_t nnonce)
{
ctr_nonce[0] = (L - 1);
memcpy(ctr_nonce + 1, nonce, nnonce);
memset(ctr_nonce + 1 + nnonce, 0, L);
}
void cf_ccm_encrypt(const cf_prp *prp, void *prpctx,
const uint8_t *plain, size_t nplain, size_t L,
const uint8_t *header, size_t nheader,
const uint8_t *nonce, size_t nnonce,
uint8_t *cipher,
uint8_t *tag, size_t ntag)
{
uint8_t block[CF_MAXBLOCK];
assert(ntag >= 4 && ntag <= 16 && ntag % 2 == 0);
assert(L >= 2 && L <= 8);
assert(nnonce == prp->blocksz - L - 1);
cf_cbcmac_stream cm;
cf_cbcmac_stream_init(&cm, prp, prpctx);
/* Add first block. */
add_block0(&cm, block, prp->blocksz,
nonce, nnonce,
L, nplain, nheader, ntag);
/* Add AAD with length prefix, if present. */
if (nheader)
add_aad(&cm, block, header, nheader);
/* Add message. */
cf_cbcmac_stream_update(&cm, plain, nplain);
zero_pad(&cm);
/* Finish tag. */
cf_cbcmac_stream_nopad_final(&cm, block);
/* Start encryption. */
/* Construct A_0 */
uint8_t ctr_nonce[CF_MAXBLOCK];
build_ctr_nonce(ctr_nonce, L, nonce, nnonce);
cf_ctr ctr;
cf_ctr_init(&ctr, prp, prpctx, ctr_nonce);
cf_ctr_custom_counter(&ctr, prp->blocksz - L, L);
/* Encrypt tag first. */
cf_ctr_cipher(&ctr, block, block, prp->blocksz);
memcpy(tag, block, ntag);
/* Then encrypt message. */
cf_ctr_cipher(&ctr, plain, cipher, nplain);
}
int cf_ccm_decrypt(const cf_prp *prp, void *prpctx,
const uint8_t *cipher, size_t ncipher, size_t L,
const uint8_t *header, size_t nheader,
const uint8_t *nonce, size_t nnonce,
const uint8_t *tag, size_t ntag,
uint8_t *plain)
{
uint8_t block[CF_MAXBLOCK];
assert(ntag >= 4 && ntag <= 16 && ntag % 2 == 0);
assert(L >= 2 && L <= 8);
assert(nnonce == prp->blocksz - L - 1);
uint8_t ctr_nonce[CF_MAXBLOCK];
build_ctr_nonce(ctr_nonce, L, nonce, nnonce);
cf_ctr ctr;
cf_ctr_init(&ctr, prp, prpctx, ctr_nonce);
cf_ctr_custom_counter(&ctr, prp->blocksz - L, L);
/* Decrypt tag. */
uint8_t plain_tag[CF_MAXBLOCK];
cf_ctr_cipher(&ctr, tag, plain_tag, ntag);
cf_ctr_discard_block(&ctr);
/* Decrypt message. */
cf_ctr_cipher(&ctr, cipher, plain, ncipher);
cf_cbcmac_stream cm;
cf_cbcmac_stream_init(&cm, prp, prpctx);
/* Add first block. */
add_block0(&cm, block, prp->blocksz,
nonce, nnonce,
L, ncipher, nheader, ntag);
if (nheader)
add_aad(&cm, block, header, nheader);
cf_cbcmac_stream_update(&cm, plain, ncipher);
zero_pad(&cm);
/* Finish tag. */
cf_cbcmac_stream_nopad_final(&cm, block);
int err = 0;
if (!mem_eq(block, plain_tag, ntag))
{
err = 1;
mem_clean(plain, ncipher);
}
mem_clean(block, sizeof block);
mem_clean(plain_tag, sizeof plain_tag);
return err;
}