forked from acouzens/open5gs
[UDM] Added validation for pubkey
a cryptographic vulnerability in the SUCI decryption routines of Open5GS 5G—specifically Profile B, which uses P-256 (secp256r1) for its elliptic curve routines. If a mobile device user passes a public key within its SUCI that does not correspond to a valid point on the P-256 elliptic curve, the Open5GS UDM will not check the point before running elliptic curve operations with it and returning a response to the mobile device user. If the public key is not checked to be a valid point, an attacker can leverage this behavior to extract the Profile B private key from the UDM, as has been done in other domains (https://owasp.org/www-pdf-archive/Practical_Invalid_Curve_Attacks_on_TLS-ECDH_-_Juraj_Somorovsky.pdf). Note that Profile A is not similarly vulnerable to this, as it is impossible to construct an invalid point on a curve25519 elliptic curve. There was some work that went into developing a practical proof of concept of this kind of attack against free5gc last year; it can be found here: https://www.gsma.com/security/wp-content/uploads/2023/10/0073-invalid_curve.pdf And here is the free5gc security advisory: https://github.com/advisories/GHSA-cqvv-r3g3-26rf To mitigate this issue in Open5GS, the public key of the UE must be validated by the UDM prior to use. Adding a validation function such as the following should work: I designed this code based on information from https://crypto.stackexchange.com/questions/90151/verify-that-a-point-belongs-to-secp256r1.
This commit is contained in:
parent
cf4ad1f248
commit
7c14073533
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ogs-core.h"
|
||||||
|
|
||||||
#define NUM_ECC_DIGITS (ECC_BYTES/8)
|
#define NUM_ECC_DIGITS (ECC_BYTES/8)
|
||||||
#define MAX_TRIES 16
|
#define MAX_TRIES 16
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ static int getRandomNumber(uint64_t *p_vli)
|
||||||
HCRYPTPROV l_prov;
|
HCRYPTPROV l_prov;
|
||||||
if(!CryptAcquireContext(&l_prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
|
if(!CryptAcquireContext(&l_prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
|
||||||
{
|
{
|
||||||
|
ogs_error("CryptAcquireContext() failed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +110,11 @@ static int getRandomNumber(uint64_t *p_vli)
|
||||||
int l_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
int l_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
||||||
if(l_fd == -1)
|
if(l_fd == -1)
|
||||||
{
|
{
|
||||||
|
ogs_error("open(/dev/urandom) failed");
|
||||||
l_fd = open("/dev/random", O_RDONLY | O_CLOEXEC);
|
l_fd = open("/dev/random", O_RDONLY | O_CLOEXEC);
|
||||||
if(l_fd == -1)
|
if(l_fd == -1)
|
||||||
{
|
{
|
||||||
|
ogs_error("open(/dev/random) failed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +127,7 @@ static int getRandomNumber(uint64_t *p_vli)
|
||||||
if(l_read <= 0)
|
if(l_read <= 0)
|
||||||
{ // read failed
|
{ // read failed
|
||||||
close(l_fd);
|
close(l_fd);
|
||||||
|
ogs_error("read() failed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
l_left -= l_read;
|
l_left -= l_read;
|
||||||
|
@ -1073,6 +1079,7 @@ int ecc_make_key(uint8_t p_publicKey[ECC_BYTES+1], uint8_t p_privateKey[ECC_BYTE
|
||||||
{
|
{
|
||||||
if(!getRandomNumber(l_private) || (l_tries++ >= MAX_TRIES))
|
if(!getRandomNumber(l_private) || (l_tries++ >= MAX_TRIES))
|
||||||
{
|
{
|
||||||
|
ogs_error("getRandomNumber() failed [%d]", l_tries);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(vli_isZero(l_private))
|
if(vli_isZero(l_private))
|
||||||
|
@ -1096,6 +1103,45 @@ int ecc_make_key(uint8_t p_publicKey[ECC_BYTES+1], uint8_t p_privateKey[ECC_BYTE
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CURVE_A_32 {0xFFFFFFFFFFFFFFFCull, 0x00000000FFFFFFFFull, 0x0000000000000000ull, 0xFFFFFFFF00000001ull}
|
||||||
|
|
||||||
|
static int ecdh_validate_pubkey(EccPoint l_public, uint64_t l_private[NUM_ECC_DIGITS]) {
|
||||||
|
uint64_t left[NUM_ECC_DIGITS];
|
||||||
|
uint64_t right[NUM_ECC_DIGITS];
|
||||||
|
uint64_t curve_a[NUM_ECC_DIGITS] = CURVE_A_32;
|
||||||
|
/*
|
||||||
|
* To ensure l_public is a valid point on the curve, we need to check:
|
||||||
|
* y^2 % p == (x^3 + a * x + b) % p)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Compute y^2 % p and store in `left` */
|
||||||
|
vli_modSquare_fast(left, l_public.y);
|
||||||
|
|
||||||
|
/* Compute x^3 and store in `right` */
|
||||||
|
vli_modSquare_fast(right, l_public.x);
|
||||||
|
vli_modMult_fast(right, right, l_public.x);
|
||||||
|
|
||||||
|
/* Compute a * x and store in `curve_a` */
|
||||||
|
vli_modMult_fast(curve_a, curve_a, l_public.x);
|
||||||
|
/* Store ((a * x) + b) % p in `curve_a */
|
||||||
|
vli_modAdd(curve_a, curve_a, curve_b, curve_p);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Combine x^3 and ((a * x) + b) to make (x^3 + a * x + b) % p);
|
||||||
|
* store in `right`
|
||||||
|
*/
|
||||||
|
vli_modAdd(right, right, curve_a, curve_p);
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < NUM_ECC_DIGITS; i++) {
|
||||||
|
if (left[i] != right[i]) {
|
||||||
|
return 0; // y^2 % p != (x^3 + a * x + b) % p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int ecdh_shared_secret(const uint8_t p_publicKey[ECC_BYTES+1], const uint8_t p_privateKey[ECC_BYTES], uint8_t p_secret[ECC_BYTES])
|
int ecdh_shared_secret(const uint8_t p_publicKey[ECC_BYTES+1], const uint8_t p_privateKey[ECC_BYTES], uint8_t p_secret[ECC_BYTES])
|
||||||
{
|
{
|
||||||
EccPoint l_public;
|
EccPoint l_public;
|
||||||
|
@ -1104,12 +1150,23 @@ int ecdh_shared_secret(const uint8_t p_publicKey[ECC_BYTES+1], const uint8_t p_p
|
||||||
|
|
||||||
if(!getRandomNumber(l_random))
|
if(!getRandomNumber(l_random))
|
||||||
{
|
{
|
||||||
|
ogs_error("getRandomNumber() failed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ecc_point_decompress(&l_public, p_publicKey);
|
ecc_point_decompress(&l_public, p_publicKey);
|
||||||
ecc_bytes2native(l_private, p_privateKey);
|
ecc_bytes2native(l_private, p_privateKey);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Validate received public key `p_publicKey` is a valid point
|
||||||
|
* on curve P-256
|
||||||
|
*/
|
||||||
|
if (!ecdh_validate_pubkey(l_public, l_private))
|
||||||
|
{
|
||||||
|
ogs_error("ecdh_validate_pubkey() failed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
EccPoint l_product;
|
EccPoint l_product;
|
||||||
EccPoint_mult(&l_product, &l_public, l_private, l_random);
|
EccPoint_mult(&l_product, &l_public, l_private, l_random);
|
||||||
|
|
||||||
|
@ -1202,6 +1259,7 @@ int ecdsa_sign(const uint8_t p_privateKey[ECC_BYTES], const uint8_t p_hash[ECC_B
|
||||||
{
|
{
|
||||||
if(!getRandomNumber(k) || (l_tries++ >= MAX_TRIES))
|
if(!getRandomNumber(k) || (l_tries++ >= MAX_TRIES))
|
||||||
{
|
{
|
||||||
|
ogs_error("getRandomNumber() failed [%d]", l_tries);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(vli_isZero(k))
|
if(vli_isZero(k))
|
||||||
|
@ -1256,11 +1314,13 @@ int ecdsa_verify(const uint8_t p_publicKey[ECC_BYTES+1], const uint8_t p_hash[EC
|
||||||
|
|
||||||
if(vli_isZero(l_r) || vli_isZero(l_s))
|
if(vli_isZero(l_r) || vli_isZero(l_s))
|
||||||
{ /* r, s must not be 0. */
|
{ /* r, s must not be 0. */
|
||||||
|
ogs_error("r, s must not be 0");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(vli_cmp(curve_n, l_r) != 1 || vli_cmp(curve_n, l_s) != 1)
|
if(vli_cmp(curve_n, l_r) != 1 || vli_cmp(curve_n, l_s) != 1)
|
||||||
{ /* r, s must be < n. */
|
{ /* r, s must be < n. */
|
||||||
|
ogs_error("r, s must be < n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue