<commit_before>/*
 * securid.c - SecurID token handling
 *
 * Copyright 2012 <NAME> <<EMAIL>>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "config.h"

#include <ctype.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <tomcrypt.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "securid.h"
#include "sdtid.h"

struct v3_token {
	uint8_t			version;
	uint8_t			password_locked;
	uint8_t			devid_locked;
	uint8_t			nonce_devid_hash[SHA256_HASH_SIZE];
	uint8_t			nonce_devid_pass_hash[SHA256_HASH_SIZE];
	uint8_t			nonce[V3_NONCE_BYTES];
	uint8_t			enc_payload[0xb0];
	uint8_t			mac[SHA256_HASH_SIZE];
};

#define V3_ADDPIN_OFF		0x1f
#define V3_ADDPIN_ON		0x21

struct v3_payload {
	char			serial[16];
	uint8_t			dec_seed[AES_KEY_SIZE];
	uint8_t			unk0[2];
	uint8_t			mode;
	uint8_t			digits;
	uint8_t			addpin;
	uint8_t			interval;
	uint8_t			res0[2];
	uint8_t			birth_date[5];
	uint8_t			res1[3];
	uint8_t			exp_date[5];
	uint8_t			res2[0x6b];
	uint8_t			padding[0x10];
};

/********************************************************************
 * Utility and crypto functions
 ********************************************************************/

static uint8_t hex2nibble(char in)
{
	uint8_t ret = in - '0';
	return (ret <= 9) ? ret : (10 + toupper(in) - 'A');
}

static uint8_t hex2byte(const char *in)
{
	return (hex2nibble(in[0]) << 4) | hex2nibble(in[1]);
}

void aes128_ecb_encrypt(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
	symmetric_key skey;
	uint8_t tmp[AES_BLOCK_SIZE];

	/* these shouldn't allocate memory or fail */
	if (rijndael_setup(key, AES_KEY_SIZE, 0, &skey) != CRYPT_OK ||
	    rijndael_ecb_encrypt(in, tmp, &skey) != CRYPT_OK)
		abort();
	rijndael_done(&skey);

	/* in case "in" and "out" point to the same buffer */
	memcpy(out, tmp, AES_BLOCK_SIZE);
}

void aes128_ecb_decrypt(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
	symmetric_key skey;
	uint8_t tmp[AES_BLOCK_SIZE];

	if (rijndael_setup(key, AES_KEY_SIZE, 0, &skey) != CRYPT_OK ||
	    rijndael_ecb_decrypt(in, tmp, &skey) != CRYPT_OK)
		abort();
	rijndael_done(&skey);

	memcpy(out, tmp, AES_BLOCK_SIZE);
}

void aes256_cbc_decrypt(const uint8_t *key, const uint8_t *in, int in_len,
			const uint8_t *iv, uint8_t *out)
{
	symmetric_key skey;
	int i, j;
	uint8_t local_iv[AES_BLOCK_SIZE];

	rijndael_setup(key, AES256_KEY_SIZE, 0, &skey);

	memcpy(local_iv, iv, AES_BLOCK_SIZE);
	for (i = 0; i < in_len; i += AES_BLOCK_SIZE) {
		rijndael_ecb_decrypt(in, out, &skey);
		for (j = 0; j < AES_BLOCK_SIZE; j++)
			out[j] ^= local_iv[j];
		memcpy(local_iv, in, AES_BLOCK_SIZE);
		in += AES_BLOCK_SIZE;
		out += AES_BLOCK_SIZE;
	}
}

int securid_rand(void *out, int len, int paranoid)
{
	if (paranoid) {
		/*
		 * Use /dev/random for long lived key material but not for
		 * test purposes.  This can block for a long time if entropy
		 * is limited.
		 */
		int fd;
		char *p = out;

		fd = open("/dev/random", O_RDONLY);
		if (fd < 0)
			return ERR_GENERAL;

		while (len) {
			ssize_t ret = read(fd, p, len);
			if (ret < 0) {
				close(fd);
				return ERR_GENERAL;
			}
			p += ret;
			len -= ret;
		}
		close(fd);
	} else {
		if (rng_get_bytes(out, len, NULL) != len)
			return ERR_GENERAL;
	}
	return ERR_NONE;
}

static void encrypt_then_xor(const uint8_t *key, uint8_t *work, uint8_t *enc)
{
	int i;

	aes128_ecb_encrypt(key, work, enc);
	for (i = 0; i < AES_BLOCK_SIZE; i++)
		work[i] ^= enc[i];
}

static void securid_mac(const uint8_t *in, int in_len, uint8_t *out)
{
	int i, odd = 0;
	const int incr = AES_KEY_SIZE;
	uint8_t work[incr], enc[incr], pad[incr], zero[incr], lastblk[incr], *p;

	memset(zero, 0, incr);
	memset(pad, 0, incr);
	memset(lastblk, 0, incr);
	memset(work, 0xff, incr);

	/* padding */
	p = &pad[incr - 1];
	for (i = in_len * 8; i > 0; i >>= 8)
		*(p--) = (uint8_t)i;

	/* handle the bulk of the input data here */
	for (; in_len > incr; in_len -= incr, in += incr, odd = !odd)
		encrypt_then_xor(in, work, enc);

	/* final 0-16 bytes of input data */
	memcpy(lastblk, in, in_len);
	encrypt_then_xor(lastblk, work, enc);

	/* hash an extra block of zeroes, for certain input lengths */
	if (odd)
		encrypt_then_xor(zero, work, enc);

	/* always hash the padding */
	encrypt_then_xor(pad, work, enc);

	/* run hash over current hash value, then return */
	memcpy(out, work, incr);
	encrypt_then_xor(work, out, enc);
}

static uint16_t securid_shortmac(const uint8_t *in, int in_len)
{
	uint8_t hash[AES_BLOCK_SIZE];

	securid_mac(in, in_len, hash);
	return (hash[0] << 7) | (hash[1] >> 1);
}

static void sha256_hash(const uint8_t *in, int in_len, uint8_t *out)
{
	hash_state md;
	sha256_init(&md);
	sha256_process(&md, in, in_len);
	sha256_done(&md, out);
}

static void sha256_hmac(const uint8_t *key, int key_len,
			const uint8_t *msg, int msg_len, uint8_t *out)
{
	hash_state md;
	uint8_t tmp_key[SHA256_HASH_SIZE], o_key_pad[SHA256_BLOCK_SIZE],
		i_key_pad[SHA256_BLOCK_SIZE], inner_hash[SHA256_BLOCK_SIZE];
	int i;

	if (key_len > SHA256_BLOCK_SIZE) {
		sha256_hash(key, key_len, tmp_key);
		key = tmp_key;
		key_len = SHA256_HASH_SIZE;
	}

	memset(o_key_pad, 0x5c, SHA256_BLOCK_SIZE);
	memset(i_key_pad, 0x36, SHA256_BLOCK_SIZE);
	for (i = 0; i < key_len; i++) {
		o_key_pad[i] ^= key[i];
		i_key_pad[i] ^= key[i];
	}

	sha256_init(&md);
	sha256_process(&md, i_key_pad, SHA256_BLOCK_SIZE);
	sha256_process(&md, msg, msg_len);
	sha256_done(&md, inner_hash);

	sha256_init(&md);
	sha256_process(&md, o_key_pad, SHA256_BLOCK_SIZE);
	sha256_process(&md, inner_hash, SHA256_HASH_SIZE);
	sha256_done(&md, out);
}

static void sha256_pbkdf2(const uint8_t *pass, int pass_len,
			  const uint8_t *salt, int salt_len,
			  int n_rounds, uint8_t *key_out)
{
	uint8_t *ext_salt;
	uint8_t hash[SHA256_HASH_SIZE];
	int i, round;

	ext_salt = alloca(salt_len + 4);
	memcpy(ext_salt, salt, salt_len);

	/* always 0x00000001, as the output size is fixed at SHA256_HASH_SIZE */
	ext_salt[salt_len + 0] = 0;
	ext_salt[salt_len + 1] = 0;
	ext_salt[salt_len + 2] = 0;
	ext_salt[salt_len + 3] = 1;

	sha256_hmac(pass, pass_len, ext_salt, salt_len + 4, key_out);
	memcpy(hash, key_out, SHA256_HASH_SIZE);

	for (round = 2; round <= n_rounds; round++) {
		sha256_hmac(pass, pass_len, hash, SHA256_HASH_SIZE, hash);

		for (i = 0; i < SHA256_HASH_SIZE; i++)
			key_out[i] ^= hash[i];
	}
}

/********************************************************************
 * V1/V2 token handling
 ********************************************************************/

static void numinput_to_bits(const char *in, uint8_t *out, unsigned int n_bits)
{
	int bitpos = 13;

	memset(out, 0, (n_bits + 7) / 8);
	for (; n_bits; n_bits -= TOKEN_BITS_PER_CHAR, in++) {
		uint16_t decoded = (*in - '0') & 0x07;
		decoded <<= bitpos;
		out[0] |= decoded >> 8;
		out[1] |= decoded & 0xff;

		bitpos -= TOKEN_BITS_PER_CHAR;
		if (bitpos < 0) {
			bitpos += 8;
			out++;
		}
	}
}

static void bits_to_numoutput(const uint8_t *in, char *out, unsigned int n_bits)
{
	int bitpos = 13;

	for (; n_bits; n_bits -= TOKEN_BITS_PER_CHAR, out++) {
		uint16_t binary = (in[0] << 8) | in[1];
		*out = ((binary >> bitpos) & 0x07) + '0';

		bitpos -= TOKEN_BITS_PER_CHAR;
		if (bitpos < 0) {
			bitpos += 8;
			in++;
		}
	}
	*out = 0;
}

static uint32_t get_bits(const uint8_t *in, unsigned int start, int n_bits)
{
	uint32_t out = 0;

	in += start / 8;
	start %= 8;

	for (; n_bits > 0; n_bits--) {
		out <<= 1;
		if ((*in << start) & 0x80)
			out |= 0x01;
		start++;
		if (start == 8) {
			start = 0;
			in++;
		}
	}
	return out;
}

static void set_bits(uint8_t *out, unsigned int start, int n_bits, uint32_t val)
{
	out += start / 8;
	start %= 8;
	val <<= (32 - n_bits);

	for (; n_bits > 0; n_bits--) {
		if (val & BIT(31))
			*out |= BIT(7 - start);
		else
			*out &= ~BIT(7 - start);
		val <<= 1;
		start++;
		if (start == 8) {
			start = 0;
			out++;
		}
	}
}

static int v2_decode_token(const char *in, struct securid_token *t)
{
	uint8_t d[MAX_TOKEN_BITS / 8 + 2];
	int len = strlen(in);
	uint16_t token_mac, computed_mac;

	if (len < MIN_TOKEN_CHARS || len > MAX_TOKEN_CHARS)
		return ERR_BAD_LEN;

	/* the last 5 digits provide a checksum for the rest of the string */
	numinput_to_bits(&in[len - CHECKSUM_CHARS], d, 15);
	token_mac = get_bits(d, 0, 15);
	computed_mac = securid_shortmac(in, len - CHECKSUM_CHARS);

	if (token_mac != computed_mac)
		return ERR_CHECKSUM_FAILED;

	t->version = in[0] - '0';
	memcpy(&t->serial, &in[VER_CHARS], SERIAL_CHARS);
	t->serial[SERIAL_CHARS] = 0;

	numinput_to_bits(&in[BINENC_OFS], d, BINENC_BITS);
	memcpy(t->enc_seed, d, AES_KEY_SIZE);
	t->has_enc_seed = 1;

	t->flags = get_bits(d, 128, 16);
	t->exp_date = get_bits(d, 144, 14);
	t->dec_seed_hash = get_bits(d, 159, 15);
	t->device_id_hash = get_bits(d, 174, 15);

	return ERR_NONE;
}

static int generate_key_hash(uint8_t *key_hash, const char *pass,
	const char *devid, uint16_t *device_id_hash, struct securid_token *t)
{
	uint8_t key[MAX_PASS + DEVID_CHARS + MAGIC_LEN + 1], *devid_buf;
	int pos = 0, devid_len = t->is_smartphone ? 40 : 32;
	const uint8_t magic[] = { 0xd8, 0xf5, 0x32, 0x53, 0x82, 0x89, 0x00 };

	memset(key, 0, sizeof(key));

	if (pass) {
		pos = strlen(pass);
		if (pos > MAX_PASS)
			return ERR_BAD_PASSWORD;
		memcpy(key, pass, pos);
	}

	devid_buf = &key[pos];
	if (devid) {
		int len = 0;

		/*
		 * For iPhone/Android ctf strings, the device ID takes up
		 * 40 bytes and consists of hex digits + zero padding.
		 *
		 * For other ctf strings (e.g. --blocks), the device ID takes
		 * up 32 bytes and consists of decimal digits + zero padding.
		 *
		 * If this seed isn't locked to a device, we'll just hash
		 * 40 (or 32) zero bytes, below.
		 */
		for (; *devid; devid++) {
			if (++len > devid_len)
				break;
			if ((t->version == 1 && isdigit(*devid)) ||
			    (t->version >= 2 && !isxdigit(*devid)))
				continue;
			key[pos++] = toupper(*devid);
		}
	}
	if (device_id_hash)
		*device_id_hash = securid_shortmac(devid_buf, devid_len);

	memcpy(&key[pos], magic, MAGIC_LEN);
	securid_mac(key, pos + MAGIC_LEN, key_hash);

	return ERR_NONE;
}

static int v2_decrypt_seed(struct securid_token *t, const char *pass,
			   const char *devid)
{
	uint8_t key_hash[AES_BLOCK_SIZE], dec_seed_hash[AES_BLOCK_SIZE];
	uint16_t computed_mac;
	uint16_t device_id_hash;
	int rc;

	if (t->flags & FL_PASSPROT && !pass)
		return ERR_MISSING_PASSWORD;
	if (t->flags & FL_SNPROT && !devid)
		return ERR_MISSING_PASSWORD;

	rc = generate_key_hash(key_hash,
			       t->flags & FL_PASSPROT ? pass : NULL,
			       t->flags & FL_SNPROT ? devid : NULL,
			       &device_id_hash, t);
	if (rc)
		return rc;

	if (t->flags & FL_SNPROT && device_id_hash != t->device_id_hash)
		return ERR_BAD_DEVID;

	aes128_ecb_decrypt(key_hash, t->enc_seed, t->dec_seed);
	securid_mac(t->dec_seed, AES_KEY_SIZE, dec_seed_hash);
	computed_mac = (dec_seed_hash[0] << 7) | (dec_seed_hash[1] >> 1);

	if (computed_mac != t->dec_seed_hash)
		return ERR_DECRYPT_FAILED;
	t->has_dec_seed = 1;

	return ERR_NONE;
}

static void key_from_time(const uint8_t *bcd_time, int bcd_time_bytes,
	const uint8_t *serial, uint8_t *key)
{
	int i;

	memset(key, 0xaa, 8);
	memcpy(key, bcd_time, bcd_time_bytes);
	memset(key + 12, 0xbb, 4);

	/* write BCD-encoded partial serial number */
	key += 8;
	for (i = 4; i < 12; i += 2)
		*(key++) = ((serial[i] - '0') << 4) |
			    (serial[i + 1] - '0');
}

static void bcd_write(uint8_t *out, int val, unsigned int bytes)
{
	out += bytes - 1;
	for (; bytes; bytes--) {
		*out = val % 10;
		val /= 10;
		*(out--) |= (val % 10) << 4;
		val /= 10;
	}
}

static int v2_encode_token(const struct securid_token *t, const char *pass,
			   const char *devid, char *out)
{
	struct securid_token newt = *t;
	uint8_t d[MAX_TOKEN_BITS / 8 + 2];
	uint8_t key_hash[AES_BLOCK_SIZE];
	int rc;

	/* empty password means "<PASSWORD>" */
	if (pass && !strlen(pass))
		pass = NULL;
	if (devid && !strlen(devid))
		devid = NULL;

	rc = generate_key_hash(key_hash, pass, devid, &newt.device_id_hash,
			       &newt);
	if (rc)
		return rc;

	if (pass)
		newt.flags |= FL_PASSPROT;
	else
		newt.flags &= ~FL_PASSPROT;

	if (devid)
		newt.flags |= FL_SNPROT;
	else
		newt.flags &= ~FL_SNPROT;

	memset(d, 0, sizeof(d));
	aes128_ecb_encrypt(key_hash, newt.dec_seed, newt.enc_seed);
	memcpy(d, newt.enc_seed, AES_KEY_SIZE);

	set_bits(d, 128, 16, newt.flags);
	set_bits(d, 144, 14, newt.exp_date);
	set_bits(d, 159, 15, securid_shortmac(newt.dec_seed, AES_KEY_SIZE));
	set_bits(d, 174, 15, newt.device_id_hash);

	sprintf(out, "%d%s", newt.version, newt.serial);
	bits_to_numoutput(d, &out[BINENC_OFS], BINENC_BITS);

	set_bits(d, 0, 15, securid_shortmac(out, CHECKSUM_OFS));
	bits_to_numoutput(d, &out[CHECKSUM_OFS], CHECKSUM_BITS);

	return ERR_NONE;
}

/********************************************************************
 * V3 token handling
 ********************************************************************/

static void v3_derive_key(const char *pass, const char *devid, const uint8_t *salt,
			  int key_id, uint8_t *out)
{
	uint8_t *buf0, *buf1;
	int pass_len = pass ? strlen(pass) : 0;
	int buf_len = V3_DEVID_CHARS + 16 + V3_NONCE_BYTES + pass_len;
	unsigned int i;
	const uint8_t key0[] = { <KEY>
				 0xda, 0x09, 0xab, 0xfc, 0x32, 0x49, 0x63, 0x4c };
	const uint8_t key1[] = { <KEY>
				 0x81, 0x60, 0xde, 0x44, 0x4e, 0x05, 0xc0, 0xdd };

	buf0 = alloca(buf_len);
	buf1 = alloca(buf_len / 2);

	memset(buf0, 0, buf_len);

	if (pass)
		strncpy(buf0, pass, pass_len);
	if (devid)
		strncpy(&buf0[pass_len], devid, V3_DEVID_CHARS);
	memcpy(&buf0[pass_len + V3_DEVID_CHARS], key_id ? key1 : key0, 16);
	memcpy(&buf0[pass_len + V3_DEVID_CHARS + 16], salt, V3_NONCE_BYTES);

	/* yup, the PBKDF2 password is really "every 2nd byte of the input" */
	for (i = 1; i < buf_len; i += 2)
		buf1[i >> 1] = buf0[i];

	sha256_pbkdf2(buf1, buf_len >> 1, salt, V3_NONCE_BYTES, 1000, out);
}

static int v3_decode_token(const char *in, struct securid_token *t)
{
	char decoded[BASE64_MIN_INPUT + 1];
	int i, j;
	unsigned long actual;

	/* remove URL-encoding */
	for (i = 0, j = 0; in[i]; ) {
		if (j == BASE64_MIN_INPUT)
			return ERR_BAD_LEN;
		if (in[i] == '%') {
			if (!isxdigit(in[i + 1]) || !isxdigit(in[i + 2]))
				return ERR_BAD_LEN;
			decoded[j++] = hex2byte(&in[i + 1]);
			i += 3;
		} else {
			decoded[j++] = in[i++];
		}
	}
	decoded[j] = 0;

	t->v3 = malloc(sizeof(struct v3_token));
	if (!t->v3)
		return ERR_NO_MEMORY;

	actual = sizeof(struct v3_token);
	if (base64_decode(decoded, strlen(decoded),
			  (void *)t->v3, &actual) != CRYPT_OK ||
	    actual != sizeof(struct v3_token) ||
	    t->v3->version != 0x03) {
		free(t->v3);
		t->v3 = NULL;
		return ERR_GENERAL;
	}

	t->version = 3;
	t->has_enc_seed = 1;

	/* more flags will get populated later when we decrypt the payload */
	t->flags = t->v3->password_locked ? FL_PASSPROT : 0;
	t->flags |= t->v3->devid_locked ? FL_SNPROT : 0;

	return ERR_NONE;
}

static uint16_t v3_parse_date(uint8_t *in)
{
	uint64_t longdate;

	longdate = ((uint64_t)in[0] << 32) |
		   ((uint64_t)in[1] << 24) |
		   ((uint64_t)in[2] << 16) |
		   ((uint64_t)in[3] <<  8) |
		   ((uint64_t)in[4] <<  0);
	longdate /= SECURID_V3_DAY;
	return longdate - (SECURID_EPOCH / (24*60*60));
}

static void v3_compute_hash(const char *pass, const char *devid,
			    const uint8_t *salt, uint8_t *hash)
{
	uint8_t hash_buf[V3_NONCE_BYTES + V3_DEVID_CHARS + MAX_PASS];
	int pass_len = 0;

	memset(hash_buf, 0, sizeof(hash_buf));
	memcpy(&hash_buf[0], salt, V3_NONCE_BYTES);

	if (devid)
		strncpy(&hash_buf[V3_NONCE_BYTES], devid, V3_DEVID_CHARS);

	if (pass) {
		pass_len = strlen(pass);
		strncpy(&hash_buf[V3_NONCE_BYTES + V3_DEVID_CHARS], pass, MAX_PASS);
	}
	sha256_hash(hash_buf, V3_NONCE_BYTES + V3_DEVID_CHARS + pass_len, hash);
}

static void v3_compute_hmac(struct v3_token *v3, const char *pass,
			    const char *devid, uint8_t *out)
{
	uint8_t hash[SHA256_HASH_SIZE];

	v3_derive_key(pass, devid, v3->nonce, 0, hash);
	sha256_hmac(hash, SHA256_HASH_SIZE,
		    (void *)v3, sizeof(*v3) - SHA256_HASH_SIZE, out);
}

static int v3_decrypt_seed(struct securid_token *t,
			   const char *pass, const char *devid)
{
	struct v3_payload payload;
	uint8_t hash[SHA256_HASH_SIZE];

	if (pass && strlen(pass) > MAX_PASS)
		return ERR_BAD_PASSWORD;

	v3_compute_hash(NULL, devid, t->v3->nonce, hash);
	if (memcmp(hash, t->v3->nonce_devid_hash, SHA256_HASH_SIZE) != 0)
		return ERR_BAD_DEVID;

	v3_compute_hash(pass, devid, t->v3->nonce, hash);
	if (memcmp(hash, t->v3->nonce_devid_pass_hash, SHA256_HASH_SIZE) != 0)
		return ERR_DECRYPT_FAILED;

	v3_compute_hmac(t->v3, pass, devid, hash);
	if (memcmp(hash, t->v3->mac, SHA256_HASH_SIZE) != 0)
		return ERR_CHECKSUM_FAILED;

	v3_derive_key(pass, devid, t->v3->nonce, 1, hash);
	aes256_cbc_decrypt(hash,
			   t->v3->enc_payload, sizeof(struct v3_payload),
			   t->v3->nonce, (void *)&payload);

	strncpy(t->serial, payload.serial, SERIAL_CHARS);
	t->serial[SERIAL_CHARS] = 0;

	memcpy(t->dec_seed, &payload.dec_seed, AES_KEY_SIZE);
	t->has_dec_seed = 1;

	t->flags |= FL_TIMESEEDS | FL_128BIT;
	t->flags |= payload.mode ? FL_FEAT4 : 0;
	t->flags |= ((payload.digits-1) << FLD_DIGIT_SHIFT) & FLD_DIGIT_MASK;
	t->flags |= (payload.addpin != V3_ADDPIN_OFF) ?
		    (0x2 << FLD_PINMODE_SHIFT) : 0;
	t->flags |= payload.interval == 60 ? (1 << FLD_NUMSECONDS_SHIFT) : 0;

	t->exp_date = v3_parse_date(payload.exp_date);

	return ERR_NONE;
}

/********************************************************************
 * Public functions
 ********************************************************************/

int securid_decode_token(const char *in, struct securid_token *t)
{
	/*
	 * V1/V2 tokens start with the ASCII version digit
	 * V3 tokens always start with a base64-encoded 0x03 byte, which
	 *   is guaranteed to encode to 'A'
	 */
	if (in[0] == '1' || in[0] == '2')
		return v2_decode_token(in, t);
	else if (strlen(in) >= BASE64_MIN_INPUT && (in[0] == 'A'))
		return v3_decode_token(in, t);
	else
		return ERR_TOKEN_VERSION;
}

int securid_decrypt_seed(struct securid_token *t, const char *pass,
			 const char *devid)
{
	if (t->sdtid)
		return sdtid_decrypt(t, pass);
	else if (t->v3)
		return v3_decrypt_seed(t, pass, devid);
	else
		return v2_decrypt_seed(t, pass, devid);
}

void securid_compute_tokencode(struct securid_token *t, time_t now,
			       char *code_out)
{
	uint8_t bcd_time[8];
	uint8_t key0[AES_KEY_SIZE], key1[AES_KEY_SIZE];
	int i, j;
	uint32_t tokencode;
	struct tm gmt;
	int pin_len = strlen(t->pin);

	gmtime_r(&now, &gmt);
	bcd_write(&bcd_time[0], gmt.tm_year + 1900, 2);
	bcd_write(&bcd_time[2], gmt.tm_mon + 1, 1);
	bcd_write(&bcd_time[3], gmt.tm_mday, 1);
	bcd_write(&bcd_time[4], gmt.tm_hour, 1);
	bcd_write(&bcd_time[5], gmt.tm_min & ~0x03, 1);
	bcd_time[6] = bcd_time[7] = 0;

	key_from_time(bcd_time, 2, t->serial, key0);
	aes128_ecb_encrypt(t->dec_seed, key0, key0);
	key_from_time(bcd_time, 3, t->serial, key1);
	aes128_ecb_encrypt(key0, key1, key1);
	key_from_time(bcd_time, 4, t->serial, key0);
	aes128_ecb_encrypt(key1, key0, key0);
	key_from_time(bcd_time, 5, t->serial, key1);
	aes128_ecb_encrypt(key0, key1, key1);
	key_from_time(bcd_time, 8, t->serial, key0);
	aes128_ecb_encrypt(key1, key0, key0);

	/* key0 now contains 4 consecutive token codes */
	i = (gmt.tm_min & 0x03) << 2;
	tokencode = (key0[i + 0] << 24) | (key0[i + 1] << 16) |
		    (key0[i + 2] << 8)  | (key0[i + 3] << 0);

	/* populate code_out backwards, adding PIN digits if available */
	j = ((t->flags & FLD_DIGIT_MASK) >> FLD_DIGIT_SHIFT) + 1;
	code_out[j--] = 0;
	for (i = 0; j >= 0; j--, i++) {
		uint8_t c = tokencode % 10;
		tokencode /= 10;

		if (i < pin_len)
			c += t->pin[pin_len - i - 1] - '0';
		code_out[j] = c % 10 + '0';
	}
}

int securid_encode_token(const struct securid_token *t, const char *pass,
	const char *devid, char *out)
{
	return v2_encode_token(t, pass, devid, out);
}

int securid_random_token(struct securid_token *t)
{
	time_t now = time(NULL);
	uint8_t randbytes[16], key_hash[AES_BLOCK_SIZE];
	int i;

	memset(t, 0, sizeof(*t));

	if (securid_rand(t->dec_seed, AES_KEY_SIZE, 0) ||
	    securid_rand(randbytes, sizeof(randbytes), 0))
		return ERR_GENERAL;

	t->dec_seed_hash = securid_shortmac(t->dec_seed, AES_KEY_SIZE);

	generate_key_hash(key_hash, NULL, NULL, &t->device_id_hash, t);
	aes128_ecb_encrypt(key_hash, t->dec_seed, t->enc_seed);
	t->has_enc_seed = 1;

	t->version = 2;
	t->flags = FL_TIMESEEDS | FLD_DIGIT_MASK | FLD_PINMODE_MASK |
		   (1 << FLD_NUMSECONDS_SHIFT) | FL_128BIT;<commit_msg>securid: Move common v2/v3 logic into securid_encode_token()<commit_after>/*
 * securid.c - SecurID token handling
 *
 * Copyright 2012 <NAME> <<EMAIL>>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "config.h"

#include <ctype.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <tomcrypt.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "securid.h"
#include "sdtid.h"

struct v3_token {
	uint8_t			version;
	uint8_t			password_locked;
	uint8_t			devid_locked;
	uint8_t			nonce_devid_hash[SHA256_HASH_SIZE];
	uint8_t			nonce_devid_pass_hash[SHA256_HASH_SIZE];
	uint8_t			nonce[V3_NONCE_BYTES];
	uint8_t			enc_payload[0xb0];
	uint8_t			mac[SHA256_HASH_SIZE];
};

#define V3_ADDPIN_OFF		0x1f
#define V3_ADDPIN_ON		0x21

struct v3_payload {
	char			serial[16];
	uint8_t			dec_seed[AES_KEY_SIZE];
	uint8_t			unk0[2];
	uint8_t			mode;
	uint8_t			digits;
	uint8_t			addpin;
	uint8_t			interval;
	uint8_t			res0[2];
	uint8_t			birth_date[5];
	uint8_t			res1[3];
	uint8_t			exp_date[5];
	uint8_t			res2[0x6b];
	uint8_t			padding[0x10];
};

/********************************************************************
 * Utility and crypto functions
 ********************************************************************/

static uint8_t hex2nibble(char in)
{
	uint8_t ret = in - '0';
	return (ret <= 9) ? ret : (10 + toupper(in) - 'A');
}

static uint8_t hex2byte(const char *in)
{
	return (hex2nibble(in[0]) << 4) | hex2nibble(in[1]);
}

void aes128_ecb_encrypt(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
	symmetric_key skey;
	uint8_t tmp[AES_BLOCK_SIZE];

	/* these shouldn't allocate memory or fail */
	if (rijndael_setup(key, AES_KEY_SIZE, 0, &skey) != CRYPT_OK ||
	    rijndael_ecb_encrypt(in, tmp, &skey) != CRYPT_OK)
		abort();
	rijndael_done(&skey);

	/* in case "in" and "out" point to the same buffer */
	memcpy(out, tmp, AES_BLOCK_SIZE);
}

void aes128_ecb_decrypt(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
	symmetric_key skey;
	uint8_t tmp[AES_BLOCK_SIZE];

	if (rijndael_setup(key, AES_KEY_SIZE, 0, &skey) != CRYPT_OK ||
	    rijndael_ecb_decrypt(in, tmp, &skey) != CRYPT_OK)
		abort();
	rijndael_done(&skey);

	memcpy(out, tmp, AES_BLOCK_SIZE);
}

void aes256_cbc_decrypt(const uint8_t *key, const uint8_t *in, int in_len,
			const uint8_t *iv, uint8_t *out)
{
	symmetric_key skey;
	int i, j;
	uint8_t local_iv[AES_BLOCK_SIZE];

	rijndael_setup(key, AES256_KEY_SIZE, 0, &skey);

	memcpy(local_iv, iv, AES_BLOCK_SIZE);
	for (i = 0; i < in_len; i += AES_BLOCK_SIZE) {
		rijndael_ecb_decrypt(in, out, &skey);
		for (j = 0; j < AES_BLOCK_SIZE; j++)
			out[j] ^= local_iv[j];
		memcpy(local_iv, in, AES_BLOCK_SIZE);
		in += AES_BLOCK_SIZE;
		out += AES_BLOCK_SIZE;
	}
}

int securid_rand(void *out, int len, int paranoid)
{
	if (paranoid) {
		/*
		 * Use /dev/random for long lived key material but not for
		 * test purposes.  This can block for a long time if entropy
		 * is limited.
		 */
		int fd;
		char *p = out;

		fd = open("/dev/random", O_RDONLY);
		if (fd < 0)
			return ERR_GENERAL;

		while (len) {
			ssize_t ret = read(fd, p, len);
			if (ret < 0) {
				close(fd);
				return ERR_GENERAL;
			}
			p += ret;
			len -= ret;
		}
		close(fd);
	} else {
		if (rng_get_bytes(out, len, NULL) != len)
			return ERR_GENERAL;
	}
	return ERR_NONE;
}

static void encrypt_then_xor(const uint8_t *key, uint8_t *work, uint8_t *enc)
{
	int i;

	aes128_ecb_encrypt(key, work, enc);
	for (i = 0; i < AES_BLOCK_SIZE; i++)
		work[i] ^= enc[i];
}

static void securid_mac(const uint8_t *in, int in_len, uint8_t *out)
{
	int i, odd = 0;
	const int incr = AES_KEY_SIZE;
	uint8_t work[incr], enc[incr], pad[incr], zero[incr], lastblk[incr], *p;

	memset(zero, 0, incr);
	memset(pad, 0, incr);
	memset(lastblk, 0, incr);
	memset(work, 0xff, incr);

	/* padding */
	p = &pad[incr - 1];
	for (i = in_len * 8; i > 0; i >>= 8)
		*(p--) = (uint8_t)i;

	/* handle the bulk of the input data here */
	for (; in_len > incr; in_len -= incr, in += incr, odd = !odd)
		encrypt_then_xor(in, work, enc);

	/* final 0-16 bytes of input data */
	memcpy(lastblk, in, in_len);
	encrypt_then_xor(lastblk, work, enc);

	/* hash an extra block of zeroes, for certain input lengths */
	if (odd)
		encrypt_then_xor(zero, work, enc);

	/* always hash the padding */
	encrypt_then_xor(pad, work, enc);

	/* run hash over current hash value, then return */
	memcpy(out, work, incr);
	encrypt_then_xor(work, out, enc);
}

static uint16_t securid_shortmac(const uint8_t *in, int in_len)
{
	uint8_t hash[AES_BLOCK_SIZE];

	securid_mac(in, in_len, hash);
	return (hash[0] << 7) | (hash[1] >> 1);
}

static void sha256_hash(const uint8_t *in, int in_len, uint8_t *out)
{
	hash_state md;
	sha256_init(&md);
	sha256_process(&md, in, in_len);
	sha256_done(&md, out);
}

static void sha256_hmac(const uint8_t *key, int key_len,
			const uint8_t *msg, int msg_len, uint8_t *out)
{
	hash_state md;
	uint8_t tmp_key[SHA256_HASH_SIZE], o_key_pad[SHA256_BLOCK_SIZE],
		i_key_pad[SHA256_BLOCK_SIZE], inner_hash[SHA256_BLOCK_SIZE];
	int i;

	if (key_len > SHA256_BLOCK_SIZE) {
		sha256_hash(key, key_len, tmp_key);
		key = tmp_key;
		key_len = SHA256_HASH_SIZE;
	}

	memset(o_key_pad, 0x5c, SHA256_BLOCK_SIZE);
	memset(i_key_pad, 0x36, SHA256_BLOCK_SIZE);
	for (i = 0; i < key_len; i++) {
		o_key_pad[i] ^= key[i];
		i_key_pad[i] ^= key[i];
	}

	sha256_init(&md);
	sha256_process(&md, i_key_pad, SHA256_BLOCK_SIZE);
	sha256_process(&md, msg, msg_len);
	sha256_done(&md, inner_hash);

	sha256_init(&md);
	sha256_process(&md, o_key_pad, SHA256_BLOCK_SIZE);
	sha256_process(&md, inner_hash, SHA256_HASH_SIZE);
	sha256_done(&md, out);
}

static void sha256_pbkdf2(const uint8_t *pass, int pass_len,
			  const uint8_t *salt, int salt_len,
			  int n_rounds, uint8_t *key_out)
{
	uint8_t *ext_salt;
	uint8_t hash[SHA256_HASH_SIZE];
	int i, round;

	ext_salt = alloca(salt_len + 4);
	memcpy(ext_salt, salt, salt_len);

	/* always 0x00000001, as the output size is fixed at SHA256_HASH_SIZE */
	ext_salt[salt_len + 0] = 0;
	ext_salt[salt_len + 1] = 0;
	ext_salt[salt_len + 2] = 0;
	ext_salt[salt_len + 3] = 1;

	sha256_hmac(pass, pass_len, ext_salt, salt_len + 4, key_out);
	memcpy(hash, key_out, SHA256_HASH_SIZE);

	for (round = 2; round <= n_rounds; round++) {
		sha256_hmac(pass, pass_len, hash, SHA256_HASH_SIZE, hash);

		for (i = 0; i < SHA256_HASH_SIZE; i++)
			key_out[i] ^= hash[i];
	}
}

/********************************************************************
 * V1/V2 token handling
 ********************************************************************/

static void numinput_to_bits(const char *in, uint8_t *out, unsigned int n_bits)
{
	int bitpos = 13;

	memset(out, 0, (n_bits + 7) / 8);
	for (; n_bits; n_bits -= TOKEN_BITS_PER_CHAR, in++) {
		uint16_t decoded = (*in - '0') & 0x07;
		decoded <<= bitpos;
		out[0] |= decoded >> 8;
		out[1] |= decoded & 0xff;

		bitpos -= TOKEN_BITS_PER_CHAR;
		if (bitpos < 0) {
			bitpos += 8;
			out++;
		}
	}
}

static void bits_to_numoutput(const uint8_t *in, char *out, unsigned int n_bits)
{
	int bitpos = 13;

	for (; n_bits; n_bits -= TOKEN_BITS_PER_CHAR, out++) {
		uint16_t binary = (in[0] << 8) | in[1];
		*out = ((binary >> bitpos) & 0x07) + '0';

		bitpos -= TOKEN_BITS_PER_CHAR;
		if (bitpos < 0) {
			bitpos += 8;
			in++;
		}
	}
	*out = 0;
}

static uint32_t get_bits(const uint8_t *in, unsigned int start, int n_bits)
{
	uint32_t out = 0;

	in += start / 8;
	start %= 8;

	for (; n_bits > 0; n_bits--) {
		out <<= 1;
		if ((*in << start) & 0x80)
			out |= 0x01;
		start++;
		if (start == 8) {
			start = 0;
			in++;
		}
	}
	return out;
}

static void set_bits(uint8_t *out, unsigned int start, int n_bits, uint32_t val)
{
	out += start / 8;
	start %= 8;
	val <<= (32 - n_bits);

	for (; n_bits > 0; n_bits--) {
		if (val & BIT(31))
			*out |= BIT(7 - start);
		else
			*out &= ~BIT(7 - start);
		val <<= 1;
		start++;
		if (start == 8) {
			start = 0;
			out++;
		}
	}
}

static int v2_decode_token(const char *in, struct securid_token *t)
{
	uint8_t d[MAX_TOKEN_BITS / 8 + 2];
	int len = strlen(in);
	uint16_t token_mac, computed_mac;

	if (len < MIN_TOKEN_CHARS || len > MAX_TOKEN_CHARS)
		return ERR_BAD_LEN;

	/* the last 5 digits provide a checksum for the rest of the string */
	numinput_to_bits(&in[len - CHECKSUM_CHARS], d, 15);
	token_mac = get_bits(d, 0, 15);
	computed_mac = securid_shortmac(in, len - CHECKSUM_CHARS);

	if (token_mac != computed_mac)
		return ERR_CHECKSUM_FAILED;

	t->version = in[0] - '0';
	memcpy(&t->serial, &in[VER_CHARS], SERIAL_CHARS);
	t->serial[SERIAL_CHARS] = 0;

	numinput_to_bits(&in[BINENC_OFS], d, BINENC_BITS);
	memcpy(t->enc_seed, d, AES_KEY_SIZE);
	t->has_enc_seed = 1;

	t->flags = get_bits(d, 128, 16);
	t->exp_date = get_bits(d, 144, 14);
	t->dec_seed_hash = get_bits(d, 159, 15);
	t->device_id_hash = get_bits(d, 174, 15);

	return ERR_NONE;
}

static int generate_key_hash(uint8_t *key_hash, const char *pass,
	const char *devid, uint16_t *device_id_hash, struct securid_token *t)
{
	uint8_t key[MAX_PASS + DEVID_CHARS + MAGIC_LEN + 1], *devid_buf;
	int pos = 0, devid_len = t->is_smartphone ? 40 : 32;
	const uint8_t magic[] = { 0xd8, 0xf5, 0x32, 0x53, 0x82, 0x89, 0x00 };

	memset(key, 0, sizeof(key));

	if (pass) {
		pos = strlen(pass);
		if (pos > MAX_PASS)
			return ERR_BAD_PASSWORD;
		memcpy(key, pass, pos);
	}

	devid_buf = &key[pos];
	if (devid) {
		int len = 0;

		/*
		 * For iPhone/Android ctf strings, the device ID takes up
		 * 40 bytes and consists of hex digits + zero padding.
		 *
		 * For other ctf strings (e.g. --blocks), the device ID takes
		 * up 32 bytes and consists of decimal digits + zero padding.
		 *
		 * If this seed isn't locked to a device, we'll just hash
		 * 40 (or 32) zero bytes, below.
		 */
		for (; *devid; devid++) {
			if (++len > devid_len)
				break;
			if ((t->version == 1 && isdigit(*devid)) ||
			    (t->version >= 2 && !isxdigit(*devid)))
				continue;
			key[pos++] = toupper(*devid);
		}
	}
	if (device_id_hash)
		*device_id_hash = securid_shortmac(devid_buf, devid_len);

	memcpy(&key[pos], magic, MAGIC_LEN);
	securid_mac(key, pos + MAGIC_LEN, key_hash);

	return ERR_NONE;
}

static int v2_decrypt_seed(struct securid_token *t, const char *pass,
			   const char *devid)
{
	uint8_t key_hash[AES_BLOCK_SIZE], dec_seed_hash[AES_BLOCK_SIZE];
	uint16_t computed_mac;
	uint16_t device_id_hash;
	int rc;

	if (t->flags & FL_PASSPROT && !pass)
		return ERR_MISSING_PASSWORD;
	if (t->flags & FL_SNPROT && !devid)
		return ERR_MISSING_PASSWORD;

	rc = generate_key_hash(key_hash,
			       t->flags & FL_PASSPROT ? pass : NULL,
			       t->flags & FL_SNPROT ? devid : NULL,
			       &device_id_hash, t);
	if (rc)
		return rc;

	if (t->flags & FL_SNPROT && device_id_hash != t->device_id_hash)
		return ERR_BAD_DEVID;

	aes128_ecb_decrypt(key_hash, t->enc_seed, t->dec_seed);
	securid_mac(t->dec_seed, AES_KEY_SIZE, dec_seed_hash);
	computed_mac = (dec_seed_hash[0] << 7) | (dec_seed_hash[1] >> 1);

	if (computed_mac != t->dec_seed_hash)
		return ERR_DECRYPT_FAILED;
	t->has_dec_seed = 1;

	return ERR_NONE;
}

static void key_from_time(const uint8_t *bcd_time, int bcd_time_bytes,
	const uint8_t *serial, uint8_t *key)
{
	int i;

	memset(key, 0xaa, 8);
	memcpy(key, bcd_time, bcd_time_bytes);
	memset(key + 12, 0xbb, 4);

	/* write BCD-encoded partial serial number */
	key += 8;
	for (i = 4; i < 12; i += 2)
		*(key++) = ((serial[i] - '0') << 4) |
			    (serial[i + 1] - '0');
}

static void bcd_write(uint8_t *out, int val, unsigned int bytes)
{
	out += bytes - 1;
	for (; bytes; bytes--) {
		*out = val % 10;
		val /= 10;
		*(out--) |= (val % 10) << 4;
		val /= 10;
	}
}

static int v2_encode_token(struct securid_token *t, const char *pass,
			   const char *devid, char *out)
{
	uint8_t d[MAX_TOKEN_BITS / 8 + 2];
	uint8_t key_hash[AES_BLOCK_SIZE];
	int rc;

	rc = generate_key_hash(key_hash, pass, devid, &t->device_id_hash, t);
	if (rc)
		return rc;

	memset(d, 0, sizeof(d));
	aes128_ecb_encrypt(key_hash, t->dec_seed, t->enc_seed);
	memcpy(d, t->enc_seed, AES_KEY_SIZE);

	set_bits(d, 128, 16, t->flags);
	set_bits(d, 144, 14, t->exp_date);
	set_bits(d, 159, 15, securid_shortmac(t->dec_seed, AES_KEY_SIZE));
	set_bits(d, 174, 15, t->device_id_hash);

	sprintf(out, "%d%s", t->version, t->serial);
	bits_to_numoutput(d, &out[BINENC_OFS], BINENC_BITS);

	set_bits(d, 0, 15, securid_shortmac(out, CHECKSUM_OFS));
	bits_to_numoutput(d, &out[CHECKSUM_OFS], CHECKSUM_BITS);

	return ERR_NONE;
}

/********************************************************************
 * V3 token handling
 ********************************************************************/

static void v3_derive_key(const char *pass, const char *devid, const uint8_t *salt,
			  int key_id, uint8_t *out)
{
	uint8_t *buf0, *buf1;
	int pass_len = pass ? strlen(pass) : 0;
	int buf_len = V3_DEVID_CHARS + 16 + V3_NONCE_BYTES + pass_len;
	unsigned int i;
	const uint8_t key0[] = { <KEY>,
				 0xda, 0x09, 0xab, 0xfc, 0x32, 0x49, 0x63, 0x4c };
	const uint8_t key1[] = { <KEY>
				 0x81, 0x60, 0xde, 0x44, 0x4e, 0x05, 0xc0, 0xdd };

	buf0 = alloca(buf_len);
	buf1 = alloca(buf_len / 2);

	memset(buf0, 0, buf_len);

	if (pass)
		strncpy(buf0, pass, pass_len);
	if (devid)
		strncpy(&buf0[pass_len], devid, V3_DEVID_CHARS);
	memcpy(&buf0[pass_len + V3_DEVID_CHARS], key_id ? key1 : key0, 16);
	memcpy(&buf0[pass_len + V3_DEVID_CHARS + 16], salt, V3_NONCE_BYTES);

	/* yup, the PBKDF2 password is really "every 2nd byte of the input" */
	for (i = 1; i < buf_len; i += 2)
		buf1[i >> 1] = buf0[i];

	sha256_pbkdf2(buf1, buf_len >> 1, salt, V3_NONCE_BYTES, 1000, out);
}

static int v3_decode_token(const char *in, struct securid_token *t)
{
	char decoded[BASE64_MIN_INPUT + 1];
	int i, j;
	unsigned long actual;

	/* remove URL-encoding */
	for (i = 0, j = 0; in[i]; ) {
		if (j == BASE64_MIN_INPUT)
			return ERR_BAD_LEN;
		if (in[i] == '%') {
			if (!isxdigit(in[i + 1]) || !isxdigit(in[i + 2]))
				return ERR_BAD_LEN;
			decoded[j++] = hex2byte(&in[i + 1]);
			i += 3;
		} else {
			decoded[j++] = in[i++];
		}
	}
	decoded[j] = 0;

	t->v3 = malloc(sizeof(struct v3_token));
	if (!t->v3)
		return ERR_NO_MEMORY;

	actual = sizeof(struct v3_token);
	if (base64_decode(decoded, strlen(decoded),
			  (void *)t->v3, &actual) != CRYPT_OK ||
	    actual != sizeof(struct v3_token) ||
	    t->v3->version != 0x03) {
		free(t->v3);
		t->v3 = NULL;
		return ERR_GENERAL;
	}

	t->version = 3;
	t->has_enc_seed = 1;

	/* more flags will get populated later when we decrypt the payload */
	t->flags = t->v3->password_locked ? FL_PASSPROT : 0;
	t->flags |= t->v3->devid_locked ? FL_SNPROT : 0;

	return ERR_NONE;
}

static uint16_t v3_parse_date(uint8_t *in)
{
	uint64_t longdate;

	longdate = ((uint64_t)in[0] << 32) |
		   ((uint64_t)in[1] << 24) |
		   ((uint64_t)in[2] << 16) |
		   ((uint64_t)in[3] <<  8) |
		   ((uint64_t)in[4] <<  0);
	longdate /= SECURID_V3_DAY;
	return longdate - (SECURID_EPOCH / (24*60*60));
}

static void v3_compute_hash(const char *pass, const char *devid,
			    const uint8_t *salt, uint8_t *hash)
{
	uint8_t hash_buf[V3_NONCE_BYTES + V3_DEVID_CHARS + MAX_PASS];
	int pass_len = 0;

	memset(hash_buf, 0, sizeof(hash_buf));
	memcpy(&hash_buf[0], salt, V3_NONCE_BYTES);

	if (devid)
		strncpy(&hash_buf[V3_NONCE_BYTES], devid, V3_DEVID_CHARS);

	if (pass) {
		pass_len = strlen(pass);
		strncpy(&hash_buf[V3_NONCE_BYTES + V3_DEVID_CHARS], pass, MAX_PASS);
	}
	sha256_hash(hash_buf, V3_NONCE_BYTES + V3_DEVID_CHARS + pass_len, hash);
}

static void v3_compute_hmac(struct v3_token *v3, const char *pass,
			    const char *devid, uint8_t *out)
{
	uint8_t hash[SHA256_HASH_SIZE];

	v3_derive_key(pass, devid, v3->nonce, 0, hash);
	sha256_hmac(hash, SHA256_HASH_SIZE,
		    (void *)v3, sizeof(*v3) - SHA256_HASH_SIZE, out);
}

static int v3_decrypt_seed(struct securid_token *t,
			   const char *pass, const char *devid)
{
	struct v3_payload payload;
	uint8_t hash[SHA256_HASH_SIZE];

	if (pass && strlen(pass) > MAX_PASS)
		return ERR_BAD_PASSWORD;

	v3_compute_hash(NULL, devid, t->v3->nonce, hash);
	if (memcmp(hash, t->v3->nonce_devid_hash, SHA256_HASH_SIZE) != 0)
		return ERR_BAD_DEVID;

	v3_compute_hash(pass, devid, t->v3->nonce, hash);
	if (memcmp(hash, t->v3->nonce_devid_pass_hash, SHA256_HASH_SIZE) != 0)
		return ERR_DECRYPT_FAILED;

	v3_compute_hmac(t->v3, pass, devid, hash);
	if (memcmp(hash, t->v3->mac, SHA256_HASH_SIZE) != 0)
		return ERR_CHECKSUM_FAILED;

	v3_derive_key(pass, devid, t->v3->nonce, 1, hash);
	aes256_cbc_decrypt(hash,
			   t->v3->enc_payload, sizeof(struct v3_payload),
			   t->v3->nonce, (void *)&payload);

	strncpy(t->serial, payload.serial, SERIAL_CHARS);
	t->serial[SERIAL_CHARS] = 0;

	memcpy(t->dec_seed, &payload.dec_seed, AES_KEY_SIZE);
	t->has_dec_seed = 1;

	t->flags |= FL_TIMESEEDS | FL_128BIT;
	t->flags |= payload.mode ? FL_FEAT4 : 0;
	t->flags |= ((payload.digits-1) << FLD_DIGIT_SHIFT) & FLD_DIGIT_MASK;
	t->flags |= (payload.addpin != V3_ADDPIN_OFF) ?
		    (0x2 << FLD_PINMODE_SHIFT) : 0;
	t->flags |= payload.interval == 60 ? (1 << FLD_NUMSECONDS_SHIFT) : 0;

	t->exp_date = v3_parse_date(payload.exp_date);

	return ERR_NONE;
}

/********************************************************************
 * Public functions
 ********************************************************************/

int securid_decode_token(const char *in, struct securid_token *t)
{
	/*
	 * V1/V2 tokens start with the ASCII version digit
	 * V3 tokens always start with a base64-encoded 0x03 byte, which
	 *   is guaranteed to encode to 'A'
	 */
	if (in[0] == '1' || in[0] == '2')
		return v2_decode_token(in, t);
	else if (strlen(in) >= BASE64_MIN_INPUT && (in[0] == 'A'))
		return v3_decode_token(in, t);
	else
		return ERR_TOKEN_VERSION;
}

int securid_decrypt_seed(struct securid_token *t, const char *pass,
			 const char *devid)
{
	if (t->sdtid)
		return sdtid_decrypt(t, pass);
	else if (t->v3)
		return v3_decrypt_seed(t, pass, devid);
	else
		return v2_decrypt_seed(t, pass, devid);
}

void securid_compute_tokencode(struct securid_token *t, time_t now,
			       char *code_out)
{
	uint8_t bcd_time[8];
	uint8_t key0[AES_KEY_SIZE], key1[AES_KEY_SIZE];
	int i, j;
	uint32_t tokencode;
	struct tm gmt;
	int pin_len = strlen(t->pin);

	gmtime_r(&now, &gmt);
	bcd_write(&bcd_time[0], gmt.tm_year + 1900, 2);
	bcd_write(&bcd_time[2], gmt.tm_mon + 1, 1);
	bcd_write(&bcd_time[3], gmt.tm_mday, 1);
	bcd_write(&bcd_time[4], gmt.tm_hour, 1);
	bcd_write(&bcd_time[5], gmt.tm_min & ~0x03, 1);
	bcd_time[6] = bcd_time[7] = 0;

	key_from_time(bcd_time, 2, t->serial, key0);
	aes128_ecb_encrypt(t->dec_seed, key0, key0);
	key_from_time(bcd_time, 3, t->serial, key1);
	aes128_ecb_encrypt(key0, key1, key1);
	key_from_time(bcd_time, 4, t->serial, key0);
	aes128_ecb_encrypt(key1, key0, key0);
	key_from_time(bcd_time, 5, t->serial, key1);
	aes128_ecb_encrypt(key0, key1, key1);
	key_from_time(bcd_time, 8, t->serial, key0);
	aes128_ecb_encrypt(key1, key0, key0);

	/* key0 now contains 4 consecutive token codes */
	i = (gmt.tm_min & 0x03) << 2;
	tokencode = (key0[i + 0] << 24) | (key0[i + 1] << 16) |
		    (key0[i + 2] << 8)  | (key0[i + 3] << 0);

	/* populate code_out backwards, adding PIN digits if available */
	j = ((t->flags & FLD_DIGIT_MASK) >> FLD_DIGIT_SHIFT) + 1;
	code_out[j--] = 0;
	for (i = 0; j >= 0; j--, i++) {
		uint8_t c = tokencode % 10;
		tokencode /= 10;

		if (i < pin_len)
			c += t->pin[pin_len - i - 1] - '0';
		code_out[j] = c % 10 + '0';
	}
}

int securid_encode_token(const struct securid_token *t, const char *pass,
			 const char *devid, char *out)
{
	struct securid_token newt = *t;

	/* empty password means "<PASSWORD>" */
	if (!pass || !strlen(pass)) {
		pass = NULL;
		newt.flags &= ~FL_PASSPROT;
	} else
		newt.flags |= FL_PASSPROT;

	if (!devid || !strlen(devid)) {
		devid = NULL;
		newt.flags &= ~FL_SNPROT;
	} else
		newt.flags |= FL_SNPROT;

	return v2_encode_token(&newt, pass, devid, out);
}

int securid_random_token(struct securid_token *t)
{
	time_t now = time(NULL);
	uint8_t randbytes[16], key_hash[AES_BLOCK_SIZE];
	int i;

	memset(t, 0, sizeof(*t));

	if (securid_rand(t->dec_seed, AES_KEY_SIZE, 0) ||
	    securid_rand(randbytes, sizeof(randbytes), 0))
		return ERR_GENERAL;

	t->dec_seed_hash = securid_shortmac(t->dec_seed, AES_KEY_SIZE);

	generate_key_hash(key_hash, NULL, NULL, &t->device_id_hash, t);
	aes128_ecb_encrypt(key_hash, t->dec_seed, t->enc_seed);
	t->has_enc_seed = 1;

	t->version = 2;
	t->flags = FL_TIMESEEDS | FLD_DIGIT_MASK | FLD_PINMODE_MASK |
		   (1 << FLD_NUMSECONDS_SHIFT) | FL_128BIT;
{'fraction_non_alphanumeric': 0.16997611576827612, 'fraction_numerical': 0.04214864421861097, 'mean_word_length': 4.196763202725724, 'pattern_counts': {'":': 0, '<': 169, '<?xml version=': 0, '>': 277, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1945047', 'n_tokens_mistral': 20128, 'n_tokens_neox': 18048, 'n_words': 4226}