Skip to content

Commit 195751c

Browse files
rsa key validation test and additions to rsa fromdata test
updated rsa_key_import / validate to survive new tests
1 parent ecdd878 commit 195751c

4 files changed

Lines changed: 647 additions & 13 deletions

File tree

src/wp_rsa_kmgmt.c

Lines changed: 242 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ static const char* wp_rsa_param_key[WP_RSA_PARAM_NUMS_CNT] = {
203203
OSSL_PKEY_PARAM_RSA_EXPONENT1, OSSL_PKEY_PARAM_RSA_EXPONENT2,
204204
OSSL_PKEY_PARAM_RSA_COEFFICIENT1
205205
};
206+
#define WP_RSA_PARAM_KEY_FACTOR_INDEX1 3
207+
#define WP_RSA_PARAM_KEY_FACTOR_INDEX2 4
208+
#define WP_RSA_PARAM_KEY_EXPONENT_INDEX1 5
209+
#define WP_RSA_PARAM_KEY_EXPONENT_INDEX2 6
210+
#define WP_RSA_PARAM_KEY_COEFFICIENT_INDEX 7
206211

207212
/**
208213
* RSA PSS parameters.
@@ -1186,6 +1191,29 @@ static int wp_rsa_match(const wp_Rsa* rsa1, const wp_Rsa* rsa2, int selection)
11861191
return ok;
11871192
}
11881193

1194+
/* These are the primes used by OSSL to reject an RSA N with a small GCD */
1195+
static const mp_digit validate_primes[] = {
1196+
0x0002, 0x0003, 0x0005, 0x0007, 0x000B, 0x000D, 0x0011, 0x0013,
1197+
0x0017, 0x001D, 0x001F, 0x0025, 0x0029, 0x002B, 0x002F, 0x0035,
1198+
0x003B, 0x003D, 0x0043, 0x0047, 0x0049, 0x004F, 0x0053, 0x0059,
1199+
0x0061, 0x0065, 0x0067, 0x006B, 0x006D, 0x0071, 0x007F, 0x0083,
1200+
0x0089, 0x008B, 0x0095, 0x0097, 0x009D, 0x00A3, 0x00A7, 0x00AD,
1201+
0x00B3, 0x00B5, 0x00BF, 0x00C1, 0x00C5, 0x00C7, 0x00D3, 0x00DF,
1202+
0x00E3, 0x00E5, 0x00E9, 0x00EF, 0x00F1, 0x00FB, 0x0101, 0x0107,
1203+
0x010D, 0x010F, 0x0115, 0x0119, 0x011B, 0x0125, 0x0133, 0x0137,
1204+
0x0139, 0x013D, 0x014B, 0x0151, 0x015B, 0x015D, 0x0161, 0x0167,
1205+
0x016F, 0x0175, 0x017B, 0x017F, 0x0185, 0x018D, 0x0191, 0x0199,
1206+
0x01A3, 0x01A5, 0x01AF, 0x01B1, 0x01B7, 0x01BB, 0x01C1, 0x01C9,
1207+
0x01CD, 0x01CF, 0x01D3, 0x01DF, 0x01E7, 0x01EB, 0x01F3, 0x01F7,
1208+
0x01FD, 0x0209, 0x020B, 0x021D, 0x0223, 0x022D, 0x0233, 0x0239,
1209+
0x023B, 0x0241, 0x024B, 0x0251, 0x0257, 0x0259, 0x025F, 0x0265,
1210+
0x0269, 0x026B, 0x0277, 0x0281, 0x0283, 0x0287, 0x028D, 0x0293,
1211+
0x0295, 0x02A1, 0x02A5, 0x02AB, 0x02B3, 0x02BD, 0x02C5, 0x02CF,
1212+
0x02D7, 0x02DD, 0x02E3, 0x02E7, 0x02EF,
1213+
};
1214+
#define VALIDATE_PRIMES_SIZE XELEM_CNT(validate_primes)
1215+
wc_static_assert(VALIDATE_PRIMES_SIZE == 133);
1216+
11891217
/**
11901218
* Validate the RSA key.
11911219
*
@@ -1218,22 +1246,193 @@ static int wp_rsa_validate(const wp_Rsa* rsa, int selection, int checkType)
12181246
else
12191247
#endif
12201248
if (checkPriv) {
1221-
if (mp_isone(&rsa->key.d) || mp_iszero((mp_int*)&rsa->key.d) ||
1222-
(mp_cmp((mp_int*)&rsa->key.d, (mp_int*)&rsa->key.n) != MP_LT)) {
1249+
if (mp_iszero((mp_int*)&rsa->key.d) ||
1250+
(mp_cmp((mp_int*)&rsa->key.d, (mp_int*)&rsa->key.n) != MP_LT)) {
12231251
ok = 0;
12241252
}
12251253
}
12261254
else if (checkPub) {
1227-
if (mp_iseven(&rsa->key.e) || mp_iszero((mp_int*)&rsa->key.e) ||
1228-
mp_isone(&rsa->key.e)) {
1255+
int nbits = mp_count_bits((mp_int*)&rsa->key.n);
1256+
mp_int res;
1257+
1258+
if (mp_iszero((mp_int*)&rsa->key.e) || mp_iszero((mp_int*)&rsa->key.n) ||
1259+
mp_isone((mp_int*)&rsa->key.e) || mp_isone((mp_int*)&rsa->key.n) ||
1260+
mp_iseven((mp_int*)&rsa->key.e) || mp_iseven((mp_int*)&rsa->key.n)) {
1261+
ok = 0;
1262+
}
1263+
1264+
if (ok && nbits > OPENSSL_RSA_MAX_MODULUS_BITS) {
1265+
ok = 0;
1266+
}
1267+
1268+
#ifdef HAVE_FIPS
1269+
if (ok && (wolfProvider_GetFipsChecks() & WP_FIPS_CHECK_RSA_KEY_SIZE)) {
1270+
/* n must have at least 112 bits of security, and
1271+
* 2^16 < e < 2^256 */
1272+
int ebits = mp_count_bits((mp_int*)&rsa->key.e);
1273+
if (nbits < 2048 || ebits <= 16 || ebits >= 257) {
1274+
ok = 0;
1275+
}
1276+
}
1277+
#endif
1278+
1279+
/* Reject a modulus that shares a small prime factor */
1280+
if (ok && mp_init(&res) != MP_OKAY) {
1281+
ok = 0;
1282+
}
1283+
else if (ok) {
1284+
unsigned int prime;
1285+
for(prime = 0; prime < VALIDATE_PRIMES_SIZE; prime++) {
1286+
if (mp_set_int(&res, validate_primes[prime]) != MP_OKAY ||
1287+
mp_mod((mp_int*)&rsa->key.n, &res, &res) != MP_OKAY ||
1288+
mp_iszero(&res)) {
1289+
ok = 0;
1290+
break;
1291+
}
1292+
}
1293+
1294+
mp_clear(&res);
1295+
}
1296+
1297+
#if (!defined(WOLFSSL_SP_MATH) && !defined(WOLFSSL_SP_MATH_ALL)) || \
1298+
defined(WOLFSSL_SP_PRIME_GEN)
1299+
/* Reject prime n since it is not composite.
1300+
* Note this does not catch a prime power (p^k), which OSSL
1301+
* also rejects. */
1302+
if (ok) {
1303+
int isPrime = MP_NO;
1304+
if (mp_prime_is_prime((mp_int*)&rsa->key.n, 5, &isPrime) != MP_OKAY ||
1305+
isPrime == MP_YES) {
1306+
ok = 0;
1307+
}
1308+
}
1309+
#endif
1310+
}
1311+
1312+
WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
1313+
return ok;
1314+
}
1315+
1316+
/**
1317+
* Copy an unsigned value from an OSSL param into a provided RSA key parameter.
1318+
* Negative values result in an error.
1319+
*
1320+
* @param [out] mp RSA key parameter.
1321+
* @param [in] param Parameter to copy value from.
1322+
* @return Size of mp in bits on success.
1323+
* @return -1 on failure.
1324+
*/
1325+
static int wp_rsa_import_store_unsigned(mp_int* mp, const OSSL_PARAM* param)
1326+
{
1327+
int ok;
1328+
int bits = -1;
1329+
const unsigned char* data;
1330+
1331+
WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_import_store_unsigned");
1332+
1333+
#if OPENSSL_VERSION_NUMBER <= 0x30100080L
1334+
ok = param->data != NULL && param->data_type == OSSL_PARAM_UNSIGNED_INTEGER;
1335+
#else
1336+
ok = param->data != NULL && (param->data_type == OSSL_PARAM_INTEGER ||
1337+
param->data_type == OSSL_PARAM_UNSIGNED_INTEGER);
1338+
#endif /* OPENSSL_VERSION_NUMBER <= 3.1.8 */
1339+
1340+
if (ok && !wp_mp_read_unsigned_bin_le(mp, param->data, param->data_size)) {
1341+
WOLFPROV_MSG(WP_LOG_COMP_RSA,
1342+
"Failed to read %s from parameters", param->key);
1343+
ok = 0;
1344+
}
1345+
1346+
if (ok) {
1347+
bits = mp_count_bits(mp);
1348+
}
1349+
1350+
/* OSSL accepts negative values for these params, but this doesn't work well
1351+
* in wolfProvider so reject it. */
1352+
if (ok && param->data_type == OSSL_PARAM_INTEGER) {
1353+
data = (const unsigned char*)param->data;
1354+
/* Assume little-endian when determining sign */
1355+
if (param->data_size > 0 && (data[param->data_size - 1] & 0x80) != 0) {
1356+
WOLFPROV_MSG(WP_LOG_COMP_RSA, "Negative value for %s rejected",
1357+
param->key);
12291358
ok = 0;
12301359
}
12311360
}
12321361

12331362
WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
1363+
return ok ? bits : -1;
1364+
}
1365+
1366+
/**
1367+
* Based on the index into wp_rsa_param_key, increment the appropriate counter.
1368+
*
1369+
* @param [in] index Index associated with wp_rsa_param_key.
1370+
* @param [out] primes Count of prime parameters.
1371+
* @param [out] exps Count of exponent parameters.
1372+
* @param [out] coeffs Count of coefficient parameters.
1373+
*/
1374+
static void wp_rsa_import_increment_crt_counts(int index, int *primes,
1375+
int *exps, int *coeffs)
1376+
{
1377+
if (index >= WP_RSA_PARAM_KEY_FACTOR_INDEX1 &&
1378+
index <= WP_RSA_PARAM_KEY_FACTOR_INDEX2) {
1379+
*primes += 1;
1380+
}
1381+
else if (index >= WP_RSA_PARAM_KEY_EXPONENT_INDEX1 &&
1382+
index <= WP_RSA_PARAM_KEY_EXPONENT_INDEX2) {
1383+
*exps += 1;
1384+
}
1385+
else if (index == WP_RSA_PARAM_KEY_COEFFICIENT_INDEX) {
1386+
*coeffs += 1;
1387+
}
1388+
}
1389+
1390+
/**
1391+
* Verify the RSA CRT parameters supplied for a private key import are complete.
1392+
*
1393+
* @param [in] primes Count of prime factor parameters provided.
1394+
* @param [in] exps Count of CRT exponent parameters provided.
1395+
* @param [in] coeffs Count of CRT coefficient parameters provided.
1396+
* @return 1 when the CRT parameters are acceptable
1397+
* @return 0 when the provided factors/CRT parameters are incomplete or
1398+
* unsupported.
1399+
*/
1400+
static int wp_rsa_import_verify_crt(int primes, int exps, int coeffs)
1401+
{
1402+
int ok = 1;
1403+
1404+
(void)exps;
1405+
(void)coeffs;
1406+
1407+
if (primes > 0) {
1408+
#if (OPENSSL_VERSION_NUMBER < 0x30100040L && OPENSSL_VERSION_NUMBER > 0x30000120L) \
1409+
|| (OPENSSL_VERSION_NUMBER < 0x300000C0L)
1410+
if (primes < 2 || primes != exps || primes != coeffs + 1) {
1411+
#else
1412+
if (primes < 2) {
1413+
#endif /* (Ver < 3.1.4 && Ver > 3.0.18) || (Ver < 3.0.12) */
1414+
WOLFPROV_MSG(WP_LOG_COMP_RSA,
1415+
"RSA factors provided but CRT parameters incomplete");
1416+
ok = 0;
1417+
}
1418+
/* TODO: multi-prime checks */
1419+
else if (primes > 2) {
1420+
WOLFPROV_MSG(WP_LOG_COMP_RSA, "RSA multi-prime import failed");
1421+
ok = 0;
1422+
}
1423+
}
1424+
12341425
return ok;
12351426
}
12361427

1428+
#if OPENSSL_VERSION_NUMBER >= 0x30400000L
1429+
#define wp_rsa_import_check_bits(bits, nbits) \
1430+
((bits) != -1 && (bits) <= (nbits))
1431+
#else
1432+
#define wp_rsa_import_check_bits(bits, nbits) \
1433+
((bits) != -1)
1434+
#endif /* OPENSSL_VERSION_NUMBER >= 3.4.0 */
1435+
12371436
/**
12381437
* Import the key data into RSA key object from parameters.
12391438
*
@@ -1251,20 +1450,39 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[],
12511450
int cnt = 0;
12521451
mp_int* mp = NULL;
12531452
const OSSL_PARAM* p = NULL;
1453+
const OSSL_PARAM* n;
1454+
const OSSL_PARAM* e;
1455+
const OSSL_PARAM* d = NULL;
1456+
int bits;
1457+
int nbits;
1458+
int primes = 0;
1459+
int exps = 0;
1460+
int coeffs = 0;
12541461

12551462
WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_import_key_data");
12561463

12571464
/* N and E params are the only ones required by OSSL, so match that.
12581465
* See ossl_rsa_fromdata() and RSA_set0_key() in OpenSSL. */
1259-
if (OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N) == NULL ||
1260-
OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E) == NULL) {
1466+
if ((n = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N)) == NULL ||
1467+
n->data == NULL ||
1468+
(e = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E)) == NULL ||
1469+
e->data == NULL) {
12611470
WOLFPROV_MSG(WP_LOG_COMP_RSA, "Param N or E is missing");
12621471
ok = 0;
12631472
}
1473+
if (ok && priv) {
1474+
d = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D);
1475+
}
12641476

12651477
if (ok) {
1266-
cnt = wp_params_count(params);
12671478
rsa->key.type = priv ? RSA_PRIVATE : RSA_PUBLIC;
1479+
mp = &rsa->key.n;
1480+
nbits = wp_rsa_import_store_unsigned(mp, n);
1481+
ok = nbits != -1;
1482+
}
1483+
1484+
if (ok && d != NULL) {
1485+
cnt = wp_params_count(params);
12681486

12691487
for (i = 0; i < cnt; i++) {
12701488
/* Use the table to look up the offset in the rsa struct */
@@ -1273,6 +1491,8 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[],
12731491
for (j = 0; j < (int)ARRAY_SIZE(wp_rsa_param_key); j++) {
12741492
if (XSTRCMP(p->key, wp_rsa_param_key[j]) == 0) {
12751493
index = j;
1494+
wp_rsa_import_increment_crt_counts(index, &primes, &exps,
1495+
&coeffs);
12761496
break;
12771497
}
12781498
}
@@ -1284,21 +1504,30 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[],
12841504
}
12851505

12861506
/* Read the value into the rsa struct */
1287-
if (ok) {
1507+
if (ok && p != n) {
12881508
mp = (mp_int*)(((byte*)&rsa->key) + wp_rsa_offset[index]);
1289-
if (!wp_mp_read_unsigned_bin_le(mp, p->data, p->data_size)) {
1290-
WOLFPROV_MSG(WP_LOG_COMP_RSA,
1291-
"Failed to read %s from parameters", p->key);
1292-
ok = 0;
1293-
}
1509+
bits = wp_rsa_import_store_unsigned(mp, p);
1510+
ok = wp_rsa_import_check_bits(bits, nbits);
12941511
}
12951512
}
1513+
1514+
/* Verify the received RSA CRT parameters */
1515+
if (ok) {
1516+
ok = wp_rsa_import_verify_crt(primes, exps, coeffs);
1517+
}
1518+
}
1519+
else if (ok && d == NULL) {
1520+
mp = &rsa->key.e;
1521+
bits = wp_rsa_import_store_unsigned(mp, e);
1522+
ok = wp_rsa_import_check_bits(bits, nbits);
12961523
}
12971524

12981525
WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
12991526
return ok;
13001527
}
13011528

1529+
#undef wp_rsa_import_check_bits
1530+
13021531
/**
13031532
* Import the key into RSA key object from parameters.
13041533
*

0 commit comments

Comments
 (0)