Skip to content

Commit 2295f64

Browse files
committed
openssl compat: cut EVP, EVP_PKEY and PEM overhead, and fix defects found
Performance: - Size WOLFSSL_SHA3_CTX's holder in pointers, not bytes. It reserved sizeof(wc_Sha3) pointers for a 440 byte object, so WOLFSSL_Hasher goes 3520 -> 448 bytes and WOLFSSL_EVP_MD_CTX 3552 -> 944. A static assert now catches the same mistake. - Resolve digests and ciphers by table pointer before comparing names, and return the table's own pointer from the accessors. EVP_sha256() 47.4 -> 1.2ns, EVP_MD_type 64.7 -> 4.3ns, EVP_CIPHER_nid 101.2 -> 8.1ns. - Copy only the fields past the hash union in EVP_MD_CTX_copy_ex, and leave the cipher union alone in EVP_CIPHER_CTX_init. - Set up an EVP_PKEY's RNG, an RSA key's blinding RNG and an EVP_PKEY's DER encoding on first use rather than at creation. Seeding a DRBG cost more than everything else about making a key put together. EVP_PKEY_new + free 13366 -> 34ns, RSA_new + free 13748 -> 230ns, EVP_PKEY_set1_RSA 13994 -> 45ns. Certificate parsing roughly halves as a result: d2i_X509 45978 -> 17198ns, X509_dup 45945 -> 17116ns, both now faster than OpenSSL. set1_DSA still encodes immediately, as WOLFSSL_DSA is not reference counted and the pkey only borrows the caller's key. - Keep the decoded public key on the X509 and hand out a reference, as OpenSSL does. X509_get_pubkey + free 27692 -> 9.8ns, against 9.6ns. - Give public key operations the shared global RNG instead of seeding one per call, where the instance locks itself or the build is single threaded. WOLFSSL_PK_LOCAL_RNG forces an RNG per call. EVP_DigestSign with ECDSA P-256 32291 -> 17008ns, against 18852ns. - Encode base64 without a call per output character, and hand whole blocks to Base64_Encode() rather than one 48 byte line at a time. EVP_EncodeUpdate 6446 -> 1855ns per 4KB. Skip the call into Base64_SkipNewline() for characters needing no skipping: EVP_DecodeUpdate 37059 -> 16194ns. What remains there is the constant time character decode, which is deliberate; wc_PemToDer() already picks the table decoder by PEM type. - Search cipher_tbl before the alias table in EVP_get_cipherbyname(), since over half the aliases only spell a name already there in another case, and compare without the platform's strcasecmp: 330 -> 67ns. The comparison is ASCII only, so it no longer follows the locale and works on platforms whose XSTRCASECMP is strcmp. - Find the PEM footer in a memory BIO's buffer and read up to it, rather than reading a byte at a time through the whole input. Loading a certificate from a file that carries a text dump alongside it 100311 -> 23301ns, against 33006ns. Fixes: - AES-CCM through EVP now behaves as OpenSSL's does: the payload length comes from the first Update() carrying payload, AAD before the length is refused, and Final() has nothing left to do. Of 376 observables compared against OpenSSL - the tag and IV length matrices, the defaults, length inference over 48 sizes, and an encrypt/decrypt matrix with the tag then corrupted - 375 agree. The one that differs, EVP_CIPHER_iv_length() reporting CCM_NONCE_MIN_SZ where OpenSSL reports 12, is unchanged by this work. - EVP_get_cipherbyname(NULL) and EVP_get_digestbyname(NULL) dereferenced the name; OpenSSL returns NULL. - EVP_EncodeUpdate() wrote the block it was holding and then wrote over it, so a caller feeding data in pieces got wrong bytes, including bytes never written. - EVP_PKEY_keygen() on DH read the parameters after replacing the key object they came from, which is the same object when a caller passes its own key back in. - EVP_PKEY_set1_EC_KEY() with an empty EC_KEY now succeeds. It reported failure only as a side effect of encoding eagerly; OpenSSL returns 1 and the empty key is caught when the encoding is asked for. - Remove three GMULT macros naming functions that do not exist. Portability: - Add XMEMCHR, with wc_memchr() for builds naming their own string functions through STRING_USER that have no memchr among them. Tests: - Known answer vectors for the base64 encoders, generated independently of wolfSSL. testwolfcrypt passed with the encoder deliberately corrupted, as it only checked return codes. - EVP_EncodeUpdate() over every chunking of ten sizes straddling the 48 byte block, and PEM_read_bio_X509() reading several certificates from one BIO while leaving nothing behind after one. - EVP_get_cipherbyname() over canonical names, alternative spellings, every casing of each, and NULL. - One RSA private key operation per freshly decoded key: sharing a key lets the first operation provide the blinding RNG and hides a missing one in the rest. Plus EVP_PKEY_set1_DSA() followed by the caller releasing its key. - The reference counting behind the cached X509 public key: outliving the certificate and the caller, replacement, and duplication. - wc_memchr(), guarded by USE_WOLF_MEMCHR so it runs where that function is built.
1 parent c02cf6c commit 2295f64

39 files changed

Lines changed: 3632 additions & 1031 deletions

‎src/internal.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5750,6 +5750,9 @@ static void FreeX509Contents(WOLFSSL_X509* x509)
57505750
XFREE(x509->pubKey.buffer, x509->heap, DYNAMIC_TYPE_PUBLIC_KEY);
57515751
x509->pubKey.buffer = NULL;
57525752
}
5753+
#ifdef WOLFSSL_X509_PUBKEY_CACHE
5754+
wolfssl_x509_free_pubkey_evp(x509);
5755+
#endif
57535756
FreeDer(&x509->derCert);
57545757
XFREE(x509->sig.buffer, x509->heap, DYNAMIC_TYPE_SIGNATURE);
57555758
x509->sig.buffer = NULL;

‎src/pk.c‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6292,6 +6292,10 @@ int wolfSSL_PEM_write_bio_PrivateKey(WOLFSSL_BIO* bio, WOLFSSL_EVP_PKEY* key,
62926292
#ifndef NO_DH
62936293
case WC_EVP_PKEY_DH:
62946294
/* Write using generic API with DH type. */
6295+
if (EvpPkeyEnsureDer(key) != WOLFSSL_SUCCESS) {
6296+
ret = WOLFSSL_FAILURE;
6297+
break;
6298+
}
62956299
ret = der_write_to_bio_as_pem((byte*)key->pkey.ptr,
62966300
key->pkey_sz, bio, DH_PRIVATEKEY_TYPE);
62976301
break;
@@ -7469,6 +7473,8 @@ int pkcs8_encrypt(WOLFSSL_EVP_PKEY* pkey,
74697473
#endif /* WOLFSSL_HAVE_MLDSA */
74707474
{
74717475
/* Encrypt private into buffer. */
7476+
if (EvpPkeyEnsureDer(pkey) != WOLFSSL_SUCCESS)
7477+
return WOLFSSL_FAILURE;
74727478
ret = TraditionalEnc(
74737479
(byte*)pkey->pkey.ptr + pkey->pkcs8HeaderSz,
74747480
(word32)pkey->pkey_sz - pkey->pkcs8HeaderSz,
@@ -7535,6 +7541,9 @@ int pkcs8_encode(WOLFSSL_EVP_PKEY* pkey, byte* key, word32* keySz)
75357541
if (keySz == NULL)
75367542
return BAD_FUNC_ARG;
75377543

7544+
if (EvpPkeyEnsureDer(pkey) != WOLFSSL_SUCCESS)
7545+
return BAD_FUNC_ARG;
7546+
75387547
if (key == NULL) {
75397548
*keySz = (word32)pkey->pkey_sz;
75407549
return LENGTH_ONLY_E;
@@ -7614,6 +7623,9 @@ int pkcs8_encode(WOLFSSL_EVP_PKEY* pkey, byte* key, word32* keySz)
76147623
ret = NOT_COMPILED_IN;
76157624
}
76167625

7626+
if ((ret >= 0) && (EvpPkeyEnsureDer(pkey) != WOLFSSL_SUCCESS)) {
7627+
ret = BAD_FUNC_ARG;
7628+
}
76177629
if (ret >= 0) {
76187630
/* Encode private key in PKCS#8 format. */
76197631
ret = wc_CreatePKCS8Key(key, keySz, (byte*)pkey->pkey.ptr +

‎src/pk_rsa.c‎

Lines changed: 142 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,130 @@ void wolfSSL_RSA_free(WOLFSSL_RSA* rsa)
236236
* @return RSA key on success.
237237
* @return NULL on failure.
238238
*/
239+
#if !defined(HAVE_FIPS) && defined(WC_RSA_BLINDING)
240+
/* Serialises the one time set up of a key's blinding RNG, so that two threads
241+
* arriving at a private key operation together cannot both create one and
242+
* leave the loser's leaked. */
243+
static wolfSSL_Mutex rsaRngMutex
244+
WOLFSSL_MUTEX_INITIALIZER_CLAUSE(rsaRngMutex);
245+
#ifdef WOLFSSL_HAVE_RSA_RNG_MUTEX
246+
static int rsaRngMutexValid = 0;
247+
248+
/* Create the lock serialising blinding RNG set up.
249+
*
250+
* Called from wolfSSL_Init(), which is already serialised against itself, so
251+
* that no caller has to create the mutex on first use. */
252+
int wolfssl_rsa_rng_mutex_init(void)
253+
{
254+
if (rsaRngMutexValid == 0) {
255+
if (wc_InitMutex(&rsaRngMutex) != 0) {
256+
WOLFSSL_MSG("Bad Init Mutex rsa rng");
257+
return BAD_MUTEX_E;
258+
}
259+
rsaRngMutexValid = 1;
260+
}
261+
return 0;
262+
}
263+
264+
/* Destroy the lock serialising blinding RNG set up. Called from
265+
* wolfSSL_Cleanup(). */
266+
void wolfssl_rsa_rng_mutex_free(void)
267+
{
268+
if (rsaRngMutexValid == 1) {
269+
(void)wc_FreeMutex(&rsaRngMutex);
270+
rsaRngMutexValid = 0;
271+
}
272+
}
273+
#endif /* WOLFSSL_HAVE_RSA_RNG_MUTEX */
274+
#endif /* !HAVE_FIPS && WC_RSA_BLINDING */
275+
276+
/* Provide the RNG that blinds private key operations, creating it if this is
277+
* the first time it has been needed.
278+
*
279+
* Deferred rather than done in wolfSSL_RSA_new_ex() because seeding a DRBG
280+
* costs more than everything else about creating the key put together, and
281+
* public key work - verifying a signature, carrying a certificate's public
282+
* key - never reaches this.
283+
*
284+
* @param [in, out] rsa RSA key.
285+
* @return The RNG set on the wolfCrypt key on success.
286+
* @return NULL when no RNG could be provided.
287+
*/
288+
WC_RNG* wolfssl_rsa_ensure_rng(WOLFSSL_RSA* rsa)
289+
{
290+
#if !defined(HAVE_FIPS) && defined(WC_RSA_BLINDING)
291+
WC_RNG* rng;
292+
int haveMutex;
293+
294+
if ((rsa == NULL) || (rsa->internal == NULL)) {
295+
return NULL;
296+
}
297+
298+
/* rngInited is read under the lock, not before it: an unlocked read would
299+
* race with another thread publishing the flag, and it shares a storage
300+
* unit with the other bits of the key. Taking the lock on the already set
301+
* up path costs nothing beside the private key operation that follows. */
302+
#ifdef WOLFSSL_HAVE_RSA_RNG_MUTEX
303+
/* wolfSSL_Init() creates it; without that there is nothing to lock, and
304+
* the same single threaded assumption applies as to the global RNG. */
305+
haveMutex = (rsaRngMutexValid == 1);
306+
#else
307+
haveMutex = 1;
308+
#endif
309+
if (haveMutex && (wc_LockMutex(&rsaRngMutex) != 0)) {
310+
WOLFSSL_MSG("Bad Lock Mutex rsa rng");
311+
return NULL;
312+
}
313+
314+
/* Already provided; hand back what the wolfCrypt key is holding. */
315+
if (rsa->rngInited) {
316+
rng = ((RsaKey*)rsa->internal)->rng;
317+
if (haveMutex) {
318+
wc_UnLockMutex(&rsaRngMutex);
319+
}
320+
return rng;
321+
}
322+
323+
/* Create a local RNG. */
324+
rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), rsa->heap, DYNAMIC_TYPE_RNG);
325+
if ((rng != NULL) && (wc_InitRng_ex(rng, rsa->heap, rsa->devId) != 0)) {
326+
WOLFSSL_MSG("InitRng failure, attempting to use global RNG");
327+
XFREE(rng, rsa->heap, DYNAMIC_TYPE_RNG);
328+
rng = NULL;
329+
}
330+
331+
rsa->ownRng = 1;
332+
if (rng == NULL) {
333+
/* Get the wolfSSL global RNG - not thread safe. */
334+
rng = wolfssl_get_global_rng();
335+
rsa->ownRng = 0;
336+
}
337+
if (rng == NULL) {
338+
/* Couldn't create global either. */
339+
WOLFSSL_ERROR_MSG("wolfSSL_RSA no WC_RNG for blinding");
340+
if (haveMutex) {
341+
wc_UnLockMutex(&rsaRngMutex);
342+
}
343+
return NULL;
344+
}
345+
346+
/* Set the local or global RNG into the wolfCrypt RSA key. */
347+
(void)wc_RsaSetRNG((RsaKey*)rsa->internal, rng);
348+
/* Won't fail as key and rng are not NULL. */
349+
rsa->rngInited = 1;
350+
351+
if (haveMutex) {
352+
wc_UnLockMutex(&rsaRngMutex);
353+
}
354+
355+
return rng;
356+
#else
357+
(void)rsa;
358+
return NULL;
359+
#endif /* !HAVE_FIPS && WC_RSA_BLINDING */
360+
}
361+
362+
239363
WOLFSSL_RSA* wolfSSL_RSA_new_ex(void* heap, int devId)
240364
{
241365
WOLFSSL_RSA* rsa = NULL;
@@ -285,37 +409,14 @@ WOLFSSL_RSA* wolfSSL_RSA_new_ex(void* heap, int devId)
285409
rsaKeyInited = 1;
286410
}
287411
}
288-
#if !defined(HAVE_FIPS) && defined(WC_RSA_BLINDING)
289412
if (!err) {
290-
WC_RNG* rng;
413+
/* The RNG used to blind private key operations is not set up here.
414+
* Seeding it dominates the cost of creating an RSA object, and a key
415+
* that is only ever used to verify a signature or to carry a public
416+
* key never needs it. wolfssl_rsa_ensure_rng() provides it on the
417+
* first private key operation instead. */
418+
rsa->devId = devId;
291419

292-
/* Create a local RNG. */
293-
rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), heap, DYNAMIC_TYPE_RNG);
294-
if ((rng != NULL) && (wc_InitRng_ex(rng, heap, devId) != 0)) {
295-
WOLFSSL_MSG("InitRng failure, attempting to use global RNG");
296-
XFREE(rng, heap, DYNAMIC_TYPE_RNG);
297-
rng = NULL;
298-
}
299-
300-
rsa->ownRng = 1;
301-
if (rng == NULL) {
302-
/* Get the wolfSSL global RNG - not thread safe. */
303-
rng = wolfssl_get_global_rng();
304-
rsa->ownRng = 0;
305-
}
306-
if (rng == NULL) {
307-
/* Couldn't create global either. */
308-
WOLFSSL_ERROR_MSG("wolfSSL_RSA_new no WC_RNG for blinding");
309-
err = 1;
310-
}
311-
else {
312-
/* Set the local or global RNG into the wolfCrypt RSA key. */
313-
(void)wc_RsaSetRNG(key, rng);
314-
/* Won't fail as key and rng are not NULL. */
315-
}
316-
}
317-
#endif /* !HAVE_FIPS && WC_RSA_BLINDING */
318-
if (!err) {
319420
/* Set wolfCrypt RSA key into RSA key. */
320421
rsa->internal = key;
321422
/* Data from external RSA key has not been set into internal one. */
@@ -2419,8 +2520,9 @@ WC_RNG* WOLFSSL_RSA_GetRNG(WOLFSSL_RSA* rsa, WC_RNG** tmpRng, int* initTmpRng)
24192520
*initTmpRng = 0;
24202521

24212522
#if !defined(HAVE_FIPS) && defined(WC_RSA_BLINDING)
2422-
/* Use wolfCrypt RSA key's RNG if available/set. */
2423-
rng = ((RsaKey*)rsa->internal)->rng;
2523+
/* Use wolfCrypt RSA key's RNG, setting it up if this is its first
2524+
* use. */
2525+
rng = wolfssl_rsa_ensure_rng(rsa);
24242526
#endif
24252527
}
24262528
if ((!err) && (rng == NULL) && (tmpRng != NULL)) {
@@ -3638,6 +3740,15 @@ int wolfSSL_RSA_private_decrypt(int len, const unsigned char* from,
36383740
}
36393741
}
36403742

3743+
/* This takes the RNG off the wolfCrypt key rather than being passed one,
3744+
* so the key's blinding RNG has to be in place before the call. */
3745+
#if !defined(HAVE_FIPS) && defined(WC_RSA_BLINDING)
3746+
if ((ret == 0) && (wolfssl_rsa_ensure_rng(rsa) == NULL)) {
3747+
WOLFSSL_ERROR_MSG("No RNG for RSA blinding");
3748+
ret = WOLFSSL_FATAL_ERROR;
3749+
}
3750+
#endif
3751+
36413752
if (ret == 0) {
36423753
/* Use wolfCrypt to private-decrypt with RSA key.
36433754
* Size of 'to' buffer must be size of RSA key */

‎src/ssl.c‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,22 @@ WC_RNG* wolfssl_make_global_rng(void)
347347
* for RSA, DH, ECC and DSA for BN. */
348348
WC_RNG* wolfssl_make_rng(WC_RNG* rng, int* local);
349349

350+
/* Whether the shared global RNG is handed to the operations that need one, in
351+
* preference to giving each call an RNG of its own.
352+
*
353+
* Setting up an RNG seeds a DRBG, which costs more than many of the operations
354+
* that ask for one: an ECDSA signature spends more time on it than on the
355+
* signature. Sharing the one the library already has avoids that, and is only
356+
* done where it is safe to share: either the instance takes its own lock
357+
* whenever it is used, or there are no other threads to share it with.
358+
*
359+
* Define WOLFSSL_PK_LOCAL_RNG to go back to an RNG per call regardless.
360+
*/
361+
#if !defined(WOLFSSL_PK_LOCAL_RNG) && \
362+
(defined(WC_RNG_HAVE_AUTO_LOCK) || defined(SINGLE_THREADED))
363+
#define WOLFSSL_PK_GLOBAL_RNG
364+
#endif
365+
350366
/* Make a random number generator or get global if possible.
351367
*
352368
* Global may not be available and NULL will be returned.
@@ -361,7 +377,20 @@ WC_RNG* wolfssl_make_rng(WC_RNG* rng, int* local)
361377
WC_RNG* ret = NULL;
362378
#ifdef WOLFSSL_SMALL_STACK
363379
int freeRng = 0;
380+
#endif
364381

382+
#ifdef WOLFSSL_PK_GLOBAL_RNG
383+
/* Hand back the shared one, which is already seeded. */
384+
ret = wolfssl_make_global_rng();
385+
if (ret != NULL) {
386+
/* Not the caller's to free. */
387+
*local = 0;
388+
return ret;
389+
}
390+
/* No global to be had, so fall through and make one. */
391+
#endif
392+
393+
#ifdef WOLFSSL_SMALL_STACK
365394
/* Allocate RNG object . */
366395
if (rng == NULL) {
367396
rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), NULL, DYNAMIC_TYPE_RNG);
@@ -2540,6 +2569,20 @@ int wolfSSL_Init(void)
25402569
}
25412570
#endif
25422571

2572+
/* The compatibility layer's caches each need a global mutex, made here
2573+
* because a mutex created on first use cannot be made safe. */
2574+
#ifdef WOLFSSL_HAVE_X509_PUBKEY_MUTEX
2575+
if ((ret == WOLFSSL_SUCCESS) &&
2576+
(wolfssl_x509_pubkey_mutex_init() != 0)) {
2577+
ret = BAD_MUTEX_E;
2578+
}
2579+
#endif
2580+
#ifdef WOLFSSL_HAVE_RSA_RNG_MUTEX
2581+
if ((ret == WOLFSSL_SUCCESS) && (wolfssl_rsa_rng_mutex_init() != 0)) {
2582+
ret = BAD_MUTEX_E;
2583+
}
2584+
#endif
2585+
25432586
#ifdef WC_RNG_SEED_CB
25442587
wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT);
25452588
#endif
@@ -3831,6 +3874,15 @@ int wolfSSL_Cleanup(void)
38313874
#endif
38323875
#endif
38333876

3877+
/* Outside the global RNG block above: wolfSSL_Init() creates these two
3878+
* whatever that is configured to. */
3879+
#ifdef WOLFSSL_HAVE_X509_PUBKEY_MUTEX
3880+
wolfssl_x509_pubkey_mutex_free();
3881+
#endif
3882+
#ifdef WOLFSSL_HAVE_RSA_RNG_MUTEX
3883+
wolfssl_rsa_rng_mutex_free();
3884+
#endif
3885+
38343886
#ifdef HAVE_EX_DATA_CRYPTO
38353887
crypto_ex_cb_free(crypto_ex_cb_ctx_session);
38363888
crypto_ex_cb_ctx_session = NULL;

‎src/ssl_crypto.c‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,15 @@ int wolfSSL_SHA3_224_Init(WOLFSSL_SHA3_224_CTX* sha3_224)
801801
{
802802
/* Ensure WOLFSSL_SHA3_224_CTX is big enough for wolfCrypt wc_Sha3. */
803803
WOLFSSL_ASSERT_SIZEOF_GE(WOLFSSL_SHA3_224_CTX, wc_Sha3);
804+
/* And no bigger than it needs to be. The holder is an array of pointers,
805+
* so a size in bytes used as the element count inflates it eightfold -
806+
* which nothing else notices, because it is still big enough. This
807+
* context is the largest member of the union in WOLFSSL_EVP_MD_CTX, so
808+
* the cost lands on every digest context in the compatibility layer.
809+
* Measured on the holder rather than the context, because ALIGN16 rounds
810+
* the context's size up and that tail padding is not waste to report. */
811+
wc_static_assert(sizeof(((WOLFSSL_SHA3_224_CTX*)0)->holder) <
812+
sizeof(wc_Sha3) + sizeof(void*));
804813

805814
WOLFSSL_ENTER("SHA3_224_Init");
806815

0 commit comments

Comments
 (0)