Compare commits

...

4 Commits

Author SHA1 Message Date
c2f21a2185 [test] Add generic tests for elliptic curve point multiplication
Signed-off-by: Michael Brown <mcb30@ipxe.org>
2025-01-22 15:07:02 +00:00
c9291bc5c7 [tls] Allow for NIST elliptic curve point formats
The elliptic curve point representation for the x25519 curve includes
only the X value, since the curve is designed such that the Montgomery
ladder does not need to ever know or calculate a Y value.  There is no
curve point format byte: the public key data is simply the X value.
The pre-master secret is also simply the X value of the shared secret
curve point.

The point representation for the NIST curves includes both X and Y
values, and a single curve point format byte that must indicate that
the format is uncompressed.  The pre-master secret for the NIST curves
does not include both X and Y values: only the X value is used.

Extend the definition of an elliptic curve to allow the point size to
be specified separately from the key size, and extend the definition
of a TLS named curve to include an optional curve point format byte
and a pre-master secret length.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2025-01-21 15:55:33 +00:00
df7ec31766 [crypto] Generalise elliptic curve key exchange to ecdhe_key()
Split out the portion of tls_send_client_key_exchange_ecdhe() that
actually performs the elliptic curve key exchange into a separate
function ecdhe_key().

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2025-01-21 15:20:17 +00:00
cc38d7dd3e [crypto] Add bigint_ntoa() for transcribing big integers
In debug messages, big integers are currently printed as hex dumps.
This is quite verbose and cumbersome to check against external
sources.

Add bigint_ntoa() to transcribe big integers into a static buffer
(following the model of inet_ntoa(), eth_ntoa(), uuid_ntoa(), etc).

Abbreviate big integers that will not fit within the static buffer,
showing both the most significant and least significant digits in the
transcription.  This is generally the most useful form when visually
comparing against external sources (such as test vectors, or results
produced by high-level languages).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2025-01-20 16:00:44 +00:00
11 changed files with 338 additions and 18 deletions

View File

@ -26,6 +26,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ipxe/profile.h>
#include <ipxe/bigint.h>
@ -38,6 +39,52 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
static struct profiler bigint_mod_profiler __profiler =
{ .name = "bigint_mod" };
/** Minimum number of least significant bytes included in transcription */
#define BIGINT_NTOA_LSB_MIN 16
/**
* Transcribe big integer (for debugging)
*
* @v value0 Element 0 of big integer to be transcribed
* @v size Number of elements
* @ret string Big integer in string form (may be abbreviated)
*/
const char * bigint_ntoa_raw ( const bigint_element_t *value0,
unsigned int size ) {
const bigint_t ( size ) __attribute__ (( may_alias ))
*value = ( ( const void * ) value0 );
bigint_element_t element;
static char buf[256];
unsigned int count;
int threshold;
uint8_t byte;
char *tmp;
int i;
/* Calculate abbreviation threshold, if any */
count = ( size * sizeof ( element ) );
threshold = count;
if ( count >= ( ( sizeof ( buf ) - 1 /* NUL */ ) / 2 /* "xx" */ ) ) {
threshold -= ( ( sizeof ( buf ) - 3 /* "..." */
- ( BIGINT_NTOA_LSB_MIN * 2 /* "xx" */ )
- 1 /* NUL */ ) / 2 /* "xx" */ );
}
/* Transcribe bytes, abbreviating with "..." if necessary */
for ( tmp = buf, i = ( count - 1 ) ; i >= 0 ; i-- ) {
element = value->element[ i / sizeof ( element ) ];
byte = ( element >> ( 8 * ( i % sizeof ( element ) ) ) );
tmp += sprintf ( tmp, "%02x", byte );
if ( i == threshold ) {
tmp += sprintf ( tmp, "..." );
i = BIGINT_NTOA_LSB_MIN;
}
}
assert ( tmp < ( buf + sizeof ( buf ) ) );
return buf;
}
/**
* Conditionally swap big integers (in constant time)
*

66
src/crypto/ecdhe.c Normal file
View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or 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
* General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* Elliptic Curve Ephemeral Diffie-Hellman (ECDHE) key exchange
*
*/
#include <string.h>
#include <ipxe/ecdhe.h>
/**
* Calculate ECDHE key
*
* @v curve Elliptic curve
* @v partner Partner public curve point
* @v private Private key
* @v public Public curve point to fill in (may overlap partner key)
* @v shared Shared secret curve point to fill in
* @ret rc Return status code
*/
int ecdhe_key ( struct elliptic_curve *curve, const void *partner,
const void *private, void *public, void *shared ) {
int rc;
/* Construct shared key */
if ( ( rc = elliptic_multiply ( curve, partner, private,
shared ) ) != 0 ) {
DBGC ( curve, "CURVE %s could not generate shared key: %s\n",
curve->name, strerror ( rc ) );
return rc;
}
/* Construct public key */
if ( ( rc = elliptic_multiply ( curve, NULL, private,
public ) ) != 0 ) {
DBGC ( curve, "CURVE %s could not generate public key: %s\n",
curve->name, strerror ( rc ) );
return rc;
}
return 0;
}

View File

@ -42,4 +42,5 @@ struct asn1_algorithm x25519_algorithm __asn1_algorithm = {
struct tls_named_curve tls_x25519_named_curve __tls_named_curve ( 01 ) = {
.curve = &x25519_curve,
.code = htons ( TLS_NAMED_CURVE_X25519 ),
.pre_master_secret_len = sizeof ( struct x25519_value ),
};

View File

@ -839,6 +839,7 @@ static int x25519_curve_multiply ( const void *base, const void *scalar,
/** X25519 elliptic curve */
struct elliptic_curve x25519_curve = {
.name = "x25519",
.pointsize = sizeof ( struct x25519_value ),
.keysize = sizeof ( struct x25519_value ),
.multiply = x25519_curve_multiply,
};

View File

@ -40,6 +40,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define bigint_size( bigint ) \
( sizeof ( *(bigint) ) / sizeof ( (bigint)->element[0] ) )
/**
* Transcribe big integer (for debugging)
*
* @v value Big integer to be transcribed
* @ret string Big integer in string form (may be abbreviated)
*/
#define bigint_ntoa( value ) ( { \
unsigned int size = bigint_size (value); \
bigint_ntoa_raw ( (value)->element, size ); \
} )
/**
* Initialise big integer
*
@ -360,6 +371,8 @@ bigint_msb_is_set_raw ( const bigint_element_t *value0, unsigned int size ) {
return ( !! ( value->element[index] & ( 1UL << subindex ) ) );
}
const char * bigint_ntoa_raw ( const bigint_element_t *value0,
unsigned int size );
void bigint_init_raw ( bigint_element_t *value0, unsigned int size,
const void *data, size_t len );
void bigint_done_raw ( const bigint_element_t *value0, unsigned int size,

View File

@ -184,7 +184,9 @@ struct pubkey_algorithm {
struct elliptic_curve {
/** Curve name */
const char *name;
/** Key size */
/** Point (and public key) size */
size_t pointsize;
/** Scalar (and private key) size */
size_t keysize;
/** Multiply scalar by curve point
*

17
src/include/ipxe/ecdhe.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef _IPXE_ECDHE_H
#define _IPXE_ECDHE_H
/** @file
*
* Elliptic Curve Ephemeral Diffie-Hellman (ECDHE) key exchange
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/crypto.h>
extern int ecdhe_key ( struct elliptic_curve *curve, const void *partner,
const void *private, void *public, void *shared );
#endif /* _IPXE_ECDHE_H */

View File

@ -218,12 +218,19 @@ struct tls_cipher_suite {
/** TLS named curved type */
#define TLS_NAMED_CURVE_TYPE 3
/** TLS uncompressed curve point format */
#define TLS_POINT_FORMAT_UNCOMPRESSED 4
/** A TLS named curve */
struct tls_named_curve {
/** Elliptic curve */
struct elliptic_curve *curve;
/** Numeric code (in network-endian order) */
uint16_t code;
/** Curve point format byte (if any) */
uint8_t format;
/** Pre-master secret length */
uint8_t pre_master_secret_len;
};
/** TLS named curve table */

View File

@ -50,6 +50,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/validator.h>
#include <ipxe/job.h>
#include <ipxe/dhe.h>
#include <ipxe/ecdhe.h>
#include <ipxe/tls.h>
#include <config/crypto.h>
@ -1670,6 +1671,9 @@ static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) {
uint8_t public[0];
} __attribute__ (( packed )) *ecdh;
size_t param_len;
size_t pointsize;
size_t keysize;
size_t offset;
int rc;
/* Parse ServerKeyExchange record */
@ -1705,9 +1709,13 @@ static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) {
tls->server.exchange_len );
return -ENOTSUP_CURVE;
}
DBGC ( tls, "TLS %p using named curve %s\n", tls, curve->curve->name );
pointsize = curve->curve->pointsize;
keysize = curve->curve->keysize;
offset = ( curve->format ? 1 : 0 );
/* Check key length */
if ( ecdh->public_len != curve->curve->keysize ) {
if ( ecdh->public_len != ( offset + pointsize ) ) {
DBGC ( tls, "TLS %p invalid %s key\n",
tls, curve->curve->name );
DBGC_HDA ( tls, 0, tls->server.exchange,
@ -1715,15 +1723,23 @@ static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) {
return -EINVAL_KEY_EXCHANGE;
}
/* Check curve point format byte (if present) */
if ( curve->format && ( ecdh->public[0] != curve->format ) ) {
DBGC ( tls, "TLS %p invalid %s curve point format\n",
tls, curve->curve->name );
DBGC_HDA ( tls, 0, tls->server.exchange,
tls->server.exchange_len );
return -EINVAL_KEY_EXCHANGE;
}
/* Construct pre-master secret and ClientKeyExchange record */
{
size_t len = curve->curve->keysize;
uint8_t private[len];
uint8_t pre_master_secret[len];
uint8_t private[keysize];
uint8_t pre_master_secret[pointsize];
struct {
uint32_t type_length;
uint8_t public_len;
uint8_t public[len];
uint8_t public[ecdh->public_len];
} __attribute__ (( packed )) key_xchg;
/* Generate ephemeral private key */
@ -1732,30 +1748,27 @@ static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) {
return rc;
}
/* Calculate pre-master secret */
if ( ( rc = elliptic_multiply ( curve->curve,
ecdh->public, private,
pre_master_secret ) ) != 0 ) {
/* Exchange keys */
if ( ( rc = ecdhe_key ( curve->curve, ( ecdh->public + offset ),
private, ( key_xchg.public + offset ),
pre_master_secret ) ) != 0 ) {
DBGC ( tls, "TLS %p could not exchange ECDHE key: %s\n",
tls, strerror ( rc ) );
return rc;
}
/* Generate master secret */
tls_generate_master_secret ( tls, pre_master_secret, len );
tls_generate_master_secret ( tls, pre_master_secret,
curve->pre_master_secret_len );
/* Generate Client Key Exchange record */
key_xchg.type_length =
( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
htonl ( sizeof ( key_xchg ) -
sizeof ( key_xchg.type_length ) ) );
key_xchg.public_len = len;
if ( ( rc = elliptic_multiply ( curve->curve, NULL, private,
key_xchg.public ) ) != 0 ) {
DBGC ( tls, "TLS %p could not generate ECDHE key: %s\n",
tls, strerror ( rc ) );
return rc;
}
key_xchg.public_len = sizeof ( key_xchg.public );
if ( curve->format )
key_xchg.public[0] = curve->format;
/* Transmit Client Key Exchange record */
if ( ( rc = tls_send_handshake ( tls, &key_xchg,

76
src/tests/elliptic_test.c Normal file
View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or 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
* General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* Elliptic curve self-tests
*
*/
/* Forcibly enable assertions */
#undef NDEBUG
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <ipxe/crypto.h>
#include <ipxe/test.h>
#include "elliptic_test.h"
/**
* Report elliptic curve point multiplication test result
*
* @v test Elliptic curve point multiplication test
* @v file Test code file
* @v line Test code line
*/
void elliptic_okx ( struct elliptic_test *test, const char *file,
unsigned int line ) {
struct elliptic_curve *curve = test->curve;
size_t pointsize = curve->pointsize;
size_t keysize = curve->keysize;
uint8_t actual[pointsize];
int rc;
/* Sanity checks */
okx ( ( test->base_len == pointsize ) || ( ! test->base_len ),
file, line );
okx ( test->scalar_len == keysize, file, line );
okx ( ( test->expected_len == pointsize ) || ( ! test->expected_len ),
file, line );
/* Perform point multiplication */
rc = elliptic_multiply ( curve, ( test->base_len ? test->base : NULL ),
test->scalar, actual );
if ( test->expected_len ) {
okx ( rc == 0, file, line );
} else {
okx ( rc != 0, file, line );
}
/* Check expected result */
okx ( memcmp ( actual, test->expected, test->expected_len ) == 0,
file, line );
}

77
src/tests/elliptic_test.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef _ELLIPTIC_TEST_H
#define _ELLIPTIC_TEST_H
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/crypto.h>
#include <ipxe/test.h>
/** An elliptic curve point multiplication test */
struct elliptic_test {
/** Elliptic curve */
struct elliptic_curve *curve;
/** Base point */
const void *base;
/** Length of base point (or 0 to use generator) */
size_t base_len;
/** Scalar multiple */
const void *scalar;
/** Length of scalar multiple */
size_t scalar_len;
/** Expected result point */
const void *expected;
/** Length of expected result point (or 0 to expect failure) */
size_t expected_len;
};
/** Define inline base point */
#define BASE(...) { __VA_ARGS__ }
/** Define base point to be curve's generator */
#define BASE_GENERATOR BASE()
/** Define inline scalar multiple */
#define SCALAR(...) { __VA_ARGS__ }
/** Define inline expected result point */
#define EXPECTED(...) { __VA_ARGS__ }
/** Define result as an expected failure */
#define EXPECTED_FAIL EXPECTED()
/**
* Define an elliptic curve point multiplication test
*
* @v name Test name
* @v CURVE Elliptic curve
* @v BASE Base point
* @v SCALAR Scalar multiple
* @v EXPECTED Expected result point
* @ret test Elliptic curve point multiplication test
*/
#define ELLIPTIC_TEST( name, CURVE, BASE, SCALAR, EXPECTED ) \
static const uint8_t name ## _base[] = BASE; \
static const uint8_t name ## _scalar[] = SCALAR; \
static const uint8_t name ## _expected[] = EXPECTED; \
static struct elliptic_test name = { \
.curve = CURVE, \
.base = name ## _base, \
.base_len = sizeof ( name ## _base ), \
.scalar = name ## _scalar, \
.scalar_len = sizeof ( name ## _scalar ), \
.expected = name ## _expected, \
.expected_len = sizeof ( name ## _expected ), \
};
extern void elliptic_okx ( struct elliptic_test *test, const char *file,
unsigned int line );
/**
* Report an elliptic curve point multiplication test result
*
* @v test Elliptic curve point multiplication test
*/
#define elliptic_ok( test ) elliptic_okx ( test, __FILE__, __LINE__ )
#endif /* _ELLIPTIC_TEST_H */