Skip to content

Commit a2d0f4f

Browse files
authored
Merge pull request #339 from sebastian-carpenter/rsa-validate-tests
rsa key validation test and additions to rsa fromdata test
2 parents 6eac178 + 7e485a4 commit a2d0f4f

4 files changed

Lines changed: 731 additions & 11 deletions

File tree

src/wp_rsa_kmgmt.c

Lines changed: 281 additions & 11 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
*
@@ -1214,26 +1242,229 @@ static int wp_rsa_validate(const wp_Rsa* rsa, int selection, int checkType)
12141242
WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_CheckRsaKey", rc);
12151243
ok = 0;
12161244
}
1245+
1246+
/* wc_CheckRsaKey runs the private key operation and only bounds-checks
1247+
* d (d < n). Check d*e = 1 mod lcm(p-1,q-1) to match OSSL */
1248+
if (ok && !mp_iszero((mp_int*)&rsa->key.p) &&
1249+
!mp_iszero((mp_int*)&rsa->key.q)) {
1250+
mp_int m; /* p-1 then q-1 */
1251+
mp_int r; /* d*e mod m */
1252+
1253+
if (mp_init(&m) != MP_OKAY) {
1254+
ok = 0;
1255+
}
1256+
else if (mp_init(&r) != MP_OKAY) {
1257+
mp_clear(&m);
1258+
ok = 0;
1259+
}
1260+
else {
1261+
if (mp_sub_d((mp_int*)&rsa->key.p, 1, &m) != MP_OKAY ||
1262+
mp_mulmod((mp_int*)&rsa->key.d, (mp_int*)&rsa->key.e,
1263+
&m, &r) != MP_OKAY || !mp_isone(&r)) {
1264+
ok = 0;
1265+
}
1266+
else if (mp_sub_d((mp_int*)&rsa->key.q, 1, &m) != MP_OKAY ||
1267+
mp_mulmod((mp_int*)&rsa->key.d, (mp_int*)&rsa->key.e,
1268+
&m, &r) != MP_OKAY || !mp_isone(&r)) {
1269+
ok = 0;
1270+
}
1271+
mp_forcezero(&r);
1272+
mp_forcezero(&m);
1273+
mp_clear(&r);
1274+
mp_clear(&m);
1275+
}
1276+
}
12171277
}
12181278
else
12191279
#endif
12201280
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)) {
1281+
if (mp_iszero((mp_int*)&rsa->key.d) ||
1282+
(mp_cmp((mp_int*)&rsa->key.d, (mp_int*)&rsa->key.n) != MP_LT)) {
12231283
ok = 0;
12241284
}
12251285
}
12261286
else if (checkPub) {
1227-
if (mp_iseven(&rsa->key.e) || mp_iszero((mp_int*)&rsa->key.e) ||
1228-
mp_isone(&rsa->key.e)) {
1287+
int nbits = mp_count_bits((mp_int*)&rsa->key.n);
1288+
mp_int res;
1289+
1290+
if (mp_iszero((mp_int*)&rsa->key.e) || mp_iszero((mp_int*)&rsa->key.n) ||
1291+
mp_isone((mp_int*)&rsa->key.e) || mp_isone((mp_int*)&rsa->key.n) ||
1292+
mp_iseven((mp_int*)&rsa->key.e) || mp_iseven((mp_int*)&rsa->key.n)) {
1293+
ok = 0;
1294+
}
1295+
1296+
if (ok && nbits > OPENSSL_RSA_MAX_MODULUS_BITS) {
1297+
ok = 0;
1298+
}
1299+
1300+
#ifdef HAVE_FIPS
1301+
if (ok && (wolfProvider_GetFipsChecks() & WP_FIPS_CHECK_RSA_KEY_SIZE)) {
1302+
/* n must have at least 112 bits of security, and
1303+
* 2^16 < e < 2^256 */
1304+
int ebits = mp_count_bits((mp_int*)&rsa->key.e);
1305+
if (nbits < 2048 || ebits <= 16 || ebits >= 257) {
1306+
ok = 0;
1307+
}
1308+
}
1309+
#endif
1310+
1311+
/* Reject a modulus that shares a small prime factor */
1312+
if (ok && mp_init(&res) != MP_OKAY) {
1313+
ok = 0;
1314+
}
1315+
else if (ok) {
1316+
unsigned int prime;
1317+
for(prime = 0; prime < VALIDATE_PRIMES_SIZE; prime++) {
1318+
if (mp_set_int(&res, validate_primes[prime]) != MP_OKAY ||
1319+
mp_mod((mp_int*)&rsa->key.n, &res, &res) != MP_OKAY ||
1320+
mp_iszero(&res)) {
1321+
ok = 0;
1322+
break;
1323+
}
1324+
}
1325+
1326+
mp_clear(&res);
1327+
}
1328+
1329+
#if (!defined(WOLFSSL_SP_MATH) && !defined(WOLFSSL_SP_MATH_ALL)) || \
1330+
defined(WOLFSSL_SP_PRIME_GEN)
1331+
/* Reject prime n since it is not composite.
1332+
* Note this does not catch a prime power (p^k), which OSSL
1333+
* also rejects. */
1334+
if (ok) {
1335+
int isPrime = MP_NO;
1336+
if (mp_prime_is_prime((mp_int*)&rsa->key.n, 5, &isPrime) != MP_OKAY ||
1337+
isPrime == MP_YES) {
1338+
ok = 0;
1339+
}
1340+
}
1341+
#endif
1342+
}
1343+
1344+
WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
1345+
return ok;
1346+
}
1347+
1348+
/**
1349+
* Copy an unsigned value from an OSSL param into a provided RSA key parameter.
1350+
* Negative values result in an error.
1351+
*
1352+
* @param [out] mp RSA key parameter.
1353+
* @param [in] param Parameter to copy value from.
1354+
* @return Size of mp in bits on success.
1355+
* @return -1 on failure.
1356+
*/
1357+
static int wp_rsa_import_store_unsigned(mp_int* mp, const OSSL_PARAM* param)
1358+
{
1359+
int ok;
1360+
int bits = -1;
1361+
const unsigned char* data;
1362+
1363+
WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_import_store_unsigned");
1364+
1365+
#if OPENSSL_VERSION_NUMBER <= 0x30100080L
1366+
ok = param->data != NULL && param->data_type == OSSL_PARAM_UNSIGNED_INTEGER;
1367+
#else
1368+
ok = param->data != NULL && (param->data_type == OSSL_PARAM_INTEGER ||
1369+
param->data_type == OSSL_PARAM_UNSIGNED_INTEGER);
1370+
#endif /* OPENSSL_VERSION_NUMBER <= 3.1.8 */
1371+
1372+
if (ok && !wp_mp_read_unsigned_bin_le(mp, param->data, param->data_size)) {
1373+
WOLFPROV_MSG(WP_LOG_COMP_RSA,
1374+
"Failed to read %s from parameters", param->key);
1375+
ok = 0;
1376+
}
1377+
1378+
if (ok) {
1379+
bits = mp_count_bits(mp);
1380+
}
1381+
1382+
/* OSSL accepts negative values for these params, but this doesn't work well
1383+
* in wolfProvider so reject it. */
1384+
if (ok && param->data_type == OSSL_PARAM_INTEGER) {
1385+
data = (const unsigned char*)param->data;
1386+
/* Assume little-endian when determining sign */
1387+
if (param->data_size > 0 && (data[param->data_size - 1] & 0x80) != 0) {
1388+
WOLFPROV_MSG(WP_LOG_COMP_RSA, "Negative value for %s rejected",
1389+
param->key);
12291390
ok = 0;
12301391
}
12311392
}
12321393

12331394
WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
1395+
return ok ? bits : -1;
1396+
}
1397+
1398+
/**
1399+
* Based on the index into wp_rsa_param_key, increment the appropriate counter.
1400+
*
1401+
* @param [in] index Index associated with wp_rsa_param_key.
1402+
* @param [out] primes Count of prime parameters.
1403+
* @param [out] exps Count of exponent parameters.
1404+
* @param [out] coeffs Count of coefficient parameters.
1405+
*/
1406+
static void wp_rsa_import_increment_crt_counts(int index, int *primes,
1407+
int *exps, int *coeffs)
1408+
{
1409+
if (index >= WP_RSA_PARAM_KEY_FACTOR_INDEX1 &&
1410+
index <= WP_RSA_PARAM_KEY_FACTOR_INDEX2) {
1411+
*primes += 1;
1412+
}
1413+
else if (index >= WP_RSA_PARAM_KEY_EXPONENT_INDEX1 &&
1414+
index <= WP_RSA_PARAM_KEY_EXPONENT_INDEX2) {
1415+
*exps += 1;
1416+
}
1417+
else if (index == WP_RSA_PARAM_KEY_COEFFICIENT_INDEX) {
1418+
*coeffs += 1;
1419+
}
1420+
}
1421+
1422+
/**
1423+
* Verify the RSA CRT parameters supplied for a private key import are complete.
1424+
*
1425+
* @param [in] primes Count of prime factor parameters provided.
1426+
* @param [in] exps Count of CRT exponent parameters provided.
1427+
* @param [in] coeffs Count of CRT coefficient parameters provided.
1428+
* @return 1 when the CRT parameters are acceptable
1429+
* @return 0 when the provided factors/CRT parameters are incomplete or
1430+
* unsupported.
1431+
*/
1432+
static int wp_rsa_import_verify_crt(int primes, int exps, int coeffs)
1433+
{
1434+
int ok = 1;
1435+
1436+
(void)exps;
1437+
(void)coeffs;
1438+
1439+
if (primes > 0) {
1440+
#if (OPENSSL_VERSION_NUMBER < 0x30100040L && OPENSSL_VERSION_NUMBER > 0x30000120L) \
1441+
|| (OPENSSL_VERSION_NUMBER < 0x300000C0L)
1442+
if (primes < 2 || primes != exps || primes != coeffs + 1) {
1443+
#else
1444+
if (primes < 2) {
1445+
#endif /* (Ver < 3.1.4 && Ver > 3.0.18) || (Ver < 3.0.12) */
1446+
WOLFPROV_MSG(WP_LOG_COMP_RSA,
1447+
"RSA factors provided but CRT parameters incomplete");
1448+
ok = 0;
1449+
}
1450+
/* TODO: multi-prime checks */
1451+
else if (primes > 2) {
1452+
WOLFPROV_MSG(WP_LOG_COMP_RSA, "RSA multi-prime import failed");
1453+
ok = 0;
1454+
}
1455+
}
1456+
12341457
return ok;
12351458
}
12361459

1460+
#if OPENSSL_VERSION_NUMBER >= 0x30400000L
1461+
#define wp_rsa_import_check_bits(bits, nbits) \
1462+
((bits) != -1 && (bits) <= (nbits))
1463+
#else
1464+
#define wp_rsa_import_check_bits(bits, nbits) \
1465+
((bits) != -1)
1466+
#endif /* OPENSSL_VERSION_NUMBER >= 3.4.0 */
1467+
12371468
/**
12381469
* Import the key data into RSA key object from parameters.
12391470
*
@@ -1251,20 +1482,39 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[],
12511482
int cnt = 0;
12521483
mp_int* mp = NULL;
12531484
const OSSL_PARAM* p = NULL;
1485+
const OSSL_PARAM* n;
1486+
const OSSL_PARAM* e;
1487+
const OSSL_PARAM* d = NULL;
1488+
int bits = -1;
1489+
int nbits = -1;
1490+
int primes = 0;
1491+
int exps = 0;
1492+
int coeffs = 0;
12541493

12551494
WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsa_import_key_data");
12561495

12571496
/* N and E params are the only ones required by OSSL, so match that.
12581497
* 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) {
1498+
if ((n = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N)) == NULL ||
1499+
n->data == NULL ||
1500+
(e = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E)) == NULL ||
1501+
e->data == NULL) {
12611502
WOLFPROV_MSG(WP_LOG_COMP_RSA, "Param N or E is missing");
12621503
ok = 0;
12631504
}
1505+
if (ok && priv) {
1506+
d = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D);
1507+
}
12641508

12651509
if (ok) {
1266-
cnt = wp_params_count(params);
12671510
rsa->key.type = priv ? RSA_PRIVATE : RSA_PUBLIC;
1511+
mp = &rsa->key.n;
1512+
nbits = wp_rsa_import_store_unsigned(mp, n);
1513+
ok = nbits != -1;
1514+
}
1515+
1516+
if (ok && d != NULL) {
1517+
cnt = wp_params_count(params);
12681518

12691519
for (i = 0; i < cnt; i++) {
12701520
/* Use the table to look up the offset in the rsa struct */
@@ -1273,6 +1523,8 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[],
12731523
for (j = 0; j < (int)ARRAY_SIZE(wp_rsa_param_key); j++) {
12741524
if (XSTRCMP(p->key, wp_rsa_param_key[j]) == 0) {
12751525
index = j;
1526+
wp_rsa_import_increment_crt_counts(index, &primes, &exps,
1527+
&coeffs);
12761528
break;
12771529
}
12781530
}
@@ -1284,21 +1536,39 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[],
12841536
}
12851537

12861538
/* Read the value into the rsa struct */
1287-
if (ok) {
1539+
if (ok && p != n) {
12881540
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);
1541+
bits = wp_rsa_import_store_unsigned(mp, p);
1542+
ok = wp_rsa_import_check_bits(bits, nbits);
1543+
1544+
/* Reject a provided CRT parameter (dP, dQ, u) that is zero.
1545+
* The key validation could otherwise pass with a 0 param */
1546+
if (ok && (((index >= WP_RSA_PARAM_KEY_EXPONENT_INDEX1 &&
1547+
index <= WP_RSA_PARAM_KEY_EXPONENT_INDEX2) ||
1548+
index == WP_RSA_PARAM_KEY_COEFFICIENT_INDEX) &&
1549+
mp_iszero(mp))) {
12921550
ok = 0;
12931551
}
12941552
}
12951553
}
1554+
1555+
/* Verify the received RSA CRT parameters */
1556+
if (ok) {
1557+
ok = wp_rsa_import_verify_crt(primes, exps, coeffs);
1558+
}
1559+
}
1560+
else if (ok && d == NULL) {
1561+
mp = &rsa->key.e;
1562+
bits = wp_rsa_import_store_unsigned(mp, e);
1563+
ok = wp_rsa_import_check_bits(bits, nbits);
12961564
}
12971565

12981566
WOLFPROV_LEAVE(WP_LOG_COMP_RSA, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
12991567
return ok;
13001568
}
13011569

1570+
#undef wp_rsa_import_check_bits
1571+
13021572
/**
13031573
* Import the key into RSA key object from parameters.
13041574
*

0 commit comments

Comments
 (0)