From 552ad4211166f4e829438cb13150e4f1295af698 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 12 Jun 2026 12:33:56 -0500 Subject: [PATCH] Fixes from review --- ChangeLog.md | 14 ++++++ doc/dox_comments/header_files/ssl.h | 59 +++++++++++++++++++++-- src/internal.c | 26 ++++++---- src/ssl_api_cert.c | 32 +++++++++++++ src/x509_str.c | 6 +++ tests/api.c | 7 ++- tests/api/test_tls13.c | 74 +++++++++++++++++++++++++++++ wolfssl/error-ssl.h | 5 +- wolfssl/openssl/x509.h | 5 +- wolfssl/ssl.h | 20 +++++--- 10 files changed, 223 insertions(+), 25 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index e54e4ffbd1..b3b562f67d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -122,6 +122,20 @@ PR stands for Pull Request, and PR references a GitHub pull request num * **BREAKING (FIPS 205 SLH-DSA)**: `wc_SlhDsaKey_SignHash`, `wc_SlhDsaKey_SignHashDeterministic`, `wc_SlhDsaKey_SignHashWithRandom`, and `wc_SlhDsaKey_VerifyHash` now take the **caller-pre-hashed message digest** via `hash`/`hashSz` parameters (renamed from `msg`/`msgSz`), aligned with ML-DSA's `wc_dilithium_sign_ctx_hash` / `wc_dilithium_verify_ctx_hash` semantics, and NIST ACVP `signatureInterface=external` / `preHash=preHash` test vectors. `hashSz` must equal `wc_HashGetDigestSize(hashType)` (32 bytes for SHAKE128, 64 bytes for SHAKE256 per FIPS 205 Section 10.2.2); otherwise `BAD_LENGTH_E` is returned. Migration: hash the message yourself before the call (callers using positional arguments are source-compatible; only the parameter names changed). Caveat: callers who today pass a raw message whose length happens to equal the digest size for the chosen `hashType` (e.g., signing a 32-byte handle/IV/seed with `WC_HASH_TYPE_SHA256`) will not trip `BAD_LENGTH_E`; the resulting signature is syntactically valid but is over the wrong bytes. The pre-existing `wc_SlhDsaKey_SignMsgDeterministic` and `wc_SlhDsaKey_SignMsgWithRandom` retain their M'-supplied-directly contract (FIPS 205 internal interface, Algorithm 19); their input validation is hardened with the same NULL/length/`MISSING_KEY` checks as the `*Hash*` family. `wc_SlhDsaKey_VerifyMsg` is unchanged. All three gain doxygen coverage. (PR 10450, PR 10465) +* **Behavioral change (Raw Public Key fail-closed)**: With `HAVE_RPK`, a peer + that presents a Raw Public Key (RFC 7250) is no longer silently accepted while + it is being authenticated. Previously an RPK handshake always completed and + the application validated the key out of band afterward (e.g. via + `wolfSSL_get_verify_result()`); it now fails closed (with `RPK_UNTRUSTED_E`, + reported as `WOLFSSL_X509_V_ERR_RPK_UNTRUSTED`) whenever the peer is being + authenticated, i.e. any verify mode other than `WOLFSSL_VERIFY_NONE`. This + applies to all `HAVE_RPK` builds, including those without `OPENSSL_EXTRA` (no + prior untrusted-RPK handling) and `NO_SHA256` builds (no in-library pinning). + To establish trust, pin the expected key with the new + `wolfSSL_set_expected_rpk()` / `wolfSSL_CTX_set_expected_rpk()` APIs + (requires SHA-256), install a verify callback that accepts the key, or set + `WOLFSSL_VERIFY_NONE` to accept without authentication. + * **Behavioral change (RSA-PSS trailerField enforcement)**: `DecodeRsaPssParams` (and its public wrapper `wc_DecodeRsaPssParams`) now enforces RFC 8017 A.2.3, which mandates `trailerField == trailerFieldBC(1)`. In the default build diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index e35c69708f..af245f5a47 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -16104,7 +16104,8 @@ int wolfSSL_get_negotiated_server_cert_type(WOLFSSL* ssl, int* tp); the handshake fails closed unless the presented key matches a pin (or a verify callback accepts it). May be called more than once to pin several keys, up to WOLFSSL_MAX_RPK_PINS. The key is stored as its SHA-256 digest, so this API - requires SHA-256. Pins are append-only for the lifetime of the CTX. + requires SHA-256. Pins are append-only - there is no per-entry remove, but + wolfSSL_CTX_clear_expected_rpk() empties the table so it can be repopulated. \return WOLFSSL_SUCCESS on success \return BAD_FUNC_ARG if ctx or spki is NULL, or spkiSz is 0 @@ -16118,13 +16119,14 @@ int wolfSSL_get_negotiated_server_cert_type(WOLFSSL* ssl, int* tp); \code int ret; WOLFSSL_CTX* ctx; - const unsigned char* spki; /* DER SubjectPublicKeyInfo */ + const unsigned char* spki; unsigned int spkiSz; ... ret = wolfSSL_CTX_set_expected_rpk(ctx, spki, spkiSz); \endcode \sa wolfSSL_set_expected_rpk + \sa wolfSSL_CTX_clear_expected_rpk \sa wolfSSL_set_client_cert_type \sa wolfSSL_set_server_cert_type */ @@ -16140,7 +16142,8 @@ int wolfSSL_CTX_set_expected_rpk(WOLFSSL_CTX* ctx, const unsigned char* spki, the handshake fails closed unless the presented key matches a pin (or a verify callback accepts it). May be called more than once to pin several keys, up to WOLFSSL_MAX_RPK_PINS. The key is stored as its SHA-256 digest, so this API - requires SHA-256. Pins are append-only for the lifetime of the object. + requires SHA-256. Pins are append-only - there is no per-entry remove, but + wolfSSL_clear_expected_rpk() empties the table so it can be repopulated. \return WOLFSSL_SUCCESS on success \return BAD_FUNC_ARG if ssl or spki is NULL, or spkiSz is 0 @@ -16154,19 +16157,67 @@ int wolfSSL_CTX_set_expected_rpk(WOLFSSL_CTX* ctx, const unsigned char* spki, \code int ret; WOLFSSL* ssl; - const unsigned char* spki; /* DER SubjectPublicKeyInfo */ + const unsigned char* spki; unsigned int spkiSz; ... ret = wolfSSL_set_expected_rpk(ssl, spki, spkiSz); \endcode \sa wolfSSL_CTX_set_expected_rpk + \sa wolfSSL_clear_expected_rpk \sa wolfSSL_set_client_cert_type \sa wolfSSL_set_server_cert_type */ int wolfSSL_set_expected_rpk(WOLFSSL* ssl, const unsigned char* spki, unsigned int spkiSz); +/*! + \ingroup Setup + \brief Remove all pinned expected peer Raw Public Keys (RFC 7250) from the + WOLFSSL_CTX object, emptying the table so it can be repopulated - for example + across a peer key rotation, since the pinning APIs are otherwise append-only. + Like other WOLFSSL_CTX setters this is not locked, so reconfigure the pins on a + shared CTX while no handshakes are in flight (a WOLFSSL created with + wolfSSL_new() copies the pin table by value at creation time). + + \return WOLFSSL_SUCCESS on success + \return BAD_FUNC_ARG if ctx is NULL + + \param ctx WOLFSSL_CTX object pointer + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + + wolfSSL_CTX_clear_expected_rpk(ctx); + \endcode + \sa wolfSSL_CTX_set_expected_rpk + \sa wolfSSL_clear_expected_rpk + */ +int wolfSSL_CTX_clear_expected_rpk(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + \brief Remove all pinned expected peer Raw Public Keys (RFC 7250) from the + WOLFSSL object, emptying the table so it can be repopulated - for example + across a peer key rotation, since the pinning APIs are otherwise append-only. + + \return WOLFSSL_SUCCESS on success + \return BAD_FUNC_ARG if ssl is NULL + + \param ssl WOLFSSL object pointer + _Example_ + \code + WOLFSSL* ssl; + ... + + wolfSSL_clear_expected_rpk(ssl); + \endcode + \sa wolfSSL_set_expected_rpk + \sa wolfSSL_CTX_clear_expected_rpk + */ +int wolfSSL_clear_expected_rpk(WOLFSSL* ssl); + /*! \brief Enable use of ConnectionID extensions for the SSL object. See RFC 9146 diff --git a/src/internal.c b/src/internal.c index 83ba848e88..1db828485e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15216,6 +15216,7 @@ void DoCertFatalAlert(WOLFSSL* ssl, int ret) alertWhy = certificate_expired; } else if (ret == WC_NO_ERR_TRACE(ASN_NO_SIGNER_E) || + ret == WC_NO_ERR_TRACE(RPK_UNTRUSTED_E) || ret == WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E) || ret == WC_NO_ERR_TRACE(ASN_PATHLEN_SIZE_E)) { alertWhy = unknown_ca; @@ -17815,16 +17816,18 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (!rpkTrusted && !ssl->options.verifyNone) { WOLFSSL_MSG("Untrusted RPK and peer verification " "is on; failing handshake"); - /* ASN_NO_SIGNER_E ("no trusted signer/anchor") is - * the closest existing error for an unpinned RPK. - * It is set here in the parse-SUCCESS branch, so the - * X.509 recovery that keys on this code (alternate - * chains, CA hash-dir lookup, Apple native - * validation) - all of which live in the - * parse-FAILURE branch above - is not reachable for - * RPK. The leaf verify callback is still invoked at - * TLS_ASYNC_FINALIZE and may override. */ - ret = ASN_NO_SIGNER_E; + /* Use a dedicated RPK error rather than an + * X.509 one (e.g. ASN_NO_SIGNER_E) so the failure is + * distinguishable: GetX509Error() maps it to + * WOLFSSL_X509_V_ERR_RPK_UNTRUSTED, so a verify + * callback that accepts X.509 issuer-lookup errors + * (ASN_NO_SIGNER_E / + * WOLFSSL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + * cannot accidentally accept an unauthenticated RPK. + * The leaf verify callback is still invoked at + * TLS_ASYNC_FINALIZE and may deliberately override + * this RPK-specific code. */ + ret = RPK_UNTRUSTED_E; WOLFSSL_ERROR_VERBOSE(ret); } } @@ -29023,6 +29026,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) case SEQUENCE_NUMBER_E: return "Record sequence number would wrap"; + + case RPK_UNTRUSTED_E: + return "RFC 7250 Raw Public Key not trusted"; } return "unknown error number"; diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index f2f6969112..b879e93eeb 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -505,6 +505,38 @@ int wolfSSL_set_expected_rpk(WOLFSSL* ssl, const unsigned char* spki, ret = rpk_add_expected(&ssl->options.rpkConfig, spki, spkiSz); return (ret == 0) ? WOLFSSL_SUCCESS : ret; } + +/* Remove all pinned expected peer Raw Public Keys from the SSL/TLS CTX object, + * so the table can be repopulated (e.g. across a peer key rotation). + * + * @param [in] ctx SSL/TLS CTX object. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL. + */ +int wolfSSL_CTX_clear_expected_rpk(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + ctx->rpkConfig.expectedRpkCnt = 0; + return WOLFSSL_SUCCESS; +} + +/* Remove all pinned expected peer Raw Public Keys from the SSL/TLS object, so + * the table can be repopulated (e.g. across a peer key rotation). + * + * @param [in] ssl SSL/TLS object. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ssl is NULL. + */ +int wolfSSL_clear_expected_rpk(WOLFSSL* ssl) +{ + if (ssl == NULL) { + return BAD_FUNC_ARG; + } + ssl->options.rpkConfig.expectedRpkCnt = 0; + return WOLFSSL_SUCCESS; +} #endif /* !NO_SHA256 */ #endif /* HAVE_RPK */ diff --git a/src/x509_str.c b/src/x509_str.c index 30055ba840..710d18b74c 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -282,6 +282,12 @@ int GetX509Error(int e) case WC_NO_ERR_TRACE(ASN_NO_SIGNER_E): /* get issuer error if no CA found locally */ return WOLFSSL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY; + case WC_NO_ERR_TRACE(RPK_UNTRUSTED_E): + /* RFC 7250 Raw Public Key not trusted out of band. Distinct from + * the X.509 issuer-lookup error above so verify callbacks that + * accept ASN_NO_SIGNER_E / UNABLE_TO_GET_ISSUER_CERT_LOCALLY do not + * accidentally accept an unauthenticated RPK. */ + return WOLFSSL_X509_V_ERR_RPK_UNTRUSTED; case WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E): return WOLFSSL_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT; case WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E): diff --git a/tests/api.c b/tests/api.c index ffbe568c8c..4bd5c6fecb 100644 --- a/tests/api.c +++ b/tests/api.c @@ -28912,7 +28912,12 @@ static int error_test(void) } } -#if defined(OPENSSL_EXTRA) + /* Guard matches the compilation condition of the OpenSSL-style reason + * strings (wolfSSL_ERR_reason_error_string_OpenSSL), so the RPK string is + * exercised in OPENSSL_EXTRA_X509_SMALL / webserver / memcached builds too, + * not only OPENSSL_EXTRA. */ +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ + defined(HAVE_WEBSERVER) || defined(HAVE_MEMCACHED) /* WOLFSSL_X509_V_ERR_RPK_UNTRUSTED is >= WC_OSSL_V509_V_ERR_MAX, so it is * intentionally outside the contiguous sweep above. Check its reason string * explicitly so it cannot regress silently. error_test() is invoked as diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 901f7d9495..9c56102a06 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3703,6 +3703,27 @@ static int test_rpk_accept_cb(int preverify, WOLFSSL_X509_STORE_CTX* store) (void)store; return 1; } + +/* Mimics a real-world X.509 verify callback that accepts ONLY the "issuer not + * found locally" errors (the pattern used elsewhere to allow an unchained or + * self-signed leaf). It must NOT rescue an untrusted RPK: because the RPK + * failure now reports WOLFSSL_X509_V_ERR_RPK_UNTRUSTED (not the X.509 + * issuer-lookup codes), these checks no longer match and the RPK stays rejected. + * Returns 1 (accept) only for the X.509 issuer codes, otherwise rejects. */ +static int test_rpk_x509_issuer_accept_cb(int preverify, + WOLFSSL_X509_STORE_CTX* store) +{ + (void)preverify; + if ((store->error == WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)) +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + || (store->error == + WOLFSSL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) +#endif + ) { + return 1; /* would wrongly accept an RPK that reused these codes */ + } + return 0; /* reject everything else, including WOLFSSL_X509_V_ERR_RPK_* */ +} #endif /* HAVE_RPK && WOLFSSL_TLS13 && client && server && !NO_SHA256 */ #if defined(HAVE_RPK) && \ @@ -3926,6 +3947,31 @@ int test_tls13_rpk_trust(void) wolfSSL_CTX_free(ctx_c); wolfSSL_CTX_free(ctx_s); + /* --- a callback that accepts only X.509 issuer-lookup errors must NOT + * rescue an untrusted RPK: the RPK failure reports its own error code, so + * the callback's X.509 checks do not match and the handshake fails closed. + * (Would complete if the RPK reused ASN_NO_SIGNER_E / the issuer-lookup + * verify-result code.) --- */ + ctx_c = ctx_s = NULL; + ssl_c = ssl_s = NULL; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_rpk_nopin_setup(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, + test_rpk_x509_issuer_accept_cb); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, + test_rpk_x509_issuer_accept_cb); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + ExpectIntEQ(wolfSSL_get_verify_result(ssl_c), + WOLFSSL_X509_V_ERR_RPK_UNTRUSTED); +#endif + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + /* --- SSL-level pin via wolfSSL_set_expected_rpk() -> V_OK --- */ ctx_c = ctx_s = NULL; ssl_c = ssl_s = NULL; @@ -3995,9 +4041,37 @@ int test_tls13_rpk_trust(void) } ExpectIntEQ(wolfSSL_set_expected_rpk(ssl_c, svrSpki, (unsigned int)svrSpkiSz), WC_NO_ERR_TRACE(BUFFER_E)); + /* clear empties the (full) table so an add succeeds again */ + ExpectIntEQ(wolfSSL_clear_expected_rpk(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_clear_expected_rpk(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_expected_rpk(ssl_c, svrSpki, + (unsigned int)svrSpkiSz), WOLFSSL_SUCCESS); wolfSSL_free(ssl_c); wolfSSL_CTX_free(ctx_c); + /* same argument/capacity errors for the CTX-level wrapper */ + ctx_c = NULL; + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_expected_rpk(NULL, svrSpki, + (unsigned int)svrSpkiSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_expected_rpk(ctx_c, NULL, + (unsigned int)svrSpkiSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_expected_rpk(ctx_c, svrSpki, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + for (i = 0; i < WOLFSSL_MAX_RPK_PINS; i++) { + ExpectIntEQ(wolfSSL_CTX_set_expected_rpk(ctx_c, svrSpki, + (unsigned int)svrSpkiSz), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_CTX_set_expected_rpk(ctx_c, svrSpki, + (unsigned int)svrSpkiSz), WC_NO_ERR_TRACE(BUFFER_E)); + /* clear empties the (full) table so an add succeeds again */ + ExpectIntEQ(wolfSSL_CTX_clear_expected_rpk(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_clear_expected_rpk(ctx_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_expected_rpk(ctx_c, svrSpki, + (unsigned int)svrSpkiSz), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(ctx_c); + XFREE(svrSpki, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(cliSpki, NULL, DYNAMIC_TYPE_TMP_BUFFER); #endif /* HAVE_RPK && WOLFSSL_TLS13 && client && server && !NO_SHA256 */ diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 2bb162c1ba..2783f00813 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -247,7 +247,10 @@ enum wolfSSL_ErrorCodes { SEQUENCE_NUMBER_E = -520, /* Record sequence number would wrap */ - WOLFSSL_LAST_E = -520 + RPK_UNTRUSTED_E = -521, /* RFC 7250 Raw Public Key not trusted + * out of band */ + + WOLFSSL_LAST_E = -521 /* codes -1000 to -1999 are reserved for wolfCrypt. */ }; diff --git a/wolfssl/openssl/x509.h b/wolfssl/openssl/x509.h index 4a74d46ce7..5dd01797cc 100644 --- a/wolfssl/openssl/x509.h +++ b/wolfssl/openssl/x509.h @@ -210,9 +210,8 @@ #define X509_V_ERR_CA_CERT_MISSING_KEY_USAGE 92 #define X509_V_ERR_EXTENSIONS_REQUIRE_VERSION_3 93 #define X509_V_ERR_EC_KEY_EXPLICIT_PARAMS 94 -/* wolfSSL-specific value: this header's numbering already diverges from - * OpenSSL's for the higher X509_V_ERR_* codes, so RPK-untrusted takes the next - * free wolfSSL slot (95) rather than any particular OpenSSL number. */ +/* 95 matches OpenSSL's X509_V_ERR_RPK_UNTRUSTED (OpenSSL 3.2+) for source + * compatibility. */ #define X509_V_ERR_RPK_UNTRUSTED 95 #define X509_R_CERT_ALREADY_IN_HASH_TABLE 101 #define X509_R_KEY_VALUES_MISMATCH WC_KEY_MISMATCH_E diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 1e31d9e7a8..e824856d8d 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2782,9 +2782,9 @@ enum { WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH = 64, WOLFSSL_X509_V_ERR_INVALID_CA = 79, WC_OSSL_V509_V_ERR_MAX = 80, - /* wolfSSL-specific verify-result codes are assigned at or above - * WC_OSSL_V509_V_ERR_MAX, intentionally outside the contiguous - * OpenSSL-compatible range below it. */ + /* 95 matches OpenSSL's X509_V_ERR_RPK_UNTRUSTED (OpenSSL 3.2+). It is + * deliberately at or above WC_OSSL_V509_V_ERR_MAX, i.e. outside the + * contiguous block of verify-result codes below it. */ WOLFSSL_X509_V_ERR_RPK_UNTRUSTED = 95, #ifdef HAVE_OCSP @@ -6258,15 +6258,23 @@ WOLFSSL_API int wolfSSL_get_negotiated_server_cert_type(WOLFSSL* ssl, int* tp); * stored as its SHA-256 digest, so these APIs require SHA-256; without it, * express RPK trust through a verify callback. Returns WOLFSSL_SUCCESS, or a * negative error code (BAD_FUNC_ARG for a NULL argument or zero length, BUFFER_E - * when the pin table is full). Pins are append-only for the lifetime of the - * object/CTX; there is no clear/replace call, so a long-lived CTX that re-pins - * across key rotations can exhaust the WOLFSSL_MAX_RPK_PINS-entry table. */ + * when the pin table is full). Pins are append-only: there is no per-entry + * remove, but wolfSSL_clear_expected_rpk()/wolfSSL_CTX_clear_expected_rpk() + * empties the WOLFSSL_MAX_RPK_PINS-entry table so it can be repopulated (e.g. + * across a peer key rotation). */ WOLFSSL_API int wolfSSL_CTX_set_expected_rpk(WOLFSSL_CTX* ctx, const unsigned char* spki, unsigned int spkiSz); WOLFSSL_API int wolfSSL_set_expected_rpk(WOLFSSL* ssl, const unsigned char* spki, unsigned int spkiSz); +/* Remove all pinned expected peer Raw Public Keys, emptying the table so it can + * be repopulated. Returns WOLFSSL_SUCCESS, or BAD_FUNC_ARG when the handle is + * NULL. Like the set calls and other CTX setters these are unlocked, so + * reconfigure pins on a shared CTX while no handshakes are in flight (each + * WOLFSSL copies the pin table by value at wolfSSL_new()). */ +WOLFSSL_API int wolfSSL_CTX_clear_expected_rpk(WOLFSSL_CTX* ctx); +WOLFSSL_API int wolfSSL_clear_expected_rpk(WOLFSSL* ssl); #endif /* !NO_SHA256 */ #endif /* HAVE_RPK */