Replaces the cryptographic primitive implementations inside OpenSSL 1.1.1 with wolfCrypt (from wolfSSL), leaving the OpenSSL protocol machinery — TLS state machine, ASN.1, X.509, BIO, record layer — unchanged.
The result is a drop-in libcrypto.so.1.1 / libssl.so.1.1 / openssl
binary where every crypto operation is handled by wolfCrypt underneath the
standard OpenSSL 1.1.1 public API.
The standard way to swap OpenSSL's crypto for an alternative is the
ENGINE API.
wolfSSL ships a full
wolfEngine that works well for
applications written to use only the high-level EVP layer (EVP_DigestInit,
EVP_EncryptInit, etc.).
The ENGINE dispatch path is bypassed, however, whenever an application calls OpenSSL's low-level API directly:
SHA256_Init(&ctx); // bypasses ENGINE
RSA_private_encrypt(...); // bypasses ENGINE
AES_cbc_encrypt(...); // bypasses ENGINEApplications that are not disciplined about the EVP boundary — legacy codebases, generated code, third-party libraries — will have a significant fraction of their crypto operations never reach the ENGINE at all. A link-time audit of OpenSSL 1.1.1 shows 4,744 public crypto symbols; wolfEngine covers only those reached via EVP dispatch.
This shim covers all of them.
OpenSSL 1.1.1 reached end-of-life in September 2023. Its FIPS 140-2 validated
module (openssl-fips-2.0) is also end-of-life and cannot be upgraded to
FIPS 140-3. There is no supported path to FIPS 140-3 certification for
OpenSSL 1.x.
wolfCrypt holds a current
FIPS 140-3 certificate
(certificate #4718, boundary wolfCrypt FIPS 140-3 Module). By replacing
OpenSSL 1.1.1's crypto primitives with wolfCrypt, an application that cannot
yet migrate to a newer OpenSSL can have its cryptographic operations performed
by a FIPS 140-3 validated module.
Important caveats on FIPS:
- This shim wires wolfCrypt into OpenSSL's plumbing. FIPS compliance requires that your wolfSSL build is specifically the FIPS boundary build (not the open-source build used here). Contact wolfSSL for the FIPS source package.
- The wolfCrypt FIPS boundary excludes certain algorithms: MD5 (only in non-approved mode), DES/3DES, RC4, and others. Applications using those via this shim will work but will not be operating within the FIPS boundary.
- A FIPS-ready deployment also requires a power-on self-test, integrity check, and use of the approved algorithm indicators. None of that is configured in this open-source shim — it is a foundation, not a finished FIPS product.
| Approach | Covers EVP layer | Covers low-level API | FIPS 140-3 path |
|---|---|---|---|
| wolfEngine | ✅ | ❌ | Needs extra config |
| wolfSSL compat headers (compile-time) | ✅ | ❌ (no ELF symbols) | — |
| This shim (link-time replacement) | ✅ | ✅ | wolfCrypt underneath |
This shim is real engineering work, and it will make your auditor ask hard questions. Before committing to it, check whether an OpenSSL 3 migration is actually off the table.
Audit friction. Replacing 4,744 symbols in an EOL library is not a configuration your security auditor has seen before. Expect custom evidence packages, extended scoping discussions, and questions with no standard answer yet.
Ongoing maintenance. OpenSSL 1.1.1 receives no security patches. Every CVE in the protocol layers — TLS state machine, ASN.1 parser, X.509 — stays your problem, indefinitely.
Upgrade is often less work than it looks. wolfProvider on OpenSSL 3.x delivers FIPS 140-3 on a supported, familiar library. Most "we can't upgrade" blockers are build-system constraints, not source-level ones. wolfSSL offers porting consulting — a scoped engagement to move to OpenSSL 3 + wolfProvider is often cheaper than maintaining this shim long-term.
Either way, you need to contact wolfSSL: FIPS 140-3 requires the wolfCrypt FIPS boundary build, which is not the open-source build in this repo. Whether you stay on OpenSSL 1.x with this shim or migrate to OpenSSL 3 + wolfProvider, the path to FIPS starts the same place. Talk to us first and we will help you pick the right approach. Contact us at facts@wolfssl.com or call +1 425 245 8247.
wolfssl-openssl1/
├── openssl/ OpenSSL 1.1.1w — git submodule (unmodified upstream)
├── wolfssl/ wolfSSL v5.9.0-stable — git submodule
├── shim/
│ ├── include/ Headers shared across shim translation units
│ │ (includes wolfshim_abort.h — WOLFSHIM_FATAL macro)
│ ├── lib/ libwolfshim.a (built artifact, not committed)
│ │ ⚠ Does NOT contain EC, BN, or RSA — those symbols
│ │ are provided by libwolfssl.so's OpenSSL compat layer.
│ └── src/
│ ├── sha/ SHA-1/224/256/384/512/3-* wrappers → wc_Sha*
│ ├── aesni/ aesni_* ABI stubs dispatching to wolfCrypt AES
│ ├── aliases/ ~290 ELF wrappers: FOO() { return wolfSSL_FOO(); }
│ ├── stubs/ EVP_PKEY_METHOD / EVP_PKEY_ASN1_METHOD stubs,
│ │ legacy cipher stubs (MDC2, WHIRLPOOL, etc.)
│ ├── pkey/ EVP_PKEY_METHOD objects for RSA, EC, DH,
│ │ X25519, Ed25519, Ed448
│ ├── evp/ EVP digest bridge — constructs OpenSSL EVP_MD
│ │ objects whose callbacks call into wolfSSL EVP
│ ├── rsa/ RSA padding, keygen, engine vtable stubs
│ ├── ec/ EC point arithmetic, ECDSA, ECDH, binary-curve stubs
│ ├── rand/ RAND_DRBG shims
│ ├── aes/ AES key-wrap (RFC 3394) shims
│ ├── rng/ Per-thread WC_RNG implementation (shim_rng.c, shared by rsa/ and pkey/)
│ ├── bn/ BIGNUM arithmetic shims
│ └── wolfshim_abi_check.c Runtime ABI sanity check (runs at load time)
├── patches/
│ ├── openssl-wolfshim.patch Five source patches (plus test infrastructure) applied at build time
│ └── Makefile.wolfshim Pre-generated wolfshim-patched OpenSSL Makefile
├── build.sh Builds wolfSSL → shim → OpenSSL in one shot
└── .clangd clangd include-path config for IDE support
OpenSSL's Makefile (pre-generated by ./Configure linux-x86_64, then
modified) gains a WOLFCRYPT_EXCLUDE=1 flag. When set:
- The primitive crypto object files are dropped from the link:
crypto/aes/,crypto/sha/,crypto/rsa/,crypto/evp/m_sha*.o,crypto/hmac/,crypto/rand/,crypto/bn/,crypto/ec/, and the x86_64 AES-NI / SHA-NI assembly stubs. libwolfshim.a(via--whole-archive) andlibwolfssl.soare linked in their place.- The glue layers —
crypto/evp/,crypto/x509/,crypto/asn1/,ssl/— are compiled and linked normally. They still call into the OpenSSL public API, which the shim now backs.
wolfSSL exports wolfSSL_AES_cbc_encrypt. OpenSSL callers reference
AES_cbc_encrypt. wolfSSL's header macros (#define AES_cbc_encrypt wolfSSL_AES_cbc_encrypt) work at compile time but do not create ELF symbols,
so the linker cannot resolve them at link time.
shim/src/aliases/aliases.c provides ~290 real function definitions — one
per wolfSSL macro alias — so the linker sees actual symbols:
// undef the macro so we can define the real function
#undef AES_cbc_encrypt
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, const int enc)
{
wolfSSL_AES_cbc_encrypt(in, out, length, key, ivec, enc);
}OpenSSL's EVP_MD struct (used for SHA, MD5, RIPEMD, etc.) is an internal
type that cannot be constructed from a public API in 1.1.1 without
crypto/evp.h (an internal header). Including both OpenSSL's and wolfSSL's
headers in the same translation unit causes fatal typedef conflicts.
The bridge splits cleanly across two compilation units:
evp_digest_shim.c— compiled with OpenSSL headers only. CallsEVP_MD_meth_new()to constructEVP_MDobjects and registers wolfSSL callbacks forinit,update,final,copy,cleanup.evp_wolf_bridge.c— compiled with wolfSSL headers only. Implements those callbacks usingwolfSSL_EVP_MD_CTX_new()/wolfSSL_EVP_DigestInit_ex()/ etc.
The bridge also avoids an ABI size mismatch: the sizeof(WOLFSSL_EVP_MD_CTX)
seen by the shim's compile-time wolfSSL headers (3360 bytes) differs from the
runtime struct inside the installed libwolfssl.so (3552 bytes). Using
wolfSSL_EVP_MD_CTX_new() instead of malloc(sizeof(...)) lets the wolfSSL
library allocate the correct runtime size internally.
shim/src/pkey/pkey_meth_shim.c uses __attribute__((constructor)) to run
at shared-library load time and register EVP_PKEY_METHOD objects for RSA,
EC, DH, X25519, Ed25519, and Ed448. This is necessary because OpenSSL's
internal standard_methods[] table (a sorted array used for binary search)
must have every entry present with the correct NID for EVP_PKEY_meth_find()
to work — including NIDs for algorithms excluded at compile time.
Stub methods for sm2 (excluded from this build) are provided as
correctly-sized structs with the right pkey_id values so the binary search
table remains sorted and complete. siphash and poly1305 use the real
EVP_PKEY_METHOD and EVP_PKEY_ASN1_METHOD objects from their corresponding
OpenSSL .o files, linked directly into libwolfshim.a.
OpenSSL's x86_64 build compiles AES-NI assembly stubs (aesni_set_encrypt_key,
aesni_cbc_encrypt, aesni_ctr32_encrypt_blocks, etc.) from
crypto/aes/aesni-x86_64.s. These are removed under WOLFCRYPT_EXCLUDE=1.
shim/src/aesni/aesni_shim.c re-provides them, dispatching to the wolfCrypt
AES path.
The x86_64 aesni_gcm_encrypt/aesni_gcm_decrypt assembly routines read the
AES key schedule in Intel's hardware aeskeygenassist format. wolfCrypt stores
its key schedule in a different internal format. A patch to crypto/evp/e_aes.c
disables the AES_GCM_ASM fast path (which would use those routines for data
≥ 288 bytes), forcing GCM to use the software CTR path through
aesni_ctr32_encrypt_blocks → AES_encrypt → wc_AesEncryptDirect, which
is correct for all data sizes.
Five patches (in patches/openssl-wolfshim.patch, applied at build time) are
the only modifications to OpenSSL's source:
-
crypto/asn1/ameth_lib.c— Correctness fix, not a defensive guard. The wolfshimASN1_METH_STUBmacro creates stub method entries withpem_str = NULLandASN1_PKEY_ALIASclear. Upstream OpenSSL only allowspem_str = NULLon entries whereASN1_PKEY_ALIASis set, soEVP_PKEY_asn1_find_str()has no NULL check before callingstrlen(ameth->pem_str). The patch adds that check. Without it, the loop dereferences NULL and the search never matches a real algorithm. -
crypto/evp/names.c— NULL-guards for a NULLEVP_MD *or NULL OID name passed toEVP_add_digest(). wolfshim may callEVP_add_digestwith a NULL digest when a stub returns NULL fromEVP_sm3. -
crypto/evp/e_aes.c— disableAES_GCM_ASM(see above). -
ssl/record/rec_layer_s3.c— Security-relevant TLS state machine change. The original code had aTLS_ANY_VERSIONguard that ran before theSSL3_RT_ALERTdispatch block. When a client sent a fatal alert before version negotiation completed (e.g. aprotocol_versionalert in a version-mismatch handshake), the guard fired first, returningSSL_AD_UNEXPECTED_MESSAGEwithout ever delivering the alert to the info callback. This meantSSL_CB_READ_ALERTnever fired, fatal alerts were silently dropped, and the connection state machine did not terminate cleanly. The patch moves the guard to after the alert dispatch block so that alerts received at any point in the handshake are processed per RFC 5246 §7.2 / RFC 8446 §6. Non-alert, non-handshake records still hitUNEXPECTED_MESSAGEvia the relocated guard — the original safety check is fully preserved, only its position changes. -
test/— test infrastructure for therec_layer_s3.cfix: adds theExpectedClientAlertReceivedassertion, theProtocolVersionalert name, and31-wolfshim-alert-delivery.confwhich verifies the fix by asserting that aprotocol_versionalert sent before version negotiation is received by the server's info callback.
# Debian / Ubuntu
sudo apt install build-essential autoconf automake libtool
# Fedora / RHEL
sudo dnf install gcc make autoconf automake libtoolgit clone <this-repo> wolfssl-openssl1
cd wolfssl-openssl1
git submodule update --init --depth=1./build.shOr step-by-step:
./build.sh wolfssl # configure + build wolfSSL
./build.sh shim # compile shim objects → shim/lib/libwolfshim.a
./build.sh openssl # patch + make WOLFCRYPT_EXCLUDE=1build.sh openssl applies patches/openssl-wolfshim.patch with
patch -N -p1 before compiling (idempotent — safe to run multiple times).
cd openssl
export LD_LIBRARY_PATH=.:../wolfssl/src/.libs
./apps/openssl version
echo hello | ./apps/openssl dgst -sha256
./apps/openssl genrsa 2048 2>/dev/null | ./apps/openssl rsa -check
./apps/openssl ecparam -name prime256v1 -genkey -noout -out /tmp/ec.pem
echo test | ./apps/openssl dgst -sha256 -sign /tmp/ec.pem -out /tmp/ec.sig /dev/stdin# terminal 1
openssl req -x509 -newkey rsa:2048 -keyout /tmp/srv.key -out /tmp/srv.crt \
-days 1 -nodes -subj "/CN=test" -config <(printf '[req]\ndistinguished_name=d\n[d]\n')
./apps/openssl s_server -cert /tmp/srv.crt -key /tmp/srv.key -port 14433 -tls1_3 -quiet
# terminal 2
echo Q | ./apps/openssl s_client -connect localhost:14433 -tls1_3 -quietExpected: TLS_AES_256_GCM_SHA384 negotiated, certificate depth shown,
read:errno=0 on clean disconnect.
Note on
make tests: Runningmake testsdirectly in theopenssl/directory will report approximately 46 failures out of 152 tests. These are expected: they cover legacy ciphers excluded from this build (Blowfish, CAST-5, RC2, DES raw), OpenSSL internal struct tests that differ by design when wolfCrypt replaces the primitives, and TLS proxy edge cases. The maintained test baseline is./test.sh(described below), which covers all correctness claims made for this shim.
After ./build.sh, a single wrapper runs everything:
./test.sh # all groups (no extra dependencies needed)
./test.sh evp # EVP digest + MAC tests only
./test.sh ssl # TLS handshake tests only
./test.sh nist # NIST KAT + algorithm tests only
./test.sh wychcheck # Wycheproof tests (see below)All tests pass or skip cleanly. Exit code is 0 on success.
Runs OpenSSL's own test/evp_test harness against four data files:
| File | Tests | What it covers |
|---|---|---|
evpdigest.txt |
79 | All digest algorithms: SHA-1/2/3, SHAKE-128/256, MD5, RIPEMD-160, MDC2 |
evpmac.txt |
103 | EVP MAC API: HMAC, CMAC, SipHash, Poly1305 |
evpencod.txt |
47 | Base64 / hex encode + decode |
evpcase.txt |
6 | Case-insensitive digest/cipher name lookup |
Runs OpenSSL's test/ssl_test against 17 conf files from test/ssl-tests/:
01-simple 03-custom_verify 06-sni-ticket 08-npn 09-alpn
13-fragmentation 14-curves 17-renegotiate 20-cert-select
21-key-update 23-srp 24-padding 25-cipher 26-tls13_client_auth
27-ticket-appdata 28-seclevel 30-supported-groups
Confs omitted (known pre-existing gaps unrelated to the shim):
02-protocol-version, 04-client_auth, 05-sni, 07-dtls-protocol-version,
10-resumption, 11-dtls_resumption, 12-ct, 15/16-certstatus,
18-dtls-renegotiate, 19-mac-then-encrypt, 22-compression.
Tests the cryptographic primitives against official NIST Known Answer Test vectors. Three layers:
wolfcrypt testwolfcrypt — wolfSSL's own KAT suite run directly against wolfCrypt (no OpenSSL shim involved). 58 algorithm groups, all must pass:
| Category | Algorithms |
|---|---|
| Digests | SHA-1/224/256/384/512, SHA-512/224, SHA-512/256, SHA-3 (224/256/384/512), SHAKE-128/256, RIPEMD-160, MD4, MD5 |
| MACs / KDF | HMAC (all digests), HKDF, TLS 1.2/1.3 KDF, PRF, GMAC |
| DRBG | SP 800-90A Hash_DRBG |
| Symmetric | AES-128/192/256 ECB/CBC/CTR/GCM/CFB (FIPS 197, SP 800-38A/D), DES/3DES, ARC4, ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD |
| Asymmetric | RSA PKCS#1 v1.5/PSS/OAEP (FIPS 186-4), ECC/ECDSA/ECDH P-224 to P-521 (FIPS 186-4 / SP 800-56A), DH, DSA |
evpkdf.txt (36 tests) — NIST TLS-PRF vectors from the NIST test suite, exercised through the OpenSSL KDF EVP API and routed through the shim.
evppkey_ecc.txt (498 tests) — NIST ECDSA sign/verify and ECDH vectors,
exercised through the OpenSSL EVP_PKEY API and routed through the shim.
Tests wolfSSL's cryptographic correctness against Google's Wycheproof test vectors using the wychcheck harness.
Additional prerequisites: cmake, and a clone of wychcheck.
# one-time setup
git clone https://github.com/wolfSSL/wychcheck.git /tmp/wychcheck
# run (HEAD only — fast)
WYCHCHECK_REPO=/tmp/wychcheck ./test.sh wychcheck
# run across all wychcheck commits (regression sweep)
WYCHCHECK_REPO=/tmp/wychcheck ./test/wychcheck_gitref_test.shThe script builds wychcheck against this project's wolfSSL tree (so you
test the exact build that powers the shim) and runs all Wycheproof
vector files it knows about. Each file is reported as PASS, FAIL, or
SKIP (algorithm not compiled in).
All 10 tests pass on x86_64 Linux with wolfSSL v5.9.0-stable:
| Test | Result |
|---|---|
openssl version |
✅ |
| SHA-256 digest | ✅ |
| RSA-2048 keygen | ✅ |
| RSA sign + verify (SHA-256) | ✅ |
| ECDSA P-256 sign + verify | ✅ |
| AES-256-CBC encrypt/decrypt | ✅ |
| AES-256-GCM encrypt/decrypt (all sizes incl. ≥ 288 B) | ✅ |
| HMAC-SHA256 | ✅ |
| X.509 self-signed cert gen + verify | ✅ |
TLS 1.3 local handshake (TLS_AES_256_GCM_SHA384 + X25519) |
✅ |
⚠ libwolfshim.a does not contain EC, BN, or RSA.
The shim source tree includes
ec/ec_shim.c,bn/bn_shim.c, andrsa/rsa_shim.c, but these are not compiled intolibwolfshim.ain the default build. EC, BN, and RSA public symbols (EC_KEY_new,BN_new,RSA_new, etc.) are instead resolved at link time from wolfSSL's own OpenSSL compatibility layer insidelibwolfssl.so(built withOPENSSL_EXTRA).This means: a binary linked against
libwolfshim.a+libwolfssl.souses wolfSSL's built-in compat implementations for EC/BN/RSA, not the shim overrides inec/,bn/,rsa/. If you observe a behavioural difference in EC, BN, or RSA operations, the relevant source to inspect iswolfssl/src/*.c, notshim/src/ec/,shim/src/bn/, orshim/src/rsa/.The shim overrides exist as alternative implementations and can be enabled by linking
libwolfshim_ec.a,libwolfshim_bn.a, orlibwolfshim_rsa.abeforelibwolfssl.so. Seeshim/ARCHITECTURE.md§5 for when to do this.
| Algorithm group | Algorithms | Shim file |
|---|---|---|
| SHA-1 / SHA-2 | SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256 | sha/sha_shim.c, evp/evp_digest_shim.c |
| SHA-3 / XOF | SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE-128, SHAKE-256 | evp/evp_digest_shim.c |
| Legacy digests | MD4, MD5, RIPEMD-160, MDC2; EVP_md5_sha1 — MD5+SHA1 dual-context (TLS 1.0/1.1 combined, 36-byte output) |
evp/evp_digest_shim.c |
| AES | ECB, CBC, CTR, CFB-1, CFB-8, CFB-128, OFB, GCM, CCM, XTS, OCB; key-wrap/unwrap (RFC 3394); IGE, bi-IGE | aes/aes_shim.c, aesni/aesni_shim.c |
| DES / 3DES | DES-ECB/CBC/CFB/OFB; DES-EDE-CBC/CFB/OFB | des/des_shim.c, des/des_modes_bridge.c |
| ChaCha20-Poly1305 | ChaCha20 keystream, Poly1305 MAC, ChaCha20-Poly1305 AEAD | chacha/chacha_shim.c |
| RSA ¹ | Key generation, PKCS#1 v1.5, OAEP, PSS sign/verify/encrypt/decrypt | rsa/rsa_shim.c |
| EC / ECDSA / ECDH ¹ | P-256, P-384, P-521 and other named curves; ECDSA sign/verify; ECDH key agreement | ec/ec_shim.c |
| HMAC | HMAC with any supported digest | hmac/hmac_shim.c |
| RAND / DRBG | RAND_bytes, RAND_priv_bytes, RAND_DRBG_* |
rand/rand_shim.c |
| BIGNUM ¹ | Arithmetic, modular exponentiation, primality | bn/bn_shim.c |
¹ In the default build, these symbols are provided by libwolfssl.so's OpenSSL
compat layer, not by the shim .c files listed above. See the note at the top
of this section.
The following algorithms are absent from wolfCrypt's standard feature set
and are not shimmed. OpenSSL's compiled-in implementations are excluded
from the link (WOLFCRYPT_EXCLUDE=1), so calling these through the EVP API
will return NULL or an error at runtime.
| Algorithm | Reason not shimmed |
|---|---|
| Blowfish | Not in wolfCrypt |
| CAST-5 | Not in wolfCrypt |
| RC2 | Not in wolfCrypt |
| RC4 | Not in wolfCrypt |
| IDEA | Not in wolfCrypt |
| SEED | Not in wolfCrypt |
| ARIA | Not in wolfCrypt |
| Camellia | Not in wolfCrypt (available with --enable-camellia but not enabled in this build) |
| SM4 | Not in wolfCrypt default build (available with --enable-sm4) |
| Whirlpool | Not in wolfCrypt; EVP_whirlpool() returns NULL |
| BLAKE2b / BLAKE2s | In OpenSSL but not in wolfCrypt's EVP layer; shim not written |
| SM3 | Not in wolfCrypt default build (available with --enable-sm3) |
| MD2 | Not in wolfCrypt; EVP_md2() returns NULL |
| AES-CBC-HMAC-SHA1/256 | Combined TLS-record cipher; underlying AES and HMAC are wolfCrypt-backed, but the compound EVP cipher object is not re-shimmed (not needed outside TLS internals) |
These can be unlocked by adding configure flags to the wolfSSL build in
build.sh and writing the corresponding shim (or just re-enabling the
OpenSSL objects for them):
| Algorithm | Flag to add |
|---|---|
| Ed25519 / Ed448 standalone keygen | --enable-ed25519 --enable-ed448 |
| X25519 / X448 standalone keygen | --enable-curve25519 --enable-curve448 |
| SM3 | --enable-sm3 |
| SM4 | --enable-sm4 |
| Camellia | --enable-camellia |
Every known gap in the shim is tagged in the source with a severity-rated
marker (WOLFSHIM_GAP[SECURITY:HIGH], WOLFSHIM_GAP[CORRECTNESS], etc.) and
can be enumerated with the provided script:
./shim/audit-gaps.sh # all tags
./shim/audit-gaps.sh HIGH # security-critical gaps only (start here)
./shim/audit-gaps.sh ABI # struct-layout sites to re-audit on wolfSSL upgradeSee shim/ARCHITECTURE.md §21 for the full tag taxonomy and shim/UPGRADING-WOLFSSL.md
for how the ABI tags are used during wolfSSL version bumps.
-
EC, BN, and RSA come from wolfSSL's compat layer, not from libwolfshim.a.
libwolfshim.adoes not contain EC, BN, or RSA object files. In the default build, public symbols in those families (EC_KEY_new,BN_mod_exp,RSA_generate_key_ex, etc.) are resolved from wolfSSL's own OpenSSL compatibility layer insidelibwolfssl.so.This has two practical consequences:
-
Debugging EC/BN/RSA issues: the relevant code is in
wolfssl/src/ssl.c,wolfssl/src/wolfcrypt/src/ecc.c, etc. — not inshim/src/ec/,shim/src/bn/, orshim/src/rsa/. If you observe an EC, BN, or RSA behaviour that differs from OpenSSL, file the investigation against wolfSSL's compat layer. -
wolfSSL upgrade risk: wolfSSL's compat layer for EC/BN/RSA is independently versioned and may change behaviour across wolfSSL releases in ways the shim's version guards (
LIBWOLFSSL_VERSION_HEX+_Static_assert) do not catch — those guards only protect the shim's own ABI access sites. Run the EC/BN/RSA algorithm tests (./test.sh nist) after every wolfSSL upgrade.
The shim override files (
ec_shim.c,bn_shim.c,rsa_shim.c) can be enabled individually by linking the correspondinglibwolfshim_ec.a/libwolfshim_bn.a/libwolfshim_rsa.abeforelibwolfssl.so. Seeshim/ARCHITECTURE.md§5 for when this is appropriate. -
-
Platform: x86_64 Linux only. The
openssl/Makefilewas generated by./Configure linux-x86_64. Other targets need their own./Configurerun and a re-application of the patch. -
openssl encAEAD:openssl enc -aes-256-gcmreturns "AEAD ciphers not supported" — this is a limitation of theenc(1)streaming interface, not of the underlying crypto. AES-GCM works correctly via the C EVP API and in TLS. -
Standalone X25519/Ed25519 keygen: TLS 1.3 key exchange via X25519 works (handled by OpenSSL's
ecx_meth.csoftware implementation). Standaloneopenssl genpkey -algorithm X25519currently fails because this wolfSSL build was not configured with--enable-curve25519 --enable-ed25519. Add those flags to the wolfSSL configure line inbuild.shand rebuild. -
RAND_DRBG_get0_master / get0_public / get0_private — two compatibility gaps:
Singleton, not a hierarchy. All three functions return the same process-lifetime singleton backed by a single
WC_RNG. The public/private DRBG separation is absent — see Security Limitations below.Can return NULL. Under standard OpenSSL, these functions never return NULL after library initialisation — callers in application code frequently omit a NULL check. Under this shim, NULL is returned if
wc_InitRng()fails at singleton init (out-of-memory or no OS entropy source). When that happens, any caller that dereferences the result without a NULL check will crash.OpenSSL's own internal callers (
crypto/rand/drbg_lib.cdrbg_bytes,drbg_add;crypto/rand/rand_lib.cRAND_poll,RAND_priv_bytes) all already NULL-check. The risk is in application code.Audit action: search your application source for
RAND_DRBG_get0_and verify every call site checks the return value before dereferencing:RAND_DRBG *drbg = RAND_DRBG_get0_public(); if (drbg == NULL) { /* handle RNG init failure */ }
-
RAND_DRBG type selection:
RAND_DRBG_new(NID_aes_256_ctr, ...)will push an error but succeed, producing a wolfCrypt Hash-DRBG regardless of the requested NID. CallingRAND_DRBG_set(drbg, NID_aes_256_ctr, 0)orRAND_DRBG_set_defaults(NID_aes_256_ctr, 0)will fail withERR_R_UNSUPPORTED. wolfCrypt uses Hash-DRBG internally; the NID cannot be changed. UseRAND_DRBG_new(0, 0, NULL)to avoid the error. -
BN_GENCB progress callbacks are not invoked.
BN_GENCB_set()stores a callback butRSA_generate_key_ex()(and other operations that accept aBN_GENCB*) do not call it. wolfCrypt performs key generation internally without calling back to the application. Applications that display a progress indicator during RSA key generation will see no updates. -
x509 / OCSP / PKCS (790 symbols): Intentionally deferred — these will not be shimmed in this release. The OpenSSL x509/ASN.1 glue layer itself still compiles and links (it was not excluded), so high-level cert operations work. The gap is the wolfSSL-side coverage audit: it is unknown which of the 790 symbols are already covered by wolfSSL's internal
x509.cvs. which need new shims. If you need specific OCSP or DER codec symbols, runnm -u <binary>and cross-reference againstshim/audit/gap_by_priority.json(key"x509"). -
AES-GCM throughput: The
AES_GCM_ASMfast path (combined AES-NI + PCLMULQDQ GHASH) is disabled for correctness — wolfCrypt stores its AES key schedule in a different format than Intel'saeskeygenassistlayout. GCM encryption uses AES-NI for the CTR keystream but falls back to software GHASH for authentication tag computation. Expect reduced AES-GCM throughput compared to stock OpenSSL on x86_64, particularly for TLS 1.3 bulk data (AES_GCM_ASMapplies to all buffers >= 288 bytes). See §Path forward. -
No FIPS: This build uses the open-source wolfSSL release. A FIPS 140-3 deployment requires the separately licensed wolfCrypt FIPS boundary package.
-
AES_KEY memory: Each
AES_set_encrypt_key/AES_set_decrypt_keycall allocates a wolfCryptAescontext on the heap (~1100 bytes containing the full expanded AES round-key schedule). OpenSSL has noAES_KEY_free()API, so the shim cannot free this allocation through a dedicated destructor.What the shim does automatically: The shim intercepts
OPENSSL_cleanse()(seeshim/src/aliases/aliases.c). Before zeroing the buffer, it checks for the wolfshim magic sentinel and, if present, callswc_AesFree()to zero the key schedule and then frees the heap allocation. This covers:- The EVP path —
EVP_CIPHER_CTX_free()callsOPENSSL_cleanse(cipher_data, ...)internally for CBC/ECB/CFB/OFB/CTR modes. No action required from callers usingEVP_CIPHER_CTX. - Well-written direct callers — code that calls
OPENSSL_cleanse(&key, sizeof(key))before theAES_KEYgoes out of scope (the pattern used throughout OpenSSL's owncrypto/cms/and elsewhere). Re-initializing viaAES_set_encrypt_key/AES_set_decrypt_keyalso frees the previous allocation correctly.
Remaining architectural leak: A stack-allocated
AES_KEYthat goes out of scope withoutOPENSSL_cleanse(or re-initialization) cannot be intercepted. TheAesallocation leaks, and with it the live key schedule. This is a fundamental constraint of the API surface.Key-material concern: The leaked
Aesstructs contain the full expanded AES round-key schedule, not just bookkeeping overhead. When glibc reclaims the block, subsequent allocations in the same process may read the key material before it is overwritten. At TLS server scale (1000 conn/s) with direct low-levelAES_*use on the hot path, this amounts to roughly 1 MB/s of key-containing heap churn.FIPS 140-2/3 zeroization requirements are NOT met for stack-allocated
AES_KEYobjects that do not callOPENSSL_cleanse. FIPS 140-2 (section 4.7.6) and FIPS 140-3 (ISO/IEC 24759) require that key material be zeroized when no longer needed.Recommended practices (in order of preference):
-
Prefer
EVP_CIPHER_CTXpaths. The EVP path has a proper lifecycle (EVP_CIPHER_CTX_free) that the shim intercepts cleanly. No leak, no key-material exposure. -
Call
OPENSSL_cleanseon directAES_KEYuse. If you use the low-levelAES_*API, callOPENSSL_cleanse(&key, sizeof(key))before theAES_KEYgoes out of scope. The shim's hook will free and zero the heapAes. This is also the pattern used by OpenSSL's own internal code. -
Restrict low-level AES to long-lived keys. If a key is set once per connection or session object (not per record), the leak is one fixed allocation per session rather than one per operation.
Valgrind: The remaining architectural leak (case where
OPENSSL_cleanseis not called) will appear in Valgrind output as:malloc ← aes_ctx_alloc ← AES_set_encrypt_key ← <your code>A suppression file is provided at
shim/wolfshim.suppto mark this as intentional. Run Valgrind with:valgrind --suppressions=shim/wolfshim.supp --leak-check=full ./binarySee
shim/include/aes_ctx.hfor the full memory model. - The EVP path —
-
HMAC_CTX_set_flagsis accepted but ignored:HMAC_CTX_set_flags(ctx, flags)returns without error but does not forwardflagsto wolfCrypt. wolfSSL has no equivalent of OpenSSL'sEVP_MD_CTX_FLAG_NO_INITor other EVP flags on the HMAC context.Impact: TLS PRF implementations that set
EVP_MD_CTX_FLAG_NO_INITviaHMAC_CTX_set_flagswill not get the expected behaviour — the flag is silently dropped. Verify that your TLS stack's PRF and key derivation paths work correctly without this flag before deploying against this shim.Affected callers: Any code that calls
HMAC_CTX_set_flagswithEVP_MD_CTX_FLAG_NO_INIT. OpenSSL's owncrypto/evp/TLS1_PRF and HKDF implementations do not use this path, but third-party TLS PRF code might. -
AES mode gaps abort rather than silently produce wrong output: Several AES functions (
AES_ige_encrypt,AES_bi_ige_encrypt,AES_ecb_encryptdecrypt,AES_cfb1_encryptdecrypt,AES_cfb8_encryptdecrypt,AES_ofb128_encrypt) require wolfSSL to be built with specific compile-time flags (WOLFSSL_AES_DIRECT,HAVE_AES_DECRYPT,WOLFSSL_AES_CFB,WOLFSSL_AES_OFB). If a flag is absent from the wolfSSL build, calling the corresponding function will print a diagnostic tostderrand callabort().This is intentional. The alternative — zeroing the output buffer and returning — was rejected because it produces silent wrong ciphertext: the caller gets a success-shaped result (the function returned, the output buffer is filled), but the bytes are meaningless. An application encrypting data would believe the operation succeeded and store or transmit garbage. An application decrypting would silently produce garbage plaintext. Neither case is detectable without out-of-band verification.
An abort is immediately visible in any test, integration environment, or crash reporting system. It correctly communicates "this operation cannot be performed as configured" rather than fabricating a result. The fix is always to rebuild wolfSSL with the required flag (each abort message names the exact flag), not to ignore the crash.
The standard
build.shenables all required flags. This situation arises only if you supply a custom wolfSSL build without followingbuild.sh.
These are behavioural differences from OpenSSL that have security relevance. They are not bugs in the shim — they are fundamental consequences of mapping OpenSSL's API onto wolfCrypt's different architecture. Customers should read this section before deploying.
OpenSSL 1.1.1 introduced a three-DRBG hierarchy (master / public / private) so that private key material is generated from a different RNG state than public nonces. The intent is that compromise of the public DRBG output (e.g. through a side channel) does not reveal the private DRBG state.
RAND_priv_bytes() in this shim calls the same wolfCrypt RNG as
RAND_bytes(). RAND_DRBG_get0_master(), RAND_DRBG_get0_public(), and
RAND_DRBG_get0_private() all return the same process-lifetime singleton DRBG
— there is no hierarchy. The singleton is functional (backed by a real WC_RNG)
so calls through it generate genuine random bytes, but public and private
operations draw from the same entropy state. If your threat model requires
public/private DRBG separation, do not use this shim, or arrange for your
private key generation code to use an out-of-band entropy source.
RAND_set_rand_method() installs a custom method. All calls to RAND_bytes,
RAND_seed, RAND_add, and RAND_status will dispatch through the installed
method's callbacks.
RAND_DRBG_generate() does not dispatch through RAND_METHOD. The
RAND_DRBG family uses wolfCrypt's internal WC_RNG directly. If you have
installed a custom method (e.g. a hardware HSM), callers that use
RAND_DRBG_generate() will still use wolfCrypt's OS-entropy DRBG.
RAND_DRBG_set_callbacks(drbg, get_entropy, cleanup_entropy, get_nonce, cleanup_nonce) returns 0 (failure) and pushes ERR_R_UNSUPPORTED when any
callback is non-NULL. wolfCrypt seeds exclusively from OS entropy
(/dev/urandom or platform equivalent) and provides no hook for
application-supplied entropy.
When a non-NULL callback is rejected, the DRBG is poisoned: subsequent
calls to RAND_DRBG_instantiate() and RAND_DRBG_generate() on that DRBG
will also return 0 (failure) with ERR_R_UNSUPPORTED in the error queue and a
diagnostic to stderr. This ensures the missing entropy source is detected at
a call site that callers actually check, rather than silently falling back to OS
entropy without the application's knowledge.
Applications that require hardware RNG or deterministic test-vector entropy MUST NOT use this shim.
OpenSSL's RAND_DRBG_secure_new() allocates from a locked, guarded memory
region so that RNG state cannot be swapped to disk and is harder to read from
an adjacent memory corruption. wolfCrypt has no equivalent allocator.
The shim allocates from the standard heap. On RAND_DRBG_free(), the struct
is zeroised with explicit_bzero() before being returned to the heap, which
limits the window during which key material sits in freed memory. However, the
memory is not mlock'd, is not guarded, and may be present in core dumps.
wolfCrypt uses an internal Hash-DRBG (SHA-256 based). The NID-based type
selection (NID_aes_256_ctr, NID_sha512, etc.) that OpenSSL supports is
not available. RAND_DRBG_set() and RAND_DRBG_set_defaults() will fail
with ERR_R_UNSUPPORTED for any non-zero type NID.
For FIPS 140-3 deployments: wolfCrypt's FIPS boundary uses its own DRBG implementation that meets the SP 800-90A requirements for the wolfCrypt module. The NID selected here has no effect on the FIPS boundary.
The TLS record layer's CBC padding verification (tls1_cbc_remove_padding in
ssl/record/ssl3_record.c) is implemented by OpenSSL's retained glue code
using constant-time primitives (constant_time_ge_s, constant_time_eq_s,
etc.). wolfCrypt's wc_AesCbcDecrypt is used for the bulk AES-CBC cipher
operation only; it does not participate in the padding check.
The OpenSSL test 70-test_sslcbcpadding.t (which verifies Lucky13 class
padding oracle resistance) fails 5/5 in this build. Investigation confirms
this is a test infrastructure failure, not a timing vulnerability. The test
uses the TLSProxy::Proxy framework which hardcodes the -engine ossltest
flag on both s_server and s_client. The ossltest engine's no-op cipher
wrappers (which delegate through to the wolfCrypt-backed EVP_aes_128_cbc)
and deterministic RAND conflict with wolfCrypt's AES context management,
preventing the proxy from establishing a working TLS session. The proxy never
reaches the point of injecting malformed padding.
The constant-time padding verification code itself is unmodified OpenSSL C code and is not affected by the wolfCrypt substitution.
The lock parameter passed to BN_MONT_CTX_set_locked() is an OpenSSL
CRYPTO_RWLOCK (a pthread_rwlock_t internally). wolfSSL maps
CRYPTO_THREAD_write_lock to wc_LockMutex (a pthread_mutex_t wrapper) —
calling it on a pthread_rwlock_t is undefined behaviour.
The shim ignores the passed lock and uses a single static pthread_mutex_t
instead. This serialises all concurrent RSA first-use Montgomery context setup
across the process. After first use, *pmont != NULL takes a lock-free fast
path. The performance cost is acceptable for this use case (infrequent, only
on first key use); correctness is maintained.
Applications with timing-sensitive threat models MUST NOT use this release.
OpenSSL's RSA Montgomery-ladder implementation calls BN_consttime_swap to
perform a constant-time conditional bignum swap as a blinding step against
Kocher-style timing attacks. This shim implements BN_consttime_swap as a
plain branching conditional swap — the constant-time guarantee is absent.
The result is a measurable timing side-channel on RSA private-key operations (decrypt, sign). An attacker with the ability to observe many RSA operation timings can use this to recover the private key.
Remediation: This cannot be fixed within the OpenSSL 1.1.1 + wolfCrypt architecture. wolfCrypt does not expose a constant-time bignum swap primitive and implementing one directly against internal wolfCrypt struct fields introduces more risk than the gap itself. See §Path forward.
Until migration is complete, mitigate by ensuring RSA private operations are not reachable from untrusted network paths that can drive a timing oracle (e.g. by running private-key operations in a separate process or enforcing rate-limiting at the network layer).
RAND_DRBG_set_reseed_interval and RAND_DRBG_set_reseed_time_interval
accept and store their arguments but wolfCrypt's internal DRBG does not read
them. Applications that rely on deterministic reseed scheduling — for example
to satisfy FIPS 140-2 reseed count requirements — will not have their policy
enforced. Use an out-of-band reseed strategy or a wolfCrypt FIPS boundary
build that implements the required policy natively.
DES_crypt in des_shim.c is thread-safe in this implementation: it uses
crypt_r with a stack-allocated struct crypt_data buffer rather than the
traditional crypt(3) which uses a static internal buffer. Note that
DES_crypt is a Unix legacy API — applications should prefer the EVP or
wolfCrypt digest APIs for new code.
RSA key generation and EVP pkey operations use a per-thread WC_RNG
(implemented in shim/src/rng/shim_rng.c via pthread_key_t +
pthread_once_t) rather than a shared static WC_RNG guarded by a mutex.
This means:
- There is no mutex on the RSA/pkey key generation path — each thread
operates on its own
WC_RNGinstance without serialisation. - The per-thread RNG is seeded lazily on first use and destroyed by a
pthread_key_tdestructor when the thread exits. - Global mutable state (RAND method override, Montgomery context setup) still
uses
pthread_mutex_tas documented in the Thread Safety Policy inCONTRIBUTING.md.
Every fundamental limitation documented in this file — the AES_KEY heap leak, the SHA_CTX size mismatch, the RSA timing side-channel, the RAND_DRBG hierarchy gap, the AES-GCM throughput regression — shares the same root cause: the OpenSSL 1.1.1 ABI was not designed to be replaced at the primitive level. Structs are fixed-size, context ownership is implicit, and there is no hook for a third-party cryptographic backend to allocate its own state.
wolfProvider on OpenSSL 3 resolves these constraints at the source. The OpenSSL 3 provider API gives wolfCrypt its own context sizes, its own locking primitives, and its own constant-time implementations — without any of the struct-layout workarounds this shim requires. If any limitation here is a blocker for your deployment, wolfProvider is the correct long-term solution.
- Upstream:
https://github.com/openssl/openssl.git - Tag:
OpenSSL_1_1_1w - Commit:
e04bd3433fd84e1861bf258ea37928d9845e6a86 - Included as a git submodule — working tree is unmodified upstream.
Patches are applied by
build.shat build time and are not committed into the submodule.
OpenSSL 1.1.1 is end-of-life. That is intentional here: wolfCrypt is the actively maintained crypto layer; OpenSSL contributes only its protocol machinery.
To update the OpenSSL submodule to a new tag:
git -C openssl fetch --tags origin
git -C openssl checkout <new-tag>
git add openssl
git commit -m "chore: bump openssl submodule to <new-tag>"
# patches re-apply automatically on next build.sh run- Upstream:
https://github.com/wolfSSL/wolfssl.git - Tag:
v5.9.0-stable - Commit:
922d04b3568c6428a9fb905ddee3ef5a68db3108 - Included as a git submodule — no source modifications.
Configure flags used (recorded in build.sh):
--enable-opensslall --enable-opensslextra --enable-des3 --enable-arc4
--enable-md4 --enable-ripemd --enable-dsa --enable-rsa --enable-ecc
--enable-aesctr --enable-aescfb --enable-keygen --enable-debug
--enable-sha3 --enable-shake128 --enable-shake256
CFLAGS=-DOPENSSL_EXTRA
To update wolfSSL:
git -C wolfssl fetch --tags origin
git -C wolfssl checkout <new-tag>
git add wolfssl
git commit -m "chore: bump wolfssl submodule to <new-tag>"
./build.sh wolfssl && ./build.sh shim && ./build.sh opensslwolfSSL Inc. provides commercial support, consulting, integration services, non-recurring engineering (NRE), and porting work for this project and for wolfSSL itself. Commercial licenses for wolfSSL are also available.
| Need | Contact |
|---|---|
| General questions, porting, FIPS | facts@wolfssl.com |
| Commercial licensing | licensing@wolfssl.com |
| Technical support | support@wolfssl.com |
| Phone | +1 (425) 245-8247 |
| Web | https://www.wolfssl.com/contact/ |
The shim code in this repository — everything under shim/, the
patches/ directory, and the build scripts — is released under the
MIT License. See LICENSE for the full text.
Copyright (c) 2026 wolfssl-openssl1 contributors
SPDX-License-Identifier: MIT
The openssl/ submodule contains OpenSSL 1.1.1w, which is distributed
under its own dual license: the OpenSSL License and the original
SSLeay License (both BSD-style, with an advertising clause). These
licenses are not changed by this project. The full text is in
openssl/LICENSE. This shim does not relicense, modify, or distribute
OpenSSL source under any other terms.
The wolfssl/ submodule contains wolfSSL, which is distributed under
the GNU General Public License v3 for open-source use, or under a
commercial license available from wolfSSL Inc. for proprietary
deployments. These license terms are not changed by this project.
The full text of the GPLv3 is in wolfssl/COPYING.
Note: If you distribute a product that links this shim against wolfSSL under the GPLv3, the combined work is subject to the GPLv3's copyleft requirements. If that is not acceptable, obtain a commercial wolfSSL license from wolfssl.com. The MIT license on the shim itself does not override wolfSSL's terms.
The .clangd file at the repo root configures include paths for IDE
diagnostics. wolfSSL-only translation units (evp_wolf_bridge.c and anything
under shim/src/ that includes wolfssl/options.h) must not have
openssl/include on their include path — wolfSSL's OpenSSL-compat headers
conflict with OpenSSL's own asn1.h type definitions when both are visible
to the same compilation unit. The bridge pattern (one file per header world)
is intentional.
WOLFSHIM_DEBUG=1 can be added to CFLAGS when building the shim to enable
fprintf(stderr, ...) trace logging for every aliased symbol call:
gcc -DWOLFSHIM_DEBUG ... shim/src/aliases/aliases.c-
Non-x86 processor support. The shim is currently built and tested only on x86-64. Validate on at least ARM64 (aarch64) and RISC-V. Key risk areas: the
Aes.reg/Aes.leftfield-offset probes inaes_shim.c, the_Static_assertsize checks, and any assembly paths in wolfSSL that differ by architecture. The ABI-check constructor (wolfshim_abi_check.c) should catch layout regressions at load time, but a full test-suite run is needed on each new target. -
wolfSSL FIPS-ready build. Verify the shim builds and passes its test suite when wolfSSL is configured with
--enable-fips=ready. FIPS-ready mode enables the FIPS self-tests and enforces approved-algorithm restrictions; severalWOLFSHIM_GAP[UNSUPPORTED]paths (key wrap, certain cipher modes) may behave differently or return different error codes. -
wolfSSL FIPS bundle. Verify the shim against an official wolfSSL FIPS 140-3 source bundle (the validated module, not just FIPS-ready). The FIPS bundle has a fixed, validated source tree; the shim's wolfSSL version guard and
_Static_assertchecks must be re-baselined against the bundle's exact struct layouts. Document the validated bundle version and certificate number inARCHITECTURE.md.
-
Run the gap audit:
./shim/audit-gaps.sh
Review every
WOLFSHIM_GAP[SECURITY]andWOLFSHIM_REVIEW [ABI]site listed. These are the locations where wolfSSL internal struct layouts or behavioural assumptions are load-bearing. A wolfSSL change that silently shifts a struct field or changes an internal behaviour will pass the build only if these sites are re-validated. -
Check
_Static_assertfailures. The build will fail if wolfSSL changes struct field offsets that the shim accesses directly (Aes.reg,Aes.left,WOLFSSL_BIGNUM.neg, etc.). Update the offset constants after re-measuring with theoffsetofprobe described inshim/src/aes/aes_shim.c. -
Update the version guard in
aes_shim.c,ec_shim.c,des_shim.c, andbn_shim.c(LIBWOLFSSL_VERSION_HEX < 0x0XXXXXYYY) to reflect the new minimum version you have validated against. -
Verify the runtime ABI check passes. After rebuilding, load the shim and confirm no
[wolfshim] FATAL: wolfSSLmessage appears:LD_LIBRARY_PATH=openssl:wolfssl/src/.libs openssl/apps/openssl version
wolfshim_abi_check.cruns as a__attribute__((constructor))at library load time and aborts immediately if the version,Aesstruct size, orEC_GROUP.curve_nid/BIGNUM.negfield offsets disagree between the compile-time headers and the runtime.so.
| Tag | Meaning |
|---|---|
WOLFSHIM_GAP[SECURITY] |
Behavioural gap with a security implication — mandatory review |
WOLFSHIM_GAP[CORRECTNESS] |
Behavioural gap that may produce wrong output |
WOLFSHIM_GAP[UNSUPPORTED] |
Feature not implemented; returns ERR_R_DISABLED or an explicit error |
WOLFSHIM_REVIEW [ABI] |
Accesses wolfSSL struct internals — re-validate on every wolfSSL upgrade |