Commit 714920a0 authored by Jan Včelák's avatar Jan Včelák 🚀
Browse files

Merge branch 'dnssec-integer-encoding' into 'master'

Proper integer encoding in signature and public key conversion operations.

Closes #357 #358
parents 64d74dbb c6680129
......@@ -22,6 +22,8 @@ libshared_la_CPPFLAGS = \
libshared_la_SOURCES = \
shared/base32hex.c \
shared/base32hex.h \
shared/bignum.c \
shared/bignum.h \
shared/dname.c \
shared/dname.h \
shared/fs.c \
......
......@@ -140,28 +140,6 @@ int dnssec_binary_cmp(const dnssec_binary_t *one, const dnssec_binary_t *two)
}
}
_public_
void dnssec_binary_ltrim(dnssec_binary_t *binary)
{
if (!binary || !binary->data) {
return;
}
size_t start = 0;
while (start + 1 < binary->size && binary->data[start] == 0x00) {
start += 1;
}
if (start == 0) {
return;
}
size_t new_size = binary->size - start;
memmove(binary->data, binary->data + start, new_size);
binary->size = new_size;
}
_public_
int dnssec_binary_from_base64(const dnssec_binary_t *base64,
dnssec_binary_t *binary)
......
......@@ -111,15 +111,6 @@ int dnssec_binary_resize(dnssec_binary_t *data, size_t new_size);
*/
int dnssec_binary_cmp(const dnssec_binary_t *one, const dnssec_binary_t *two);
/*!
* Trim leading zeroes from the binary data.
*
* If the input are zeroes only, the last zero will be preserved.
*
* \param binary Input to be trimmed.
*/
void dnssec_binary_ltrim(dnssec_binary_t *binary);
/*!
* Allocate binary from Base64 encoded string.
*
......
......@@ -21,6 +21,7 @@
#include <stdint.h>
#include <string.h>
#include "bignum.h"
#include "binary.h"
#include "error.h"
#include "key.h"
......@@ -29,20 +30,27 @@
#include "shared.h"
#include "wire.h"
/**
* Trim leading zeroes from binary data.
*
* GnuTLS uses Nettle, Nettle uses GMP, and some GMP conversions operations
* return numbers prefixed with a zero byte. The byte is useless and can
* create incompatibility with other DNSSEC software.
*/
static void trim_leading_zeros(gnutls_datum_t *data)
/* -- wrappers for GnuTLS types -------------------------------------------- */
static size_t bignum_size_u_datum(const gnutls_datum_t *_bignum)
{
const dnssec_binary_t bignum = binary_from_datum(_bignum);
return bignum_size_u(&bignum);
}
static void wire_write_bignum_datum(wire_ctx_t *ctx, size_t width,
const gnutls_datum_t *_bignum)
{
assert(data);
const dnssec_binary_t bignum = binary_from_datum(_bignum);
wire_write_bignum(ctx, width, &bignum);
}
static gnutls_datum_t wire_take_datum(wire_ctx_t *ctx, size_t count)
{
gnutls_datum_t result = { .data = ctx->position, .size = count };
ctx->position += count;
dnssec_binary_t tmp = datum_to_binary(data);
dnssec_binary_ltrim(&tmp);
*data = binary_to_datum(&tmp);
return result;
}
/* -- DNSSEC to crypto ------------------------------------------------------*/
......@@ -56,32 +64,30 @@ static int rsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata)
assert(rdata);
_cleanup_datum_ gnutls_datum_t modulus = { 0 };
_cleanup_datum_ gnutls_datum_t pub_exponent = { 0 };
_cleanup_datum_ gnutls_datum_t exponent = { 0 };
int result = gnutls_pubkey_get_pk_rsa_raw(key, &modulus, &pub_exponent);
int result = gnutls_pubkey_get_pk_rsa_raw(key, &modulus, &exponent);
if (result != GNUTLS_E_SUCCESS) {
return DNSSEC_KEY_EXPORT_ERROR;
}
trim_leading_zeros(&modulus);
trim_leading_zeros(&pub_exponent);
size_t exponent_size = bignum_size_u_datum(&exponent);
if (exponent_size > UINT8_MAX) {
return DNSSEC_KEY_EXPORT_ERROR;
}
assert(pub_exponent.size <= UINT8_MAX);
size_t modulus_size = bignum_size_u_datum(&modulus);
size_t size = 1 + pub_exponent.size + modulus.size;
uint8_t *data = malloc(size);
if (!data) {
return DNSSEC_ENOMEM;
result = dnssec_binary_alloc(rdata, 1 + exponent_size + modulus_size);
if (result != DNSSEC_EOK) {
return result;
}
wire_ctx_t wire = wire_init(data, size);
wire_write_u8(&wire, pub_exponent.size);
wire_write_datum(&wire, &pub_exponent);
wire_write_datum(&wire, &modulus);
assert(wire_tell(&wire) == size);
rdata->size = size;
rdata->data = data;
wire_ctx_t wire = wire_init_binary(rdata);
wire_write_u8(&wire, exponent_size);
wire_write_bignum_datum(&wire, exponent_size, &exponent);
wire_write_bignum_datum(&wire, modulus_size, &modulus);
assert(wire_tell(&wire) == rdata->size);
return DNSSEC_EOK;
}
......@@ -104,41 +110,55 @@ static int dsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata)
return DNSSEC_KEY_EXPORT_ERROR;
}
trim_leading_zeros(&p);
trim_leading_zeros(&q);
trim_leading_zeros(&g);
trim_leading_zeros(&y);
size_t p_size = bignum_size_u_datum(&p);
size_t q_size = bignum_size_u_datum(&q);
size_t g_size = bignum_size_u_datum(&g);
size_t y_size = bignum_size_u_datum(&y);
if (q.size != 20) {
if (q_size != 20) {
// only certain key size range can be exported in DNSKEY
return DNSSEC_INVALID_KEY_SIZE;
}
assert(p.size == g.size && g.size == y.size);
assert(p.size >= 64 && (p.size - 64) % 8 == 0);
if (p_size != g_size || g_size != y_size) {
return DNSSEC_INVALID_KEY_SIZE;
}
if (p_size < 64 || (p_size - 64) % 8 != 0) {
return DNSSEC_INVALID_KEY_SIZE;
}
uint8_t t = (p.size - 64) / 8;
uint8_t t = (p_size - 64) / 8;
size_t size = 1 + q.size + (3 * p.size);
uint8_t *data = malloc(size);
if (!data) {
return DNSSEC_ENOMEM;
size_t size = 1 + q_size + p_size + g_size + y_size;
result = dnssec_binary_alloc(rdata, size);
if (result != DNSSEC_EOK) {
return result;
}
wire_ctx_t ctx = wire_init(data, size);
wire_ctx_t ctx = wire_init_binary(rdata);
wire_write_u8(&ctx, t);
wire_write_datum(&ctx, &q);
wire_write_datum(&ctx, &p);
wire_write_datum(&ctx, &g);
wire_write_datum(&ctx, &y);
assert(wire_tell(&ctx) == size);
rdata->size = size;
rdata->data = data;
wire_write_bignum_datum(&ctx, q_size, &q);
wire_write_bignum_datum(&ctx, p_size, &p);
wire_write_bignum_datum(&ctx, g_size, &g);
wire_write_bignum_datum(&ctx, y_size, &y);
assert(wire_tell(&ctx) == rdata->size);
return DNSSEC_EOK;
}
/*!
* Get point size for an ECDSA curve.
*/
static size_t ecdsa_curve_point_size(gnutls_ecc_curve_t curve)
{
switch (curve) {
case GNUTLS_ECC_CURVE_SECP256R1: return 32;
case GNUTLS_ECC_CURVE_SECP384R1: return 48;
default: return 0;
}
}
/*!
* Convert ECDSA public key to DNSSEC format.
*/
......@@ -156,24 +176,20 @@ static int ecdsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata)
return DNSSEC_KEY_EXPORT_ERROR;
}
trim_leading_zeros(&point_x);
trim_leading_zeros(&point_y);
assert(point_x.size == point_y.size);
size_t size = point_x.size + point_y.size;
uint8_t *data = malloc(size);
if (!data) {
return DNSSEC_ENOMEM;
size_t point_size = ecdsa_curve_point_size(curve);
if (point_size == 0) {
return DNSSEC_INVALID_PUBLIC_KEY;
}
wire_ctx_t wire = wire_init(data, size);
wire_write_datum(&wire, &point_x);
wire_write_datum(&wire, &point_y);
assert(wire_tell(&wire) == size);
result = dnssec_binary_alloc(rdata, 2 * point_size);
if (result != DNSSEC_EOK) {
return result;
}
rdata->size = size;
rdata->data = data;
wire_ctx_t wire = wire_init_binary(rdata);
wire_write_bignum_datum(&wire, point_size, &point_x);
wire_write_bignum_datum(&wire, point_size, &point_y);
assert(wire_tell(&wire) == rdata->size);
return DNSSEC_EOK;
}
......@@ -194,35 +210,27 @@ static int rsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key
wire_ctx_t ctx = wire_init_binary(rdata);
// parse exponent
// parse public exponent
_cleanup_datum_ gnutls_datum_t pub_exponent = { 0 };
pub_exponent.size = wire_read_u8(&ctx);
if (pub_exponent.size == 0 || wire_available(&ctx) < pub_exponent.size) {
uint8_t exponent_size = wire_read_u8(&ctx);
if (exponent_size == 0 || wire_available(&ctx) < exponent_size) {
return DNSSEC_INVALID_PUBLIC_KEY;
}
pub_exponent.data = gnutls_malloc(pub_exponent.size);
if (!pub_exponent.data) {
return DNSSEC_ENOMEM;
}
wire_read_datum(&ctx, &pub_exponent);
gnutls_datum_t exponent = wire_take_datum(&ctx, exponent_size);
// parse modulus
_cleanup_datum_ gnutls_datum_t modulus = { 0 };
modulus.size = wire_available(&ctx);
if (modulus.size == 0) {
size_t modulus_size = wire_available(&ctx);
if (modulus_size == 0) {
return DNSSEC_INVALID_PUBLIC_KEY;
}
modulus.data = gnutls_malloc(modulus.size);
if (!modulus.data) {
return DNSSEC_ENOMEM;
}
wire_read_datum(&ctx, &modulus);
gnutls_datum_t modulus = wire_take_datum(&ctx, modulus_size);
assert(wire_tell(&ctx) == rdata->size);
int result = gnutls_pubkey_import_rsa_raw(key, &modulus, &pub_exponent);
int result = gnutls_pubkey_import_rsa_raw(key, &modulus, &exponent);
if (result != GNUTLS_E_SUCCESS) {
return DNSSEC_KEY_IMPORT_ERROR;
}
......@@ -276,7 +284,7 @@ static int dsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key
return DNSSEC_INVALID_PUBLIC_KEY;
}
wire_ctx_t ctx =wire_init_binary(rdata);
wire_ctx_t ctx = wire_init_binary(rdata);
// read t
......@@ -287,29 +295,14 @@ static int dsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key
// parse q
_cleanup_datum_ gnutls_datum_t q = { 0 };
q.size = 20;
q.data = gnutls_malloc(q.size);
if (!q.data) {
return DNSSEC_ENOMEM;
}
wire_read_datum(&ctx, &q);
gnutls_datum_t q = wire_take_datum(&ctx, 20);
// parse p, g, and y
_cleanup_datum_ gnutls_datum_t p = { 0 };
_cleanup_datum_ gnutls_datum_t g = { 0 };
_cleanup_datum_ gnutls_datum_t y = { 0 };
p.size = g.size = y.size = wire_available(&ctx) / 3;
p.data = gnutls_malloc(p.size);
g.data = gnutls_malloc(g.size);
y.data = gnutls_malloc(y.size);
if (!p.data || !g.data || !y.data) {
return DNSSEC_ENOMEM;
}
wire_read_datum(&ctx, &p);
wire_read_datum(&ctx, &g);
wire_read_datum(&ctx, &y);
size_t param_size = wire_available(&ctx) / 3;
gnutls_datum_t p = wire_take_datum(&ctx, param_size);
gnutls_datum_t g = wire_take_datum(&ctx, param_size);
gnutls_datum_t y = wire_take_datum(&ctx, param_size);
assert(wire_tell(&ctx) == rdata->size);
......@@ -322,9 +315,9 @@ static int dsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key
}
/**
* Choose proper ECDSA curve based on RDATA public key size.
* Get ECDSA curve based on DNSKEY RDATA size.
*/
static gnutls_ecc_curve_t choose_ecdsa_curve(size_t rdata_size)
static gnutls_ecc_curve_t ecdsa_curve_from_rdata_size(size_t rdata_size)
{
switch (rdata_size) {
case 64: return GNUTLS_ECC_CURVE_SECP256R1;
......@@ -341,26 +334,18 @@ static int ecdsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t k
assert(rdata);
assert(key);
gnutls_ecc_curve_t curve = choose_ecdsa_curve(rdata->size);
gnutls_ecc_curve_t curve = ecdsa_curve_from_rdata_size(rdata->size);
if (curve == GNUTLS_ECC_CURVE_INVALID) {
return DNSSEC_INVALID_PUBLIC_KEY;
}
wire_ctx_t ctx = wire_init_binary(rdata);
// parse points
_cleanup_datum_ gnutls_datum_t point_x = { 0 };
_cleanup_datum_ gnutls_datum_t point_y = { 0 };
point_x.size = point_y.size = rdata->size / 2;
point_x.data = gnutls_malloc(point_x.size);
point_y.data = gnutls_malloc(point_y.size);
if (!point_x.data || !point_y.data) {
return DNSSEC_ENOMEM;
}
wire_read_datum(&ctx, &point_x);
wire_read_datum(&ctx, &point_y);
wire_ctx_t ctx = wire_init_binary(rdata);
size_t point_size = wire_available(&ctx) / 2;
gnutls_datum_t point_x = wire_take_datum(&ctx, point_size);
gnutls_datum_t point_y = wire_take_datum(&ctx, point_size);
assert(wire_tell(&ctx) == rdata->size);
int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, &point_y);
......
......@@ -16,6 +16,7 @@
#include <stdbool.h>
#include "bignum.h"
#include "binary.h"
#include "error.h"
#include "sign/der.h"
......@@ -68,12 +69,12 @@ static int asn1_decode_size(wire_ctx_t *wire, size_t *size)
}
/*!
* Decode an integer object and retrieves a pointer to it.
* Decode an unsigned integer object.
*/
static int asn1_decode_integer(wire_ctx_t *wire, dnssec_binary_t *value)
static int asn1_decode_integer(wire_ctx_t *wire, dnssec_binary_t *_value)
{
assert(wire);
assert(value);
assert(_value);
if (!asn1_expect_type(wire, ASN1_TYPE_INTEGER)) {
return DNSSEC_MALFORMED_DATA;
......@@ -85,14 +86,21 @@ static int asn1_decode_integer(wire_ctx_t *wire, dnssec_binary_t *value)
return result;
}
if (size > wire_available(wire)) {
if (size == 0 || size > wire_available(wire)) {
return DNSSEC_MALFORMED_DATA;
}
value->size = size;
value->data = wire->position;
dnssec_binary_t value = { .data = wire->position, .size = size };
wire->position += size;
// skip leading zeroes (unless equal to zero)
while (value.size > 1 && value.data[0] == 0) {
value.data += 1;
value.size -= 1;
}
*_value = value;
return DNSSEC_EOK;
}
......@@ -109,16 +117,17 @@ static void asn1_write_header(wire_ctx_t *wire, uint8_t type, size_t length)
}
/*!
* Encode integer object.
* Encode unsigned integer object.
*/
static void asn1_write_integer(wire_ctx_t *wire, const dnssec_binary_t *integer)
static void asn1_write_integer(wire_ctx_t *wire, size_t integer_size,
const dnssec_binary_t *integer)
{
assert(wire);
assert(integer);
assert(integer->data);
asn1_write_header(wire, ASN1_TYPE_INTEGER, integer->size);
wire_write_binary(wire, integer);
asn1_write_header(wire, ASN1_TYPE_INTEGER, integer_size);
wire_write_bignum(wire, integer_size, integer);
}
/*!
......@@ -179,19 +188,22 @@ int dss_sig_value_decode(const dnssec_binary_t *der,
* Encode signature parameters from X.509 (EC)DSA signature.
*/
int dss_sig_value_encode(const dnssec_binary_t *r, const dnssec_binary_t *s,
dnssec_binary_t *der)
dnssec_binary_t *_der)
{
if (!r || !r->data || !s || !s->data || !der) {
if (!r || !r->data || !s || !s->data || !_der) {
return DNSSEC_EINVAL;
}
size_t r_size = bignum_size_s(r);
size_t s_size = bignum_size_s(s);
// check supported inputs range
if (r->size > ASN1_MAX_SIZE || s->size > ASN1_MAX_SIZE) {
if (r_size > ASN1_MAX_SIZE || s_size > ASN1_MAX_SIZE) {
return DNSSEC_NOT_IMPLEMENTED_ERROR;
}
size_t seq_size = 2 + r->size + 2 + s->size;
size_t seq_size = 2 + r_size + 2 + s_size;
if (seq_size > ASN1_MAX_SIZE) {
return DNSSEC_NOT_IMPLEMENTED_ERROR;
}
......@@ -199,20 +211,19 @@ int dss_sig_value_encode(const dnssec_binary_t *r, const dnssec_binary_t *s,
// encode result
size_t total_size = 2 + seq_size;
uint8_t *encoded = malloc(total_size);
if (!encoded) {
dnssec_binary_t der = { 0 };
if (dnssec_binary_alloc(&der, total_size)) {
return DNSSEC_ENOMEM;
}
wire_ctx_t wire = wire_init(encoded, total_size);
wire_ctx_t wire = wire_init_binary(&der);
asn1_write_header(&wire, ASN1_TYPE_SEQUENCE, seq_size);
asn1_write_integer(&wire, r);
asn1_write_integer(&wire, s);
asn1_write_integer(&wire, r_size, r);
asn1_write_integer(&wire, s_size, s);
assert(wire_available(&wire) == 0);
der->size = total_size;
der->data = encoded;
*_der = der;
return DNSSEC_EOK;
}
......@@ -25,6 +25,10 @@
* of the signature.
*
* This module provides decoding and encoding of this format.
*
* The 'r' and 's' values are treated as unsigned values: The leading zeroes
* are stripped on decoding; an extra leading zero is added on encoding in case
* the value starts with a set bit.
*/
/*!
......
......@@ -20,6 +20,7 @@
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include "bignum.h"
#include "error.h"
#include "key.h"
#include "key/internal.h"
......@@ -77,17 +78,7 @@ static int rsa_copy_signature(dnssec_sign_ctx_t *ctx,
assert(from);
assert(to);
uint8_t *data = malloc(from->size);
if (!data) {
return DNSSEC_ENOMEM;
}
memmove(data, from->data, from->size);
to->size = from->size;
to->data = data;
return DNSSEC_EOK;
return dnssec_binary_dup(from, to);
}
static const algorithm_functions_t rsa_functions = {
......@@ -133,29 +124,25 @@ static int dsa_x509_to_dnssec(dnssec_sign_ctx_t *ctx,
return result;
}
dnssec_binary_ltrim(&value_r);
dnssec_binary_ltrim(&value_s);
size_t r_size = bignum_size_u(&value_r);
size_t s_size = bignum_size_u(&value_s);
if (value_r.size > 20 || value_s.size > 20) {
if (r_size > 20 || s_size > 20) {
return DNSSEC_MALFORMED_DATA;
}
uint8_t value_t = dsa_dnskey_get_t_value(ctx->key);
size_t size = 41;
uint8_t *data = malloc(size);
if (!data) {
return DNSSEC_ENOMEM;
result = dnssec_binary_alloc(dnssec, 41);
if (result != DNSSEC_EOK) {
return result;
}
wire_ctx_t wire = wire_init(data, size);
wire_ctx_t wire = wire_init_binary(dnssec);
wire_write_u8(&wire, value_t);
wire_write_ralign_binary(&wire, 20, &value_r);
wire_write_ralign_binary(&wire, 20, &value_s);
assert(wire_tell(&wire) == size);
dnssec->size = size;
dnssec->data = data;
wire_write_bignum(&wire, 20, &value_r);
wire_write_bignum(&wire, 20, &value_s);
assert(wire_tell(&wire) == dnssec->size);
return DNSSEC_EOK;
}
......@@ -174,16 +161,8 @@ static int dsa_dnssec_to_x509(dnssec_sign_ctx_t *ctx,
const dnssec_binary_t value_r = { .size = 20, .data = dnssec->data + 1 };
const dnssec_binary_t value_s = { .size = 20, .data = dnssec->data + 21 };
dnssec_binary_t der = { 0 };
int result = dss_sig_value_encode(&value_r, &value_s, &der);
if (result != DNSSEC_EOK) {
return result;
}
*x509 = der;
return DNSSEC_EOK;
return dss_sig_value_encode(&value_r, &value_s, x509);