#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <openssl/opensslconf.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include "bn_local.h"
#include "bytestring.h"
#include "crypto_internal.h"
#include "err_local.h"
#if BN_BYTES == 8
#define BN_DEC_CONV UINT64_C(10000000000000000000)
#define BN_DEC_NUM 19
#else
#define BN_DEC_CONV UINT32_C(1000000000)
#define BN_DEC_NUM 9
#endif
static int bn_dec2bn_cbs(BIGNUM **bnp, CBS *cbs);
static int bn_hex2bn_cbs(BIGNUM **bnp, CBS *cbs);
static const char hex_digits[] = "0123456789ABCDEF";
static int
bn_bn2binpad_internal(const BIGNUM *bn, uint8_t *out, int out_len,
int little_endian)
{
uint8_t mask, v;
BN_ULONG w;
int i, j;
int b, n;
n = BN_num_bytes(bn);
if (out_len == -1)
out_len = n;
if (out_len < n)
return -1;
if (bn->dmax == 0) {
explicit_bzero(out, out_len);
return out_len;
}
mask = 0;
b = BN_BITS2;
j = 0;
for (i = out_len - 1; i >= 0; i--) {
if (b == BN_BITS2) {
mask = crypto_ct_lt_mask(j, bn->top);
w = bn->d[j++ % bn->dmax];
b = 0;
}
out[i] = (w >> b) & mask;
b += 8;
}
if (little_endian) {
for (i = 0, j = out_len - 1; i < out_len / 2; i++, j--) {
v = out[i];
out[i] = out[j];
out[j] = v;
}
}
return out_len;
}
int
BN_bn2bin(const BIGNUM *bn, unsigned char *to)
{
return bn_bn2binpad_internal(bn, to, -1, 0);
}
LCRYPTO_ALIAS(BN_bn2bin);
int
BN_bn2binpad(const BIGNUM *bn, unsigned char *to, int to_len)
{
if (to_len < 0)
return -1;
return bn_bn2binpad_internal(bn, to, to_len, 0);
}
LCRYPTO_ALIAS(BN_bn2binpad);
static int
bn_bin2bn_cbs(BIGNUM **bnp, CBS *cbs, int lebin)
{
BIGNUM *bn = NULL;
BN_ULONG w;
uint8_t v;
int b, i;
if ((bn = *bnp) == NULL)
bn = BN_new();
if (bn == NULL)
goto err;
if (!bn_expand_bytes(bn, CBS_len(cbs)))
goto err;
b = 0;
i = 0;
w = 0;
while (CBS_len(cbs) > 0) {
if (lebin) {
if (!CBS_get_u8(cbs, &v))
goto err;
} else {
if (!CBS_get_last_u8(cbs, &v))
goto err;
}
w |= (BN_ULONG)v << b;
b += 8;
if (b == BN_BITS2 || CBS_len(cbs) == 0) {
b = 0;
bn->d[i++] = w;
w = 0;
}
}
bn->neg = 0;
bn->top = i;
bn_correct_top(bn);
*bnp = bn;
return 1;
err:
if (*bnp == NULL)
BN_free(bn);
return 0;
}
BIGNUM *
BN_bin2bn(const unsigned char *d, int len, BIGNUM *bn)
{
CBS cbs;
if (len < 0)
return NULL;
CBS_init(&cbs, d, len);
if (!bn_bin2bn_cbs(&bn, &cbs, 0))
return NULL;
return bn;
}
LCRYPTO_ALIAS(BN_bin2bn);
int
BN_bn2lebinpad(const BIGNUM *bn, unsigned char *to, int to_len)
{
if (to_len < 0)
return -1;
return bn_bn2binpad_internal(bn, to, to_len, 1);
}
LCRYPTO_ALIAS(BN_bn2lebinpad);
BIGNUM *
BN_lebin2bn(const unsigned char *d, int len, BIGNUM *bn)
{
CBS cbs;
if (len < 0)
return NULL;
CBS_init(&cbs, d, len);
if (!bn_bin2bn_cbs(&bn, &cbs, 1))
return NULL;
return bn;
}
LCRYPTO_ALIAS(BN_lebin2bn);
int
BN_asc2bn(BIGNUM **bnp, const char *s)
{
CBS cbs, cbs_hex;
size_t s_len;
uint8_t v;
int neg;
if (bnp != NULL && *bnp != NULL)
BN_zero(*bnp);
if (s == NULL)
return 0;
if ((s_len = strlen(s)) == 0)
return 0;
CBS_init(&cbs, s, s_len);
if (!CBS_peek_u8(&cbs, &v))
return 0;
if ((neg = (v == '-'))) {
if (!CBS_skip(&cbs, 1))
return 0;
}
CBS_dup(&cbs, &cbs_hex);
if (!CBS_get_u8(&cbs_hex, &v))
goto decimal;
if (v != '0')
goto decimal;
if (!CBS_get_u8(&cbs_hex, &v))
goto decimal;
if (v != 'X' && v != 'x')
goto decimal;
if (bn_hex2bn_cbs(bnp, &cbs_hex) == 0)
return 0;
goto done;
decimal:
if (bn_dec2bn_cbs(bnp, &cbs) == 0)
return 0;
done:
if (bnp != NULL && *bnp != NULL)
BN_set_negative(*bnp, neg);
return 1;
}
LCRYPTO_ALIAS(BN_asc2bn);
char *
BN_bn2dec(const BIGNUM *bn)
{
int started = 0;
BIGNUM *tmp = NULL;
uint8_t *data = NULL;
size_t data_len = 0;
uint8_t *s = NULL;
size_t s_len;
BN_ULONG v, w;
uint8_t c;
CBB cbb;
CBS cbs;
int i;
if (!CBB_init(&cbb, 0))
goto err;
if ((tmp = BN_dup(bn)) == NULL)
goto err;
while (!BN_is_zero(tmp)) {
if ((w = BN_div_word(tmp, BN_DEC_CONV)) == -1)
goto err;
for (i = 0; i < BN_DEC_NUM; i++) {
v = w % 10;
if (!CBB_add_u8(&cbb, '0' + v))
goto err;
w /= 10;
}
}
if (!CBB_finish(&cbb, &data, &data_len))
goto err;
if (data_len > SIZE_MAX - 3)
goto err;
if (!CBB_init(&cbb, data_len + 3))
goto err;
if (BN_is_negative(bn)) {
if (!CBB_add_u8(&cbb, '-'))
goto err;
}
CBS_init(&cbs, data, data_len);
while (CBS_len(&cbs) > 0) {
if (!CBS_get_last_u8(&cbs, &c))
goto err;
if (!started && c == '0')
continue;
if (!CBB_add_u8(&cbb, c))
goto err;
started = 1;
}
if (!started) {
if (!CBB_add_u8(&cbb, '0'))
goto err;
}
if (!CBB_add_u8(&cbb, '\0'))
goto err;
if (!CBB_finish(&cbb, &s, &s_len))
goto err;
err:
BN_free(tmp);
CBB_cleanup(&cbb);
freezero(data, data_len);
return s;
}
LCRYPTO_ALIAS(BN_bn2dec);
static int
bn_dec2bn_cbs(BIGNUM **bnp, CBS *cbs)
{
CBS cbs_digits;
BIGNUM *bn = NULL;
int d, neg, num;
size_t digits = 0;
BN_ULONG w;
uint8_t v;
if (!CBS_peek_u8(cbs, &v))
goto err;
if ((neg = (v == '-'))) {
if (!CBS_skip(cbs, 1))
goto err;
}
CBS_dup(cbs, &cbs_digits);
while (CBS_len(&cbs_digits) > 0) {
if (!CBS_get_u8(&cbs_digits, &v))
goto err;
if (!isdigit(v))
break;
digits++;
}
if (digits > INT_MAX / 4)
goto err;
num = digits + neg;
if (bnp == NULL)
return num;
if ((bn = *bnp) == NULL)
bn = BN_new();
if (bn == NULL)
goto err;
if (!bn_expand_bits(bn, digits * 4))
goto err;
if ((d = digits % BN_DEC_NUM) == 0)
d = BN_DEC_NUM;
w = 0;
while (digits-- > 0) {
if (!CBS_get_u8(cbs, &v))
goto err;
if (v < '0' || v > '9')
goto err;
v -= '0';
w = w * 10 + v;
d--;
if (d == 0) {
if (!BN_mul_word(bn, BN_DEC_CONV))
goto err;
if (!BN_add_word(bn, w))
goto err;
d = BN_DEC_NUM;
w = 0;
}
}
bn_correct_top(bn);
BN_set_negative(bn, neg);
*bnp = bn;
return num;
err:
if (bnp != NULL && *bnp == NULL)
BN_free(bn);
return 0;
}
int
BN_dec2bn(BIGNUM **bnp, const char *s)
{
size_t s_len;
CBS cbs;
if (bnp != NULL && *bnp != NULL)
BN_zero(*bnp);
if (s == NULL)
return 0;
if ((s_len = strlen(s)) == 0)
return 0;
CBS_init(&cbs, s, s_len);
return bn_dec2bn_cbs(bnp, &cbs);
}
LCRYPTO_ALIAS(BN_dec2bn);
static int
bn_bn2hex_internal(const BIGNUM *bn, int include_sign, int nibbles_only,
char **out, size_t *out_len)
{
int started = 0;
uint8_t *s = NULL;
size_t s_len = 0;
BN_ULONG v, w;
int i, j;
CBB cbb;
CBS cbs;
uint8_t nul;
int ret = 0;
*out = NULL;
*out_len = 0;
if (!CBB_init(&cbb, 0))
goto err;
if (BN_is_negative(bn) && include_sign) {
if (!CBB_add_u8(&cbb, '-'))
goto err;
}
if (BN_is_zero(bn)) {
if (!CBB_add_u8(&cbb, '0'))
goto err;
}
for (i = bn->top - 1; i >= 0; i--) {
w = bn->d[i];
for (j = BN_BITS2 - 8; j >= 0; j -= 8) {
v = (w >> j) & 0xff;
if (!started && v == 0)
continue;
if (started || !nibbles_only || (v >> 4) != 0) {
if (!CBB_add_u8(&cbb, hex_digits[v >> 4]))
goto err;
}
if (!CBB_add_u8(&cbb, hex_digits[v & 0xf]))
goto err;
started = 1;
}
}
if (!CBB_add_u8(&cbb, '\0'))
goto err;
if (!CBB_finish(&cbb, &s, &s_len))
goto err;
CBS_init(&cbs, s, s_len);
if (!CBS_get_last_u8(&cbs, &nul))
goto err;
*out = (char *)CBS_data(&cbs);
*out_len = CBS_len(&cbs);
s = NULL;
s_len = 0;
ret = 1;
err:
CBB_cleanup(&cbb);
freezero(s, s_len);
return ret;
}
int
bn_bn2hex_nosign(const BIGNUM *bn, char **out, size_t *out_len)
{
return bn_bn2hex_internal(bn, 0, 0, out, out_len);
}
int
bn_bn2hex_nibbles(const BIGNUM *bn, char **out, size_t *out_len)
{
return bn_bn2hex_internal(bn, 1, 1, out, out_len);
}
char *
BN_bn2hex(const BIGNUM *bn)
{
char *s;
size_t s_len;
if (!bn_bn2hex_internal(bn, 1, 0, &s, &s_len))
return NULL;
return s;
}
LCRYPTO_ALIAS(BN_bn2hex);
static int
bn_hex2bn_cbs(BIGNUM **bnp, CBS *cbs)
{
CBS cbs_digits;
BIGNUM *bn = NULL;
int b, i, neg, num;
size_t digits = 0;
BN_ULONG w;
uint8_t v;
if (!CBS_peek_u8(cbs, &v))
goto err;
if ((neg = (v == '-'))) {
if (!CBS_skip(cbs, 1))
goto err;
}
CBS_dup(cbs, &cbs_digits);
while (CBS_len(&cbs_digits) > 0) {
if (!CBS_get_u8(&cbs_digits, &v))
goto err;
if (!isxdigit(v))
break;
digits++;
}
if (digits > INT_MAX / 4)
goto err;
num = digits + neg;
if (bnp == NULL)
return num;
if ((bn = *bnp) == NULL)
bn = BN_new();
if (bn == NULL)
goto err;
if (!bn_expand_bits(bn, digits * 4))
goto err;
if (!CBS_get_bytes(cbs, cbs, digits))
goto err;
b = 0;
i = 0;
w = 0;
while (digits-- > 0) {
if (!CBS_get_last_u8(cbs, &v))
goto err;
if (v >= '0' && v <= '9')
v -= '0';
else if (v >= 'a' && v <= 'f')
v -= 'a' - 10;
else if (v >= 'A' && v <= 'F')
v -= 'A' - 10;
else
goto err;
w |= (BN_ULONG)v << b;
b += 4;
if (b == BN_BITS2 || digits == 0) {
b = 0;
bn->d[i++] = w;
w = 0;
}
}
bn->top = i;
bn_correct_top(bn);
BN_set_negative(bn, neg);
*bnp = bn;
return num;
err:
if (bnp != NULL && *bnp == NULL)
BN_free(bn);
return 0;
}
int
BN_hex2bn(BIGNUM **bnp, const char *s)
{
size_t s_len;
CBS cbs;
if (bnp != NULL && *bnp != NULL)
BN_zero(*bnp);
if (s == NULL)
return 0;
if ((s_len = strlen(s)) == 0)
return 0;
CBS_init(&cbs, s, s_len);
return bn_hex2bn_cbs(bnp, &cbs);
}
LCRYPTO_ALIAS(BN_hex2bn);
int
BN_bn2mpi(const BIGNUM *bn, unsigned char *d)
{
uint8_t *out_bin;
size_t out_len, out_bin_len;
int bits, bytes;
int extend;
CBB cbb, cbb_bin;
bits = BN_num_bits(bn);
bytes = (bits + 7) / 8;
extend = (bits != 0) && (bits % 8 == 0);
out_bin_len = extend + bytes;
out_len = 4 + out_bin_len;
if (d == NULL)
return out_len;
if (!CBB_init_fixed(&cbb, d, out_len))
goto err;
if (!CBB_add_u32_length_prefixed(&cbb, &cbb_bin))
goto err;
if (!CBB_add_space(&cbb_bin, &out_bin, out_bin_len))
goto err;
if (BN_bn2binpad(bn, out_bin, out_bin_len) != out_bin_len)
goto err;
if (!CBB_finish(&cbb, NULL, NULL))
goto err;
if (bn->neg)
d[4] |= 0x80;
return out_len;
err:
CBB_cleanup(&cbb);
return -1;
}
LCRYPTO_ALIAS(BN_bn2mpi);
BIGNUM *
BN_mpi2bn(const unsigned char *d, int n, BIGNUM *bn_in)
{
BIGNUM *bn = bn_in;
uint32_t mpi_len;
uint8_t v;
int neg = 0;
CBS cbs;
if (n < 0)
return NULL;
CBS_init(&cbs, d, n);
if (!CBS_get_u32(&cbs, &mpi_len)) {
BNerror(BN_R_INVALID_LENGTH);
return NULL;
}
if (CBS_len(&cbs) != mpi_len) {
BNerror(BN_R_ENCODING_ERROR);
return NULL;
}
if (CBS_len(&cbs) > 0) {
if (!CBS_peek_u8(&cbs, &v))
return NULL;
neg = (v >> 7) & 1;
}
if (!bn_bin2bn_cbs(&bn, &cbs, 0))
return NULL;
if (neg)
BN_clear_bit(bn, BN_num_bits(bn) - 1);
BN_set_negative(bn, neg);
return bn;
}
LCRYPTO_ALIAS(BN_mpi2bn);