packages feed

crypton-2.0.0: cbits/aes/armv8.c

/*
 * AES using the ARMv8-A Cryptographic Extensions.
 *
 * The generic code in aes/generic.c is S-box table driven, which on AArch64
 * was the only thing available: crypton_aes.c only ever swapped in the AES-NI
 * implementation, and that is gated on x86.  This provides the AArch64
 * equivalent.
 *
 * The key schedule is laid out exactly as x86ni.c lays it out, because
 * crypton_aes.c leaves some operations -- OCB and CCM -- pointing at the
 * generic implementation even once the accelerated table is installed, and
 * those read the forward schedule.  So: the forward round keys k[0..nbr]
 * first, in the order crypton_aes_generic_init writes them, then
 * InvMixColumns(k[nbr-1]) down to InvMixColumns(k[1]) for decryption.  The two
 * ends of the decryption schedule, k[nbr] and k[0], are read back out of the
 * forward half rather than stored twice, which is what makes AES-256 fit in
 * the 16*14*2 bytes of aes_key.data.
 */

#include <stdint.h>
#include <string.h>
#include <arm_neon.h>
#if defined(__linux__)
#include <sys/auxv.h>
#include <asm/hwcap.h>
#endif
#include "crypton_aes.h"
#include "crypton_bitfn.h"

/*
 * The AES and PMULL instructions are extensions, so a translation unit
 * compiled for baseline ARMv8-A may not use them.  Mark the functions that do,
 * the way cbits/aes/x86ni.h marks their x86 counterparts, rather than raising
 * -march for every file in the library: the flag use_target_attributes picks
 * between the two, and with it set -- which is the default -- nothing else
 * enables the extensions, so without these the file does not compile at all on
 * a toolchain whose baseline lacks them.  Apple's does not lack them, which is
 * why only Linux noticed.
 *
 * "+crypto" rather than "crypto": GCC rejects the latter.
 */
#ifdef WITH_TARGET_ATTRIBUTES
#define TARGET_ARMV8_CRYPTO __attribute__((target("+crypto")))
#else
#define TARGET_ARMV8_CRYPTO
#endif

/* forward round keys: nbr + 1 of them, written by the generic key expansion */
#define FWD(key)  ((const uint8_t *) (key)->data)
/* InvMixColumns(k[nbr-1]) .. InvMixColumns(k[1]): nbr - 1 of them */
#define INV(key)  (((const uint8_t *) (key)->data) + 16 * ((key)->nbr + 1))

/*
 * The key schedule of FIPS 197 5.2, with the S-box the schedule needs coming
 * from the instructions rather than a table in memory.
 *
 * AArch64 has no counterpart to x86's AESKEYGENASSIST, but AESE is
 * AddRoundKey, SubBytes and ShiftRows together, so against a zero key it is
 * SubBytes and ShiftRows.  Give it a word in all four columns and ShiftRows
 * only moves identical bytes between them, which leaves every column holding
 * SubWord of that word.  RotWord is then a byte rotation, and on a register
 * whose four words are equal a rotation of the whole register by one byte
 * rotates each word.
 *
 * The words stay in vector registers throughout: a word moved to a general
 * register and back costs more than the instruction it is moved for.
 *
 * The exposure this removes is a small one -- sixteen lookups at addresses
 * derived from the key, once per key, against the per-block indexing the
 * instructions exist to remove -- but a key schedule is the one thing an
 * attacker most wants and it costs little to keep it out of the cache.
 */
TARGET_ARMV8_CRYPTO
static uint32x4_t sub_word(uint32x4_t w)
{
	return vreinterpretq_u32_u8(
	    vaeseq_u8(vreinterpretq_u8_u32(w), vdupq_n_u8(0)));
}

TARGET_ARMV8_CRYPTO
static uint32x4_t sub_rot_word(uint32x4_t w)
{
	const uint8x16_t s = vreinterpretq_u8_u32(sub_word(w));

	return vreinterpretq_u32_u8(vextq_u8(s, s, 1));
}

TARGET_ARMV8_CRYPTO
void crypton_aes_armv8_init(aes_key *key, uint8_t *origkey, uint8_t size)
{
	/* 2^0 .. 2^9 in GF(2^8), which is as far as any key size reaches */
	static const uint32_t rcon[10] = {
		0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
	};
	uint32_t *w = (uint32_t *) key->data;
	uint8_t *inv;
	int nk, nw, i;

	switch (size) {
	case 16: key->nbr = 10; break;
	case 24: key->nbr = 12; break;
	case 32: key->nbr = 14; break;
	default: return;
	}
	nk = size / 4;                  /* words of key */
	nw = 4 * (key->nbr + 1);        /* words of schedule */

	memcpy(w, origkey, size);
	for (i = nk; i < nw; i++) {
		uint32x4_t t = vld1q_dup_u32(w + i - 1);

		if (i % nk == 0)
			t = veorq_u32(sub_rot_word(t),
			              vdupq_n_u32(rcon[i / nk - 1]));
		else if (nk > 6 && i % nk == 4)
			t = sub_word(t);
		vst1q_lane_u32(w + i, veorq_u32(t, vld1q_dup_u32(w + i - nk)), 0);
	}

	/* and the inverted round keys the decryption modes read */
	inv = ((uint8_t *) key->data) + 16 * (key->nbr + 1);
	for (i = 1; i < key->nbr; i++) {
		uint8x16_t rk =
		    vld1q_u8(((const uint8_t *) key->data) + 16 * (key->nbr - i));
		vst1q_u8(inv + 16 * (i - 1), vaesimcq_u8(rk));
	}
}

/*
 * Whether the extensions are actually present.
 *
 * They are mandatory on Apple silicon, and on other AArch64 systems the
 * kernel reports them through the auxiliary vector.  A system without them
 * keeps the generic implementation.
 */
int crypton_aes_armv8_available(void)
{
#if defined(__APPLE__)
	return 1;
#elif defined(__linux__)
	return (getauxval(AT_HWCAP) & HWCAP_AES) != 0;
#else
	return 0;
#endif
}

/*
 * GHASH using PMULL, the AArch64 counterpart to PCLMULQDQ.
 *
 * This is a transliteration of gfmul_pclmuldq in x86ni.c rather than a fresh
 * formulation: that code is already pinned by the GCM known-answer tests, and
 * every operation it uses has a direct NEON equivalent, so translating it is
 * easier to check than reasoning about a new reduction from scratch.
 *
 *   _mm_shuffle_epi8 with a reversing mask  ->  vrev64q_u8 then vextq_u8
 *   _mm_clmulepi64_si128                    ->  vmull_p64 / vmull_high_p64
 *   _mm_slli_si128 / _mm_srli_si128         ->  vextq_u8 against zero
 *   _mm_slli_epi32 / _mm_srli_epi32         ->  vshlq_n_u32 / vshrq_n_u32
 */

/* reverse all 16 bytes */
TARGET_ARMV8_CRYPTO
static inline uint8x16_t bswap128(uint8x16_t v)
{
	return vextq_u8(vrev64q_u8(v), vrev64q_u8(v), 8);
}

/* shift the whole register left by n bytes, as _mm_slli_si128 does */
#define SHIFT_LEFT_BYTES(v, n)  vextq_u8(vdupq_n_u8(0), (v), 16 - (n))
/* and right, as _mm_srli_si128 does */
#define SHIFT_RIGHT_BYTES(v, n) vextq_u8((v), vdupq_n_u8(0), (n))

#define SHL32(v, n) vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(v), (n)))
#define SHR32(v, n) vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(v), (n)))

TARGET_ARMV8_CRYPTO
static inline uint8x16_t clmul_ll(uint8x16_t a, uint8x16_t b)
{
	return vreinterpretq_u8_p128(vmull_p64(
	    (poly64_t) vgetq_lane_u64(vreinterpretq_u64_u8(a), 0),
	    (poly64_t) vgetq_lane_u64(vreinterpretq_u64_u8(b), 0)));
}

TARGET_ARMV8_CRYPTO
static inline uint8x16_t clmul_lh(uint8x16_t a, uint8x16_t b)
{
	return vreinterpretq_u8_p128(vmull_p64(
	    (poly64_t) vgetq_lane_u64(vreinterpretq_u64_u8(a), 0),
	    (poly64_t) vgetq_lane_u64(vreinterpretq_u64_u8(b), 1)));
}

TARGET_ARMV8_CRYPTO
static inline uint8x16_t clmul_hl(uint8x16_t a, uint8x16_t b)
{
	return vreinterpretq_u8_p128(vmull_p64(
	    (poly64_t) vgetq_lane_u64(vreinterpretq_u64_u8(a), 1),
	    (poly64_t) vgetq_lane_u64(vreinterpretq_u64_u8(b), 0)));
}

TARGET_ARMV8_CRYPTO
static inline uint8x16_t clmul_hh(uint8x16_t a, uint8x16_t b)
{
	return vreinterpretq_u8_p128(vmull_high_p64(
	    vreinterpretq_p64_u8(a), vreinterpretq_p64_u8(b)));
}

/*
 * The 256-bit carry-less product of a (normal byte order) and b (already
 * reversed, as it sits in the table), before the reflection fixup and the
 * reduction.  Split out from the reduction because both of those are linear
 * over XOR: several products can be added together and fixed up just once,
 * which is what gf_mul4 below does.
 */
TARGET_ARMV8_CRYPTO
static inline void clmul_pmull(uint8x16_t a, uint8x16_t b,
                               uint8x16_t *lo, uint8x16_t *hi)
{
	uint8x16_t t3, t4, t5, t6;

	a = bswap128(a);

	t3 = clmul_ll(a, b);
	t4 = clmul_lh(a, b);
	t5 = clmul_hl(a, b);
	t6 = clmul_hh(a, b);

	t4 = veorq_u8(t4, t5);
	t5 = SHIFT_LEFT_BYTES(t4, 8);
	t4 = SHIFT_RIGHT_BYTES(t4, 8);

	*lo = veorq_u8(t3, t5);
	*hi = veorq_u8(t6, t4);
}

/* Shift the 256-bit product left by one to undo GCM's bit reflection, then
 * reduce modulo the GCM polynomial.  This is the expensive half. */
TARGET_ARMV8_CRYPTO
static inline uint8x16_t gfred_pmull(uint8x16_t t3, uint8x16_t t6)
{
	uint8x16_t t2, t4, t5, t7, t8, t9;

	t7 = SHR32(t3, 31);
	t8 = SHR32(t6, 31);
	t3 = SHL32(t3, 1);
	t6 = SHL32(t6, 1);

	t9 = SHIFT_RIGHT_BYTES(t7, 12);
	t8 = SHIFT_LEFT_BYTES(t8, 4);
	t7 = SHIFT_LEFT_BYTES(t7, 4);
	t3 = vorrq_u8(t3, t7);
	t6 = vorrq_u8(t6, t8);
	t6 = vorrq_u8(t6, t9);

	t7 = SHL32(t3, 31);
	t8 = SHL32(t3, 30);
	t9 = SHL32(t3, 25);

	t7 = veorq_u8(t7, t8);
	t7 = veorq_u8(t7, t9);
	t8 = SHIFT_RIGHT_BYTES(t7, 4);
	t7 = SHIFT_LEFT_BYTES(t7, 12);
	t3 = veorq_u8(t3, t7);

	t2 = SHR32(t3, 1);
	t4 = SHR32(t3, 2);
	t5 = SHR32(t3, 7);
	t2 = veorq_u8(t2, t4);
	t2 = veorq_u8(t2, t5);
	t2 = veorq_u8(t2, t8);
	t3 = veorq_u8(t3, t2);
	t6 = veorq_u8(t6, t3);

	return bswap128(t6);
}

TARGET_ARMV8_CRYPTO
static uint8x16_t gfmul_pmull(uint8x16_t a, const uint8_t *htable)
{
	uint8x16_t lo, hi;

	clmul_pmull(a, vld1q_u8(htable), &lo, &hi);
	return gfred_pmull(lo, hi);
}

/*
 * With PMULL there is no 4-bit table to fill: H goes in at index 0, byte
 * reversed, so that gfmul_pmull does not have to swap it every time.  This
 * mirrors crypton_aesni_hinit_pclmul.
 *
 * Indices 1..7 get H^2 .. H^8, which is what lets a group of blocks fold
 * into one reduction: gf_mul4 uses the first four, the GCM loop all eight.
 * The table has sixteen slots, so they are free.
 */
TARGET_ARMV8_CRYPTO
void crypton_aes_armv8_hinit_pmull(block128 *htable, const block128 *h)
{
	uint8x16_t p;
	int i;

	htable[0].q[0] = bitfn_swap64(h->q[1]);
	htable[0].q[1] = bitfn_swap64(h->q[0]);

	p = vld1q_u8((const uint8_t *) h);
	for (i = 1; i < 8; i++) {
		p = gfmul_pmull(p, (const uint8_t *) &htable[0]);
		vst1q_u8((uint8_t *) &htable[i], bswap128(p));
	}
}

TARGET_ARMV8_CRYPTO
void crypton_aes_armv8_gf_mul_pmull(block128 *a, const block128 *htable)
{
	vst1q_u8((uint8_t *) a,
	         gfmul_pmull(vld1q_u8((const uint8_t *) a), (const uint8_t *) htable));
}

/*
 * Four GHASH steps -- ((((a^b0)H ^ b1)H ^ b2)H ^ b3)H -- with a single
 * reduction.  Expanded that is (a^b0)H^4 ^ b1*H^3 ^ b2*H^2 ^ b3*H, so the
 * four products can be summed first and reduced once, which is where the
 * time goes.  Aggregated reduction, from the Intel GCM paper.
 */
TARGET_ARMV8_CRYPTO
void crypton_aes_armv8_gf_mul4_pmull(block128 *a, const block128 *blocks,
                                     const block128 *htable)
{
	uint8x16_t lo, hi, l, h;
	int i;

	clmul_pmull(veorq_u8(vld1q_u8((const uint8_t *) a),
	                     vld1q_u8((const uint8_t *) &blocks[0])),
	            vld1q_u8((const uint8_t *) &htable[3]), &lo, &hi);

	for (i = 1; i < 4; i++) {
		clmul_pmull(vld1q_u8((const uint8_t *) &blocks[i]),
		            vld1q_u8((const uint8_t *) &htable[3 - i]), &l, &h);
		lo = veorq_u8(lo, l);
		hi = veorq_u8(hi, h);
	}

	vst1q_u8((uint8_t *) a, gfred_pmull(lo, hi));
}

int crypton_aes_armv8_pmull_available(void)
{
#if defined(__APPLE__)
	return 1;
#elif defined(__linux__)
	return (getauxval(AT_HWCAP) & HWCAP_PMULL) != 0;
#else
	return 0;
#endif
}

/*
 * The XTS tweak advances by doubling in GF(2^128), which
 * crypton_aes_generic_gf_mulx does through memory.  Here it stays in a
 * register: shift both halves left by one, carry the low half's top bit into
 * the high half, and fold the bit that leaves the top back in as 0x87.  The
 * block is little-endian, so lane 0 is the low half.
 */
TARGET_ARMV8_CRYPTO
static inline uint8x16_t gfmulx_neon(uint8x16_t v)
{
	const uint64x2_t x = vreinterpretq_u64_u8(v);
	const uint64x2_t zero = vdupq_n_u64(0);
	const uint64x2_t carry = vshrq_n_u64(x, 63);
	/* the low half's carry becomes the high half's bit 0 */
	const uint64x2_t into_hi = vextq_u64(zero, carry, 1);
	/* and the high half's becomes all ones, or nothing, in the low half */
	const uint64x2_t out = vsubq_u64(zero, vextq_u64(carry, zero, 1));
	const uint64x2_t poly = vsetq_lane_u64(0x87, zero, 0);

	return vreinterpretq_u8_u64(veorq_u64(
	    vorrq_u64(vshlq_n_u64(x, 1), into_hi), vandq_u64(out, poly)));
}

/*
 * The modes, generated once per key size.  See armv8_impl.c for why the
 * round count has to be a compile-time constant.
 */
#define SIZED(m) m##128
#define NBR 10
#include <aes/armv8_impl.c>
#undef SIZED
#undef NBR

#define SIZED(m) m##192
#define NBR 12
#include <aes/armv8_impl.c>
#undef SIZED
#undef NBR

#define SIZED(m) m##256
#define NBR 14
#include <aes/armv8_impl.c>
#undef SIZED
#undef NBR