diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 4432aa4e697..c8102748171 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -81,6 +81,15 @@ {"name": "dtls13-ocspstapling-cert-cb", "minutes": 3.1, "configure": ["--enable-dtls", "--enable-dtls13", "--enable-ocspstapling", "--enable-ocspstapling2", "--enable-cert-setup-cb", "--enable-sessioncerts"]}, +{"name": "chain-verify-cb", "minutes": 3.1, + "comment": "postauth, stapling and rpk exercise the callback's resume-from-read and fail-hard paths.", + "configure": ["--enable-chain-verify-cb", "--enable-opensslextra", + "--enable-sessioncerts", "--enable-smallstack", "--enable-postauth", + "--enable-ocspstapling", "--enable-rpk"]}, +{"name": "chain-verify-cb-nonblock-ocsp", "minutes": 3.1, + "comment": "chain verify callback alongside the other handshake-suspend feature, and DTLS.", + "configure": ["--enable-chain-verify-cb", "--enable-ocsp", "--enable-crl", + "--enable-dtls", "CPPFLAGS=-DWOLFSSL_NONBLOCK_OCSP"]}, {"name": "tsp-verifier", "minutes": 3, "comment": "Time-Stamp Protocol Verifier", "configure": ["--enable-tsp", "--enable-opensslall", diff --git a/CMakeLists.txt b/CMakeLists.txt index a35e365e017..114db300b8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1654,6 +1654,11 @@ add_option("WOLFSSL_CERTGENCACHE" "Enable decoded cert caching (default: disabled)" "no" "yes;no") +# Chain verify callback +add_option("WOLFSSL_CHAIN_VERIFY_CB" + "Enable replacing peer certificate verification with a user callback (default: disabled)" + "no" "yes;no") + # HKDF add_option("WOLFSSL_HKDF" "Enable HKDF (HMAC-KDF) support (default: disabled)" @@ -3118,6 +3123,10 @@ if(WOLFSSL_CERTGENCACHE) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CERT_GEN_CACHE") endif() +if(WOLFSSL_CHAIN_VERIFY_CB) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CHAIN_VERIFY_CB") +endif() + if(WOLFSSL_CRYPTOCB) list(APPEND WOLFSSL_DEFINITIONS "-DWOLF_CRYPTO_CB") endif() diff --git a/cmake/options.h.in b/cmake/options.h.in index 78f4ce3c55b..1cf1f27ca91 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -775,6 +775,9 @@ extern "C" { #cmakedefine WOLFSSL_ALWAYS_VERIFY_CB #undef WOLFSSL_CERT_SETUP_CB #cmakedefine WOLFSSL_CERT_SETUP_CB + +#undef WOLFSSL_CHAIN_VERIFY_CB +#cmakedefine WOLFSSL_CHAIN_VERIFY_CB #undef WOLFSSL_CIPHER_INTERNALNAME #cmakedefine WOLFSSL_CIPHER_INTERNALNAME #undef WOLFSSL_DER_LOAD diff --git a/configure.ac b/configure.ac index ea998c287ea..4d17483d880 100644 --- a/configure.ac +++ b/configure.ac @@ -1413,6 +1413,7 @@ then test "$enable_savesession" = "" && enable_savesession=yes test "$enable_savecert" = "" && enable_savecert=yes test "$enable_postauth" = "" && enable_postauth=yes + test "$enable_chain_verify_cb" = "" && enable_chain_verify_cb=yes test "$enable_hrrcookie" = "" && enable_hrrcookie=yes test "$enable_crl_monitor" = "" && enable_crl_monitor=yes test "$enable_sni" = "" && enable_sni=yes @@ -12206,6 +12207,13 @@ AC_ARG_ENABLE([cert-setup-cb], [ ENABLED_CERT_SETUP_CB=no ] ) +# Replaces peer certificate chain verification with an application callback +AC_ARG_ENABLE([chain-verify-cb], + [AS_HELP_STRING([--enable-chain-verify-cb],[Enable replacing peer certificate verification with a user callback (default: disabled)])], + [ ENABLED_CHAIN_VERIFY_CB=$enableval ], + [ ENABLED_CHAIN_VERIFY_CB=no ] + ) + # check if should run the trusted peer certs test # (for now checking both C_FLAGS and C_EXTRA_FLAGS) AS_CASE(["$CFLAGS $CPPFLAGS"],[*'WOLFSSL_TRUST_PEER_CERT'*],[ENABLED_TRUSTED_PEER_CERT=yes]) @@ -12715,6 +12723,9 @@ AS_IF([test "x$ENABLED_RPK" = "xyes"], AS_IF([test "x$ENABLED_CERT_SETUP_CB" = "xyes"], [AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CERT_SETUP_CB"]) +AS_IF([test "x$ENABLED_CHAIN_VERIFY_CB" = "xyes"], + [AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CHAIN_VERIFY_CB"]) + AS_IF([test "x$ENABLED_ALTNAMES" = "xyes"], [AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALT_NAMES"]) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index a58c6b4f202..4470c482209 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -3187,6 +3187,159 @@ void wolfSSL_SetCertCbCtx(WOLFSSL* ssl, void* ctx); */ void wolfSSL_CTX_SetCertCbCtx(WOLFSSL_CTX* ctx, void* userCtx); +/*! + \ingroup CertsKeys + + \brief Replaces wolfSSL's verification of the peer's certificate chain with + an application callback, for every SSL/TLS object created from the context. + When a callback is set, wolfSSL decodes the certificates from the + Certificate message and hands them to the callback as raw DER, the peer's + own certificate first. It builds no chain, verifies no signature and checks + no date, revocation status, key usage or host name, and the verify callback + set with wolfSSL_CTX_set_verify() is not called; the callback is consulted + even under WOLFSSL_VERIFY_NONE. Malformed DER, or a certificate the parser + refuses regardless of the verify mode such as one with a zero serial + number, still fails the handshake before the callback is called. The + callback returns 0 to accept, CHAIN_VERIFY_WANT_E to suspend the handshake + until the application re-enters wolfSSL_connect(), wolfSSL_accept(), + wolfSSL_read() or wolfSSL_write() (a certificate received after the + handshake is resumed by wolfSSL_read() only, and wolfSSL_write() fails with + CHAIN_VERIFY_WANT_E until then), or any other value to reject with + CHAIN_VERIFY_CB_E and a fatal bad_certificate alert. DTLS, raw public keys + and OCSP stapling are not supported with the callback: setting it on a + context configured for one of them fails, and so does the handshake of a + connection using one of them, with CHAIN_VERIFY_UNSUPPORTED_E. Requires + WOLFSSL_CHAIN_VERIFY_CB (--enable-chain-verify-cb). + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when ctx is NULL. + \return CHAIN_VERIFY_UNSUPPORTED_E when the context uses a DTLS method, + raw public keys or OCSP stapling. + + \param ctx pointer to the SSL context, created with wolfSSL_CTX_new(). + \param cb the callback, or NULL to clear it. + + _Example_ + \code + static int myChainVerify(WOLFSSL* ssl, const WOLFSSL_BUFFER_INFO* certs, + int certsSz, void* ctx) + { + // certs[0] is the peer's certificate, the rest is the chain it sent + if (!hsmVerifyStarted(certs, certsSz)) + return CHAIN_VERIFY_WANT_E; // ask again later + return hsmVerifyPassed() ? 0 : -1; + } + ... + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(method); + if (wolfSSL_CTX_SetChainVerifyCb(ctx, myChainVerify) != WOLFSSL_SUCCESS) { + // context uses DTLS, raw public keys or OCSP stapling + } + \endcode + + \sa wolfSSL_SetChainVerifyCb + \sa wolfSSL_CTX_SetChainVerifyCtx + \sa wolfSSL_SetChainVerifyCtx + \sa wolfSSL_GetChainVerifyCtx +*/ +int wolfSSL_CTX_SetChainVerifyCb(WOLFSSL_CTX* ctx, ChainVerifyCb cb); + +/*! + \ingroup CertsKeys + + \brief Sets the chain verification callback for one SSL/TLS object. It + takes precedence over the callback set on the context with + wolfSSL_CTX_SetChainVerifyCb(), which documents the callback's contract. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when ssl is NULL. + \return CHAIN_VERIFY_UNSUPPORTED_E when the object uses DTLS, raw public + keys or OCSP stapling. + + \param ssl pointer to the SSL session, created with wolfSSL_new(). + \param cb the callback, or NULL to fall back to the context's. + + _Example_ + \code + WOLFSSL* ssl = wolfSSL_new(ctx); + if (wolfSSL_SetChainVerifyCb(ssl, myChainVerify) != WOLFSSL_SUCCESS) { + // object uses DTLS, raw public keys or OCSP stapling + } + \endcode + + \sa wolfSSL_CTX_SetChainVerifyCb + \sa wolfSSL_SetChainVerifyCtx +*/ +int wolfSSL_SetChainVerifyCb(WOLFSSL* ssl, ChainVerifyCb cb); + +/*! + \ingroup CertsKeys + + \brief Sets the user context passed to the chain verification callback + for every SSL/TLS object created from the context. + + \return none No return. + + \param ctx pointer to the SSL context, created with wolfSSL_CTX_new(). + \param userCtx the value the callback receives as its ctx argument. + + _Example_ + \code + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(method); + wolfSSL_CTX_SetChainVerifyCb(ctx, myChainVerify); + wolfSSL_CTX_SetChainVerifyCtx(ctx, &myHsm); + \endcode + + \sa wolfSSL_CTX_SetChainVerifyCb + \sa wolfSSL_SetChainVerifyCtx + \sa wolfSSL_GetChainVerifyCtx +*/ +void wolfSSL_CTX_SetChainVerifyCtx(WOLFSSL_CTX* ctx, void* userCtx); + +/*! + \ingroup CertsKeys + + \brief Sets the user context passed to the chain verification callback + for one SSL/TLS object. It takes precedence over the value set on the + context with wolfSSL_CTX_SetChainVerifyCtx(). + + \return none No return. + + \param ssl pointer to the SSL session, created with wolfSSL_new(). + \param ctx the value the callback receives as its ctx argument, or NULL + to fall back to the context's. + + _Example_ + \code + WOLFSSL* ssl = wolfSSL_new(ctx); + wolfSSL_SetChainVerifyCtx(ssl, &myConnectionState); + \endcode + + \sa wolfSSL_CTX_SetChainVerifyCtx + \sa wolfSSL_GetChainVerifyCtx +*/ +void wolfSSL_SetChainVerifyCtx(WOLFSSL* ssl, void* ctx); + +/*! + \ingroup CertsKeys + + \brief Returns the user context the chain verification callback is + called with for this SSL/TLS object: the value set with + wolfSSL_SetChainVerifyCtx() when there is one, otherwise the context's. + + \return void* the user context, or NULL when none is set or ssl is NULL. + + \param ssl pointer to the SSL session, created with wolfSSL_new(). + + _Example_ + \code + struct myState* st = (struct myState*)wolfSSL_GetChainVerifyCtx(ssl); + \endcode + + \sa wolfSSL_SetChainVerifyCtx + \sa wolfSSL_CTX_SetChainVerifyCtx +*/ +void* wolfSSL_GetChainVerifyCtx(const WOLFSSL* ssl); + /*! \ingroup IO diff --git a/examples/configs/user_settings_all.h b/examples/configs/user_settings_all.h index a0574f80882..71ccab5f1a0 100644 --- a/examples/configs/user_settings_all.h +++ b/examples/configs/user_settings_all.h @@ -126,6 +126,8 @@ extern "C" { #define HAVE_OID_ENCODING #define WOLFSSL_ASN_TEMPLATE #define WOLFSSL_ALT_NAMES /* Support subject alternative names extension */ +/* Replace peer chain verification with an application callback */ +#define WOLFSSL_CHAIN_VERIFY_CB /* Certificate Revocation */ #define HAVE_OCSP diff --git a/src/internal.c b/src/internal.c index c78193cd183..f68452b9f89 100644 --- a/src/internal.c +++ b/src/internal.c @@ -645,6 +645,128 @@ int IsTLS_ex(const ProtocolVersion pv) } +int IsHsSuspendErr(int err) +{ +#ifdef WOLFSSL_ASYNC_CRYPT + if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) + return 1; +#endif +#ifdef WOLFSSL_NONBLOCK_OCSP + if (err == WC_NO_ERR_TRACE(OCSP_WANT_READ)) + return 1; +#endif +#ifdef WOLFSSL_CHAIN_VERIFY_CB + if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) + return 1; +#endif + (void)err; + return 0; +} + +#ifdef WOLFSSL_CHAIN_VERIFY_CB +/* DTLS, raw public keys and OCSP stapling are not supported with the chain + * verify callback. Checked when the callback is set, against what the context + * or object is configured for, and again when the peer's certificates arrive, + * against what was negotiated, so that using them fails as early as + * possible. */ +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) +static int ChainVerifyCbStaplingRequested(TLSX* extensions) +{ +#if !defined(NO_TLS) && defined(HAVE_CERTIFICATE_STATUS_REQUEST) + if (TLSX_Find(extensions, TLSX_STATUS_REQUEST) != NULL) + return 1; +#endif +#if !defined(NO_TLS) && defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) + if (TLSX_Find(extensions, TLSX_STATUS_REQUEST_V2) != NULL) + return 1; +#endif + (void)extensions; + return 0; +} +#endif + +#ifdef HAVE_RPK +static int ChainVerifyCbRpkConfigured(const RpkConfig* cfg) +{ + int i; + + for (i = 0; i < cfg->preferred_ClientCertTypeCnt; i++) { + if (cfg->preferred_ClientCertTypes[i] == WOLFSSL_CERT_TYPE_RPK) + return 1; + } + for (i = 0; i < cfg->preferred_ServerCertTypeCnt; i++) { + if (cfg->preferred_ServerCertTypes[i] == WOLFSSL_CERT_TYPE_RPK) + return 1; + } + return 0; +} +#endif + +int ChainVerifyCbCheckCtx(const WOLFSSL_CTX* ctx) +{ +#ifdef WOLFSSL_DTLS + if (ctx->method->version.major == DTLS_MAJOR) { + WOLFSSL_MSG("DTLS not supported with chain verify callback"); + return CHAIN_VERIFY_UNSUPPORTED_E; + } +#endif +#ifdef HAVE_RPK + if (ChainVerifyCbRpkConfigured(&ctx->rpkConfig)) { + WOLFSSL_MSG("Raw public key not supported with chain verify callback"); + return CHAIN_VERIFY_UNSUPPORTED_E; + } +#endif +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) + if ((ctx->method->side != WOLFSSL_SERVER_END) && + (((ctx->cm != NULL) && ctx->cm->ocspMustStaple) || + ChainVerifyCbStaplingRequested(ctx->extensions))) { + WOLFSSL_MSG("OCSP stapling not supported with chain verify callback"); + return CHAIN_VERIFY_UNSUPPORTED_E; + } +#endif + (void)ctx; + return 0; +} + +int ChainVerifyCbCheckSsl(const WOLFSSL* ssl) +{ +#ifdef WOLFSSL_DTLS + if (ssl->options.dtls) { + WOLFSSL_MSG("DTLS not supported with chain verify callback"); + return CHAIN_VERIFY_UNSUPPORTED_E; + } +#endif +#ifdef HAVE_RPK + if (ChainVerifyCbRpkConfigured(&ssl->options.rpkConfig) || + ((ssl->options.side == WOLFSSL_CLIENT_END) && + (ssl->options.rpkState.received_ServerCertTypeCnt == 1) && + (ssl->options.rpkState.received_ServerCertTypes[0] == + WOLFSSL_CERT_TYPE_RPK)) || + ((ssl->options.side == WOLFSSL_SERVER_END) && + (ssl->options.rpkState.sending_ClientCertTypeCnt == 1) && + (ssl->options.rpkState.sending_ClientCertTypes[0] == + WOLFSSL_CERT_TYPE_RPK))) { + WOLFSSL_MSG("Raw public key not supported with chain verify callback"); + return CHAIN_VERIFY_UNSUPPORTED_E; + } +#endif +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) + if ((ssl->options.side != WOLFSSL_SERVER_END) && + (SSL_CM(ssl)->ocspMustStaple || + ChainVerifyCbStaplingRequested(ssl->extensions) || + ChainVerifyCbStaplingRequested(ssl->ctx->extensions))) { + WOLFSSL_MSG("OCSP stapling not supported with chain verify callback"); + return CHAIN_VERIFY_UNSUPPORTED_E; + } +#endif + (void)ssl; + return 0; +} +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ + int IsAtLeastTLSv1_2(const WOLFSSL* ssl) { if (ssl->version.major == SSLv3_MAJOR && ssl->version.minor >=TLSv1_2_MINOR) @@ -15955,6 +16077,29 @@ int InitSigPkCb(WOLFSSL* ssl, SignatureCtx* sigCtx) #endif /* HAVE_PK_CALLBACKS */ #if !defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH) + +#ifdef WOLFSSL_CHAIN_VERIFY_CB +/* The SSL object's callback and user context take precedence over the + * context's. */ +static ChainVerifyCb GetChainVerifyCb(const WOLFSSL* ssl) +{ + if (ssl->chainVerifyCb != NULL) + return ssl->chainVerifyCb; + return ssl->ctx->chainVerifyCb; +} + +static void* GetChainVerifyCtx(const WOLFSSL* ssl) +{ + if (ssl->chainVerifyCtx != NULL) + return ssl->chainVerifyCtx; + return ssl->ctx->chainVerifyCtx; +} + +/* Non-zero when the application has replaced chain verification. */ +#define UsingChainVerifyCb(ssl) (GetChainVerifyCb(ssl) != NULL) +#else +#define UsingChainVerifyCb(ssl) 0 +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ void DoCertFatalAlert(WOLFSSL* ssl, int ret) { int alertWhy; @@ -15985,6 +16130,12 @@ void DoCertFatalAlert(WOLFSSL* ssl, int ret) alertWhy = unsupported_certificate; } #endif /* HAVE_RPK */ +#ifdef WOLFSSL_CHAIN_VERIFY_CB + else if (ret == WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)) { + /* a local configuration problem, not the peer's certificate */ + alertWhy = internal_error; + } +#endif else if (ret == WC_NO_ERR_TRACE(NO_PEER_CERT)) { #ifdef WOLFSSL_TLS13 if (ssl->options.tls1_3) { @@ -17469,7 +17620,8 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, /* check size of peer RSA key */ if (ret == 0 && ssl->peerRsaKeyPresent && - !ssl->options.verifyNone && + (!ssl->options.verifyNone || + UsingChainVerifyCb(ssl)) && wc_RsaEncryptSize(ssl->peerRsaKey) < ssl->options.minRsaKeySz) { ret = RSA_KEY_SIZE_E; @@ -17548,7 +17700,8 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, /* check size of peer ECC key */ if (ret == 0 && ssl->peerEccDsaKeyPresent && - !ssl->options.verifyNone && + (!ssl->options.verifyNone || + UsingChainVerifyCb(ssl)) && wc_ecc_size(ssl->peerEccDsaKey) < ssl->options.minEccKeySz) { ret = ECC_KEY_SIZE_E; @@ -17608,7 +17761,7 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, /* check size of peer ECC key */ if (ret == 0 && ssl->peerEd25519KeyPresent && - !ssl->options.verifyNone && + (!ssl->options.verifyNone || UsingChainVerifyCb(ssl)) && ED25519_KEY_SIZE < ssl->options.minEccKeySz) { ret = ECC_KEY_SIZE_E; WOLFSSL_ERROR_VERBOSE(ret); @@ -17666,7 +17819,7 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, /* check size of peer ECC key */ if (ret == 0 && ssl->peerEd448KeyPresent && - !ssl->options.verifyNone && + (!ssl->options.verifyNone || UsingChainVerifyCb(ssl)) && ED448_KEY_SIZE < ssl->options.minEccKeySz) { ret = ECC_KEY_SIZE_E; WOLFSSL_ERROR_VERBOSE(ret); @@ -17719,7 +17872,8 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, /* check size of peer Falcon key */ if (ret == 0 && ssl->peerFalconKeyPresent && - !ssl->options.verifyNone && + (!ssl->options.verifyNone || + UsingChainVerifyCb(ssl)) && FALCON_MAX_KEY_SIZE < ssl->options.minFalconKeySz) { ret = FALCON_KEY_SIZE_E; @@ -17793,7 +17947,8 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, /* check size of peer Dilithium key */ if (ret == 0 && ssl->peerMlDsaKeyPresent && - !ssl->options.verifyNone && + (!ssl->options.verifyNone || + UsingChainVerifyCb(ssl)) && MLDSA_MAX_KEY_SIZE < ssl->options.minMlDsaKeySz) { ret = MLDSA_KEY_SIZE_E; @@ -17899,11 +18054,94 @@ static int RpkIsTrusted(WOLFSSL* ssl, const byte* spki, word32 spkiSz) } #endif /* HAVE_RPK */ +#ifdef WOLFSSL_CHAIN_VERIFY_CB +/* Errors from ParseCert(NO_VERIFY) that report content wolfSSL does not + * understand or agree with rather than malformed DER: an unknown critical + * extension, an unsupported key or signature algorithm, mismatched signature + * algorithm identifiers, or keyCertSign asserted by a non-CA. The certificate + * decoded; judging its content is the chain verify callback's job. */ +static int IsCertContentErr(int err) +{ + return (err == WC_NO_ERR_TRACE(ASN_CRIT_EXT_E)) || + (err == WC_NO_ERR_TRACE(ASN_SIG_OID_E)) || + (err == WC_NO_ERR_TRACE(ASN_UNKNOWN_OID_E)) || + (err == WC_NO_ERR_TRACE(KEYUSAGE_E)); +} + +/* Decode every certificate the peer sent, verifying nothing. The chain verify + * callback replaces wolfSSL's trust decision, not its parsing, so DER that + * wolfSSL cannot decode fails the handshake before the callback is consulted. + * NO_VERIFY skips the signature, issuer and date checks but still walks the + * whole structure. */ +static int CheckPeerCertsDecode(WOLFSSL* ssl, ProcPeerCertArgs* args) +{ + WC_DECLARE_VAR(cert, DecodedCert, 1, ssl->heap); + int ret = 0; + int i; + + WC_ALLOC_VAR_EX(cert, DecodedCert, 1, ssl->heap, DYNAMIC_TYPE_DCERT, + return MEMORY_E); + + for (i = 0; i < args->totalCerts; i++) { + InitDecodedCert(cert, args->certs[i].buffer, args->certs[i].length, + ssl->heap); + ret = ParseCert(cert, CERT_TYPE, NO_VERIFY, NULL); + if (IsCertContentErr(ret)) + ret = 0; + #ifdef HAVE_RPK + if ((ret == 0) && cert->isRPK) { + WOLFSSL_MSG("Raw public key not supported with chain verify " + "callback"); + ret = CHAIN_VERIFY_UNSUPPORTED_E; + } + #endif + FreeDecodedCert(cert); + + if (ret != 0) { + WOLFSSL_MSG_EX("Peer certificate %d failed to decode", i); + WOLFSSL_ERROR_VERBOSE(ret); + break; + } + } + + WC_FREE_VAR_EX(cert, ssl->heap, DYNAMIC_TYPE_DCERT); + + return ret; +} + +/* Hand the peer's DER certificates to the application, which has taken over + * chain verification completely. Called once per Certificate message, and + * again on every re-entry while the callback defers its verdict. + * + * Returns 0 when accepted, CHAIN_VERIFY_WANT_E to suspend the handshake, + * CHAIN_VERIFY_CB_E when rejected, or MEMORY_E. */ +static int DoChainVerifyCb(WOLFSSL* ssl, ProcPeerCertArgs* args) +{ + int ret; + + WOLFSSL_MSG("Calling user chain verify callback"); + ret = GetChainVerifyCb(ssl)(ssl, args->certs, args->totalCerts, + GetChainVerifyCtx(ssl)); + if (ret == 0) { + WOLFSSL_MSG("\tuser chain verify callback accepted"); + return 0; + } + if (ret == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + WOLFSSL_MSG("\tuser chain verify callback has no verdict yet"); + return CHAIN_VERIFY_WANT_E; + } + + WOLFSSL_MSG("\tuser chain verify callback rejected"); + WOLFSSL_ERROR_VERBOSE(CHAIN_VERIFY_CB_E); + return CHAIN_VERIFY_CB_E; +} +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ + int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 totalSz) { int ret = 0; -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) +#ifdef WOLFSSL_HAVE_HS_SUSPEND ProcPeerCertArgs* args = NULL; WOLFSSL_ASSERT_SIZEOF_GE(ssl->async->args, *args); #elif defined(WOLFSSL_SMALL_STACK) @@ -17921,7 +18159,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, #endif WOLFSSL_ENTER("ProcessPeerCerts"); -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) +#ifdef WOLFSSL_HAVE_HS_SUSPEND if (ssl->async == NULL) { ssl->async = (struct WOLFSSL_ASYNC*) XMALLOC(sizeof(struct WOLFSSL_ASYNC), ssl->heap, @@ -17961,13 +18199,25 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } else #endif /* WOLFSSL_NONBLOCK_OCSP */ +#ifdef WOLFSSL_CHAIN_VERIFY_CB + if (ssl->error == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + /* Re-entry after the chain verify callback deferred its verdict. Keep + * the saved state so the same certificates are handed back to it. */ + #ifdef WOLFSSL_ASYNC_CRYPT + /* if async operations not pending, reset error code */ + if (ret == WC_NO_ERR_TRACE(WC_NO_PENDING_E)) + ret = 0; + #endif + } + else +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ #elif defined(WOLFSSL_SMALL_STACK) args = (ProcPeerCertArgs*)XMALLOC( sizeof(ProcPeerCertArgs), ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (args == NULL) { ERROR_OUT(MEMORY_E, exit_ppc); } -#endif /* WOLFSSL_ASYNC_CRYPT || WOLFSSL_NONBLOCK_OCSP */ +#endif /* WOLFSSL_HAVE_HS_SUSPEND */ { /* Reset state */ ret = 0; @@ -17975,7 +18225,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, XMEMSET(args, 0, sizeof(ProcPeerCertArgs)); args->idx = *inOutIdx; args->begin = *inOutIdx; - #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) + #ifdef WOLFSSL_HAVE_HS_SUSPEND ssl->async->freeArgs = FreeProcPeerCertArgs; #endif } @@ -18009,6 +18259,17 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif + #ifdef WOLFSSL_CHAIN_VERIFY_CB + /* Before any certificate entry or its extensions is parsed. */ + if (UsingChainVerifyCb(ssl)) { + ret = ChainVerifyCbCheckSsl(ssl); + if (ret != 0) { + DoCertFatalAlert(ssl, ret); + goto exit_ppc; + } + } + #endif + /* allocate buffer for certs */ args->certs = (buffer*)XMALLOC(sizeof(buffer) * MAX_CHAIN_DEPTH, ssl->heap, DYNAMIC_TYPE_DER); @@ -18067,7 +18328,10 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, * can hold */ } #else - if (args->totalCerts >= ssl->verifyDepth || + /* The verify depth is chain-building policy, which the chain + * verify callback owns; MAX_CHAIN_DEPTH is the buffer. */ + if ((!UsingChainVerifyCb(ssl) && + args->totalCerts >= ssl->verifyDepth) || args->totalCerts >= MAX_CHAIN_DEPTH) { WOLFSSL_ERROR_VERBOSE(MAX_CHAIN_ERROR); ERROR_OUT(MAX_CHAIN_ERROR, exit_ppc); @@ -18177,6 +18441,45 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, case TLS_ASYNC_BUILD: { + #ifdef WOLFSSL_CHAIN_VERIFY_CB + /* The application verifies the chain instead of wolfSSL. No chain + * is built, so no intermediate is added to the CM either, and + * everything wolfSSL would check about the certificates is + * skipped below; only the parsing the handshake needs is kept. + * Only consult the callback if the Certificate message itself + * parsed cleanly - an error here (an empty message, or a chain + * past MAX_CHAIN_DEPTH) means it would be handed an incomplete + * list. That error is kept and reported by the check below. */ + if (UsingChainVerifyCb(ssl)) { + /* Check once, not again on every re-entry. */ + if (ret == 0 && args->count > 0 && !args->chainDecoded) { + ret = CheckPeerCertsDecode(ssl, args); + if (ret != 0) { + args->fatal = 1; + DoCertFatalAlert(ssl, ret); + goto exit_ppc; + } + args->chainDecoded = 1; + } + if (ret == 0 && args->count > 0) { + ret = DoChainVerifyCb(ssl, args); + if (ret == WC_NO_ERR_TRACE(CHAIN_VERIFY_CB_E)) { + args->fatal = 1; + #if defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) + if (ssl->peerVerifyRet == 0) { + ssl->peerVerifyRet = + WOLFSSL_X509_V_ERR_CERT_REJECTED; + } + #endif + DoCertFatalAlert(ssl, ret); + } + if (ret != 0) + goto exit_ppc; + } + } + else + #endif /* WOLFSSL_CHAIN_VERIFY_CB */ if (args->count > 0) { /* check for trusted peer and get untrustedDepth */ @@ -18624,8 +18927,11 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, /* select peer cert (first one) */ args->certIdx = 0; + /* With a chain verify callback the leaf is only parsed, never + * verified - the parse is needed for the peer's public key. */ ret = ProcessPeerCertParse(ssl, args, CERT_TYPE, - !ssl->options.verifyNone ? VERIFY : NO_VERIFY, + (!ssl->options.verifyNone && !UsingChainVerifyCb(ssl)) ? + VERIFY : NO_VERIFY, &subjectHash, &alreadySigner); #if defined(OPENSSL_ALL) && defined(WOLFSSL_CERT_GEN) && \ (defined(WOLFSSL_CERT_REQ) || defined(WOLFSSL_CERT_EXT)) && \ @@ -18643,7 +18949,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, args->dCertInit = 0; /* once again */ ret = ProcessPeerCertParse(ssl, args, CERT_TYPE, - !ssl->options.verifyNone ? VERIFY : NO_VERIFY, + (!ssl->options.verifyNone && + !UsingChainVerifyCb(ssl)) ? VERIFY : NO_VERIFY, &subjectHash, &alreadySigner); } else { @@ -18652,6 +18959,13 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } } #endif + #ifdef WOLFSSL_CHAIN_VERIFY_CB + /* The callback accepted this certificate, content wolfSSL + * would object to included. Only what the handshake needs + * from it - the key - can still fail below. */ + if (UsingChainVerifyCb(ssl) && IsCertContentErr(ret)) + ret = 0; + #endif #ifdef WOLFSSL_ASYNC_CRYPT if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) goto exit_ppc; @@ -18671,7 +18985,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, * pinned out of band (expected RPK). Applies to both peers * - client checking the server's RPK and server checking * the client's RPK. */ - if (args->dCert->isRPK) { + if (args->dCert->isRPK && !UsingChainVerifyCb(ssl)) { int rpkTrusted = RpkIsTrusted(ssl, args->certs[args->certIdx].buffer, args->certs[args->certIdx].length); @@ -18737,7 +19051,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, * different version has been negotiated using RFC 7250. * OpenSSL doesn't appear to be performing this check. * For TLS 1.3 see RFC8446 Section 4.4.2.3 */ - if (ssl->options.side == WOLFSSL_SERVER_END) { + if (ssl->options.side == WOLFSSL_SERVER_END && + !UsingChainVerifyCb(ssl)) { #if defined(HAVE_RPK) if (args->dCert->isRPK) { /* RPK certs carry no X.509 version; the RPK trust @@ -18821,15 +19136,28 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif /* defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS) */ - /* Do verify callback. */ - args->leafVerifyErr = ret = - DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); - - if (ret != 0) { + #ifdef WOLFSSL_CHAIN_VERIFY_CB + /* The chain verify callback already accepted these + * certificates, so anything left here is a decode failure + * and there is no verify callback to override it. */ + if (UsingChainVerifyCb(ssl)) { WOLFSSL_MSG("\tfatal cert error"); args->fatal = 1; DoCertFatalAlert(ssl, ret); } + else + #endif + { + /* Do verify callback. */ + args->leafVerifyErr = ret = + DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); + + if (ret != 0) { + WOLFSSL_MSG("\tfatal cert error"); + args->fatal = 1; + DoCertFatalAlert(ssl, ret); + } + } } #ifdef HAVE_SECURE_RENEGOTIATION @@ -18893,7 +19221,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, #if defined(HAVE_OCSP) || defined(HAVE_CRL) /* only attempt to check OCSP or CRL if not previous error such * as ASN_BEFORE_DATE_E or ASN_AFTER_DATE_E */ - if (args->fatal == 0 && ret == 0) { + if (args->fatal == 0 && ret == 0 && !UsingChainVerifyCb(ssl)) { if (ProcessPeerCertLeafRevocation(ssl, args, &ret)) goto exit_ppc; } @@ -18929,7 +19257,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } else #endif - if (args->dCert->extKeyUsageSet) { + if (args->dCert->extKeyUsageSet && !UsingChainVerifyCb(ssl)) { if ((ssl->specs.kea == rsa_kea) && (ssl->options.side == WOLFSSL_CLIENT_END) && (args->dCert->extKeyUsage & KEYUSE_KEY_ENCIPHER) == 0) { @@ -18962,7 +19290,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } else #endif - if (args->dCert->extExtKeyUsageSet) { + if (args->dCert->extExtKeyUsageSet && + !UsingChainVerifyCb(ssl)) { if (ssl->options.side == WOLFSSL_CLIENT_END) { if ((args->dCert->extExtKeyUsage & (EXTKEYUSE_ANY | EXTKEYUSE_SERVER_AUTH)) == 0) { @@ -19027,7 +19356,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif - if (!ssl->options.verifyNone && domainName) { + if (!ssl->options.verifyNone && !UsingChainVerifyCb(ssl) && + domainName) { #ifndef WOLFSSL_ALLOW_NO_CN_IN_SAN /* Per RFC 5280 section 4.2.1.6, "Whenever such identities * are to be bound into a certificate, the subject @@ -19085,7 +19415,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #ifndef OPENSSL_EXTRA - if (!ssl->options.verifyNone && ssl->buffers.ipasc.buffer) { + if (!ssl->options.verifyNone && !UsingChainVerifyCb(ssl) && + ssl->buffers.ipasc.buffer) { if (CheckIPAddr(args->dCert, (const char*)ssl->buffers.ipasc.buffer, (size_t)ssl->buffers.ipasc.length) != 0) { @@ -19134,8 +19465,10 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif - /* Do leaf verify callback when it wasn't called yet */ - if (ret == 0 || ret != args->leafVerifyErr) + /* Do leaf verify callback when it wasn't called yet. Skipped when + * the chain verify callback owns the verdict. */ + if (!UsingChainVerifyCb(ssl) && + (ret == 0 || ret != args->leafVerifyErr)) ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); if (ssl->options.verifyNone && @@ -19179,17 +19512,16 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, WOLFSSL_LEAVE("ProcessPeerCerts", ret); -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) || - ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { +#ifdef WOLFSSL_HAVE_HS_SUSPEND + if (IsHsSuspendErr(ret)) { /* Mark message as not received so it can process again */ ssl->msgsReceived.got_certificate = 0; return ret; } -#endif /* WOLFSSL_ASYNC_CRYPT || WOLFSSL_NONBLOCK_OCSP */ +#endif /* WOLFSSL_HAVE_HS_SUSPEND */ -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) +#ifdef WOLFSSL_HAVE_HS_SUSPEND /* Cleanup async */ FreeAsyncCtx(ssl, 0); #elif defined(WOLFSSL_SMALL_STACK) @@ -19199,9 +19531,11 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #else FreeProcPeerCertArgs(ssl, args); -#endif /* WOLFSSL_ASYNC_CRYPT || WOLFSSL_NONBLOCK_OCSP || WOLFSSL_SMALL_STACK */ +#endif /* WOLFSSL_HAVE_HS_SUSPEND || WOLFSSL_SMALL_STACK */ -#if !defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_SMALL_STACK) +/* args points into ssl->async when the handshake can suspend, so it is only a + * separate allocation to free otherwise. */ +#if !defined(WOLFSSL_HAVE_HS_SUSPEND) && defined(WOLFSSL_SMALL_STACK) XFREE(args, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); #endif @@ -19226,9 +19560,8 @@ static int DoCertificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, #ifdef SESSION_CERTS /* Reset the session cert chain count in case the session resume failed, * do not reset if we are resuming after an async wait */ -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ssl->error != WC_NO_ERR_TRACE(OCSP_WANT_READ) && - ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) +#ifdef WOLFSSL_HAVE_HS_SUSPEND + if (!IsHsSuspendErr(ssl->error)) #endif { ssl->session->chain.count = 0; @@ -19241,7 +19574,11 @@ static int DoCertificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, ret = ProcessPeerCerts(ssl, input, inOutIdx, size); #ifdef OPENSSL_EXTRA - ssl->options.serverState = SERVER_CERT_COMPLETE; + /* the certificate is not processed yet if it only suspended */ +#ifdef WOLFSSL_HAVE_HS_SUSPEND + if (!IsHsSuspendErr(ret)) +#endif + ssl->options.serverState = SERVER_CERT_COMPLETE; #endif WOLFSSL_LEAVE("DoCertificate", ret); @@ -20177,13 +20514,12 @@ static int SanityCheckMsgReceived(WOLFSSL* ssl, byte type) ((defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3)) || \ (defined(HAVE_ED25519) && !defined(NO_ED25519_CLIENT_AUTH)) || \ (defined(HAVE_ED448) && !defined(NO_ED448_CLIENT_AUTH))) -/* Free the cached handshake messages used for sign/verify, unless an async or - * non-blocking OCSP operation is still pending. */ +/* Free the cached handshake messages used for sign/verify, unless the + * handshake is suspended part way through a message. */ static void FreeCachedHandshakeMessages(WOLFSSL* ssl, int ret) { -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ret != WC_NO_ERR_TRACE(WC_PENDING_E) && - ret != WC_NO_ERR_TRACE(OCSP_WANT_READ)) +#ifdef WOLFSSL_HAVE_HS_SUSPEND + if (!IsHsSuspendErr(ret)) #endif { ssl->options.cacheMessages = 0; @@ -20334,11 +20670,8 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, /* above checks handshake state */ /* hello_request not hashed */ if (type != hello_request - #ifdef WOLFSSL_ASYNC_CRYPT - && ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E) - #endif - #ifdef WOLFSSL_NONBLOCK_OCSP - && ssl->error != WC_NO_ERR_TRACE(OCSP_WANT_READ) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + && !IsHsSuspendErr(ssl->error) #endif ) { ret = HashInput(ssl, input + *inOutIdx, (int)size); @@ -20522,10 +20855,9 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, WOLFSSL_ERROR_VERBOSE(ret); } -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - /* if async, offset index so this msg will be processed again */ - if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E) || - ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) && *inOutIdx > 0) { +#ifdef WOLFSSL_HAVE_HS_SUSPEND + /* if suspended, offset index so this msg will be processed again */ + if (IsHsSuspendErr(ret) && *inOutIdx > 0) { *inOutIdx -= HANDSHAKE_HEADER_SZ; #ifdef WOLFSSL_DTLS if (ssl->options.dtls) { @@ -20534,12 +20866,11 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, #endif } - /* make sure async error is cleared */ - if (ret == 0 && (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E) || - ssl->error == WC_NO_ERR_TRACE(OCSP_WANT_READ))) { + /* make sure suspend error is cleared */ + if (ret == 0 && IsHsSuspendErr(ssl->error)) { ssl->error = 0; } -#endif /* WOLFSSL_ASYNC_CRYPT || WOLFSSL_NONBLOCK_OCSP */ +#endif /* WOLFSSL_HAVE_HS_SUSPEND */ #ifdef WOLFSSL_DTLS if (ret == 0) { @@ -20661,12 +20992,13 @@ static int DoHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, return ret; } - #ifdef WOLFSSL_ASYNC_CRYPT - if (ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + if (!IsHsSuspendErr(ssl->error)) #endif { - /* for async this copy was already done, do not replace, since - * contents may have been changed for inline operations */ + /* when resuming a suspended message this copy was already done, do + * not replace, since contents may have been changed for inline + * operations */ XMEMCPY(ssl->pendingMsg + ssl->pendingMsgOffset, input + *inOutIdx, inputLength); } @@ -20681,8 +21013,10 @@ static int DoHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, &idx, ssl->pendingMsgType, ssl->pendingMsgSz - idx, ssl->pendingMsgSz); - #ifdef WOLFSSL_ASYNC_CRYPT - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { + #ifdef WOLFSSL_HAVE_HS_SUSPEND + /* ProcPeerCertArgs keeps pointers into pendingMsg across a + * suspend, so it must outlive this call. */ + if (IsHsSuspendErr(ret)) { /* setup to process fragment again */ ssl->pendingMsgOffset -= inputLength; *inOutIdx -= inputLength; @@ -20721,6 +21055,9 @@ int SendFatalAlertOnly(WOLFSSL *ssl, int error) #endif #ifdef WOLFSSL_ASYNC_CRYPT case WC_NO_ERR_TRACE(WC_PENDING_E): +#endif +#ifdef WOLFSSL_CHAIN_VERIFY_CB + case WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E): #endif return 0; @@ -25075,11 +25412,8 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) #if defined(HAVE_SECURE_RENEGOTIATION) || defined(WOLFSSL_DTLS13) && ssl->error != WC_NO_ERR_TRACE(APP_DATA_READY) #endif - #ifdef WOLFSSL_ASYNC_CRYPT - && ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E) - #endif - #ifdef WOLFSSL_NONBLOCK_OCSP - && ssl->error != WC_NO_ERR_TRACE(OCSP_WANT_READ) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + && !IsHsSuspendErr(ssl->error) #endif && (allowSocketErr != 1 || ssl->error != WC_NO_ERR_TRACE(SOCKET_ERROR_E)) @@ -28841,6 +29175,29 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) } } +#ifdef WOLFSSL_CHAIN_VERIFY_CB + /* A Certificate suspended by the chain verify callback is resumed before + * anything is sent; sending would free its saved state. wolfSSL_negotiate() + * resumes it during the handshake, only wolfSSL_read() after it. */ + if (error == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + int err; + if (ssl->options.handShakeState == HANDSHAKE_DONE) { + WOLFSSL_MSG("Chain verify callback pending, use wolfSSL_read"); + return error; + } + WOLFSSL_MSG("Chain verify callback pending, trying to finish"); + if ((err = wolfSSL_negotiate(ssl)) != WOLFSSL_SUCCESS) { + #ifdef WOLFSSL_ASYNC_CRYPT + /* if async would block return WANT_WRITE */ + if (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E)) { + return WOLFSSL_CBIO_ERR_WANT_WRITE; + } + #endif + return err; + } + } +#endif + #ifdef WOLFSSL_EARLY_DATA if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->earlyData != no_early_data && @@ -29241,8 +29598,9 @@ int ReceiveData(WOLFSSL* ssl, byte* output, size_t sz, int peek) #endif /* WOLFSSL_DTLS */ if (error != 0 && error != WC_NO_ERR_TRACE(WANT_WRITE) -#ifdef WOLFSSL_ASYNC_CRYPT - && error != WC_NO_ERR_TRACE(WC_PENDING_E) +#ifdef WOLFSSL_HAVE_HS_SUSPEND + /* a suspended handshake message is resumed by reading again */ + && !IsHsSuspendErr(error) #endif #if defined(HAVE_SECURE_RENEGOTIATION) || defined(WOLFSSL_DTLS13) && error != WC_NO_ERR_TRACE(APP_DATA_READY) @@ -30319,6 +30677,15 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) case RPK_UNTRUSTED_E: return "RFC 7250 Raw Public Key not trusted"; + + case CHAIN_VERIFY_WANT_E: + return "Chain verify callback has no verdict yet"; + + case CHAIN_VERIFY_CB_E: + return "Chain verify callback rejected peer certificates"; + + case CHAIN_VERIFY_UNSUPPORTED_E: + return "Chain verify callback used with unsupported feature"; } return "unknown error number"; @@ -46424,13 +46791,8 @@ int wolfSSL_TestAppleNativeCertValidation_AppendCA(WOLFSSL_CTX* ctx, void wolfssl_local_MaybeCheckAlertOnErr(WOLFSSL* ssl, int err) { #if defined(WOLFSSL_CHECK_ALERT_ON_ERR) -#if defined(WOLFSSL_ASYNC_CRYPT) - if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) { - return; - } -#endif -#if defined(WOLFSSL_NONBLOCK_OCSP) - if (err == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { +#if defined(WOLFSSL_HAVE_HS_SUSPEND) + if (IsHsSuspendErr(err)) { return; } #endif diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 9c036fac882..90bad6f15ed 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -639,6 +639,120 @@ void wolfSSL_CTX_set_cert_verify_callback(WOLFSSL_CTX* ctx, } #endif +#ifdef WOLFSSL_CHAIN_VERIFY_CB +/* Set the callback that replaces peer certificate chain verification for + * every SSL/TLS object created from the context. + * + * Setting a callback turns off all of wolfSSL's own checking of the peer's + * certificates. See ChainVerifyCb in ssl.h. + * + * @param [in, out] ctx SSL/TLS context object. + * @param [in] cb Chain verification callback. NULL to clear. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL. + * @return CHAIN_VERIFY_UNSUPPORTED_E when the context uses DTLS, raw public + * keys or OCSP stapling. + */ +int wolfSSL_CTX_SetChainVerifyCb(WOLFSSL_CTX* ctx, ChainVerifyCb cb) +{ + int ret; + + WOLFSSL_ENTER("wolfSSL_CTX_SetChainVerifyCb"); + + if (ctx == NULL) + return BAD_FUNC_ARG; + + if (cb != NULL) { + ret = ChainVerifyCbCheckCtx(ctx); + if (ret != 0) + return ret; + } + ctx->chainVerifyCb = cb; + + return WOLFSSL_SUCCESS; +} + +/* Set the callback that replaces peer certificate chain verification for one + * SSL/TLS object. Takes precedence over the context's callback. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] cb Chain verification callback. NULL to fall back to the + * context's. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ssl is NULL. + * @return CHAIN_VERIFY_UNSUPPORTED_E when the object uses DTLS, raw public + * keys or OCSP stapling. + */ +int wolfSSL_SetChainVerifyCb(WOLFSSL* ssl, ChainVerifyCb cb) +{ + int ret; + + WOLFSSL_ENTER("wolfSSL_SetChainVerifyCb"); + + if (ssl == NULL) + return BAD_FUNC_ARG; + + if (cb != NULL) { + ret = ChainVerifyCbCheckSsl(ssl); + if (ret != 0) + return ret; + } + ssl->chainVerifyCb = cb; + + return WOLFSSL_SUCCESS; +} + +/* Set the user context passed to the chain verification callback for every + * SSL/TLS object created from the context. + * + * @param [in, out] ctx SSL/TLS context object. + * @param [in] userCtx User context. + */ +void wolfSSL_CTX_SetChainVerifyCtx(WOLFSSL_CTX* ctx, void* userCtx) +{ + WOLFSSL_ENTER("wolfSSL_CTX_SetChainVerifyCtx"); + + if (ctx != NULL) { + ctx->chainVerifyCtx = userCtx; + } +} + +/* Set the user context passed to the chain verification callback for one + * SSL/TLS object. Takes precedence over the context's. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] ctx User context. NULL to fall back to the context's. + */ +void wolfSSL_SetChainVerifyCtx(WOLFSSL* ssl, void* ctx) +{ + WOLFSSL_ENTER("wolfSSL_SetChainVerifyCtx"); + + if (ssl != NULL) { + ssl->chainVerifyCtx = ctx; + } +} + +/* Get the user context the chain verification callback is called with: the + * SSL/TLS object's when set, otherwise the context's. + * + * @param [in] ssl SSL/TLS object. + * @return User context, or NULL when none set or ssl is NULL. + */ +void* wolfSSL_GetChainVerifyCtx(const WOLFSSL* ssl) +{ + WOLFSSL_ENTER("wolfSSL_GetChainVerifyCtx"); + + if (ssl != NULL) { + if (ssl->chainVerifyCtx != NULL) { + return ssl->chainVerifyCtx; + } + return ssl->ctx->chainVerifyCtx; + } + + return NULL; +} +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ + /* Set the verification options against the SSL/TLS object. * * @param [in, out] ssl SSL/TLS object. diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index fe7be6441a0..40e1fe26b74 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -131,10 +131,10 @@ static int wolfssl_connect_flush(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS13 */ if ((ssl->buffers.outputBuffer.length > 0) - #ifdef WOLFSSL_ASYNC_CRYPT - /* do not send buffered or advance state if last error was an - async pending operation */ - && (ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + /* do not send buffered or advance state if the last error + suspended the handshake - advancing frees the saved state */ + && !IsHsSuspendErr(ssl->error) #endif ) { ret = SendBuffered(ssl); @@ -201,10 +201,10 @@ static int wolfssl_accept_flush(WOLFSSL* ssl) int ret = 0; if ((ssl->buffers.outputBuffer.length > 0) - #ifdef WOLFSSL_ASYNC_CRYPT - /* do not send buffered or advance state if last error was an - async pending operation */ - && (ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + /* do not send buffered or advance state if the last error + suspended the handshake - advancing frees the saved state */ + && !IsHsSuspendErr(ssl->error) #endif ) { ret = SendBuffered(ssl); diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 589c5950575..89626ed0b3d 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -592,8 +592,8 @@ static int wolfSSL_read_internal(WOLFSSL* ssl, void* data, size_t sz, int peek) if (ssl->dupWrite != NULL) { if ((ssl->error != 0) && (ssl->error != WC_NO_ERR_TRACE(WANT_READ)) - #ifdef WOLFSSL_ASYNC_CRYPT - && (ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + && !IsHsSuspendErr(ssl->error) #endif ) { int notifyErr; diff --git a/src/tls13.c b/src/tls13.c index 1322d76df55..30db75955e5 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -15129,9 +15129,8 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, && (!ssl->options.dtls) #endif ) { - #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ret != WC_NO_ERR_TRACE(WC_PENDING_E) && - ret != WC_NO_ERR_TRACE(OCSP_WANT_READ)) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + if (!IsHsSuspendErr(ret)) #endif { ssl->options.cacheMessages = 0; @@ -15246,21 +15245,19 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, break; } -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_ASYNC_IO) - /* if async, offset index so this msg will be processed again */ +#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_ASYNC_IO) || \ + defined(WOLFSSL_HAVE_HS_SUSPEND) + /* if suspended, offset index so this msg will be processed again */ /* NOTE: check this now before other calls can overwrite ret */ - if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E) || - ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) && *inOutIdx > 0) { + if (IsHsSuspendErr(ret) && *inOutIdx > 0) { /* DTLS always stores a message in a buffer when async is enable, so we * don't need to adjust for the extra bytes here (*inOutIdx is always * == 0) */ *inOutIdx -= HANDSHAKE_HEADER_SZ; } - /* make sure async error is cleared */ - if (ret == 0 && - (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E) || - ssl->error == WC_NO_ERR_TRACE(OCSP_WANT_READ))) { + /* make sure suspend error is cleared */ + if (ret == 0 && IsHsSuspendErr(ssl->error)) { ssl->error = 0; } #endif @@ -15582,9 +15579,8 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, &idx, ssl->pendingMsgType, ssl->pendingMsgSz - HANDSHAKE_HEADER_SZ, ssl->pendingMsgSz); - #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) || - ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { + #ifdef WOLFSSL_HAVE_HS_SUSPEND + if (IsHsSuspendErr(ret)) { /* setup to process fragment again */ ssl->pendingMsgOffset -= inputLength; *inOutIdx -= inputLength; @@ -15679,10 +15675,10 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS13 */ if (ssl->buffers.outputBuffer.length > 0 - #ifdef WOLFSSL_ASYNC_CRYPT - /* do not send buffered or advance state if last error was an - async pending operation */ - && ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + /* do not send buffered or advance state if the last error suspended + the handshake - advancing frees the saved state */ + && !IsHsSuspendErr(ssl->error) #endif ) { if ((ret = SendBuffered(ssl)) == 0) { @@ -17000,10 +16996,10 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) #endif /* NO_CERTS */ if (ssl->buffers.outputBuffer.length > 0 - #ifdef WOLFSSL_ASYNC_CRYPT - /* do not send buffered or advance state if last error was an - async pending operation */ - && ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E) + #ifdef WOLFSSL_HAVE_HS_SUSPEND + /* do not send buffered or advance state if the last error suspended + the handshake - advancing frees the saved state */ + && !IsHsSuspendErr(ssl->error) #endif ) { @@ -17379,12 +17375,19 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) FreeHandshakeResources(ssl); } +#ifdef WOLFSSL_HAVE_HS_SUSPEND + /* A post-handshake Certificate may be suspended in ssl->async. + * Keep it and the error so wolfSSL_read() can resume it. */ + if (!IsHsSuspendErr(ssl->error)) +#endif + { #if defined(WOLFSSL_ASYNC_IO) && !defined(WOLFSSL_ASYNC_CRYPT) - /* Free the remaining async context if not using it for crypto */ - FreeAsyncCtx(ssl, 1); + /* Free the remaining async context if not using it for + * crypto */ + FreeAsyncCtx(ssl, 1); #endif - - ssl->error = 0; /* clear the error */ + ssl->error = 0; /* clear the error */ + } WOLFSSL_LEAVE("wolfSSL_accept", WOLFSSL_SUCCESS); return WOLFSSL_SUCCESS; diff --git a/tests/api.c b/tests/api.c index 88999c0cfb7..42be86603e6 100644 --- a/tests/api.c +++ b/tests/api.c @@ -4626,7 +4626,8 @@ static int test_wolfSSL_clear_chain_certs(void) #if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA) && \ defined(KEEP_OUR_CERT) && !defined(NO_RSA) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(OPENSSL_COEXIST) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(OPENSSL_COEXIST) && \ (defined(OPENSSL_ALL) || defined(WOLFSSL_ASIO) || \ defined(WOLFSSL_HAPROXY) || defined(WOLFSSL_NGINX)) /* Server-side ssl_ready hook: add chain certs then clear them, so the @@ -6574,6 +6575,11 @@ int test_ssl_memio_do_handshake(test_ssl_memio_ctx* ctx, int max_rounds, if (err == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) { /* retry non-blocking math */ } + #ifdef WOLFSSL_CHAIN_VERIFY_CB + else if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + /* application has not reached a verdict yet; retry */ + } + #endif else if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE && err != WC_NO_ERR_TRACE(OCSP_WANT_READ)) { @@ -6600,6 +6606,11 @@ int test_ssl_memio_do_handshake(test_ssl_memio_ctx* ctx, int max_rounds, if (err == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) { /* retry non-blocking math */ } + #ifdef WOLFSSL_CHAIN_VERIFY_CB + else if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + /* application has not reached a verdict yet; retry */ + } + #endif else if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE && err != WC_NO_ERR_TRACE(OCSP_WANT_READ)) { diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index c9c0c8a44b6..cdeacf8cc14 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3378,3 +3378,635 @@ int test_record_size_cache_invalidated_on_renegotiation(void) #endif return EXPECT_RESULT(); } + +#if defined(WOLFSSL_CHAIN_VERIFY_CB) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +#define HAVE_CHAIN_VERIFY_CB_TESTS +#endif + +#ifdef HAVE_CHAIN_VERIFY_CB_TESTS + +/* State shared with the chain verify callbacks below. */ +typedef struct test_chain_verify_cb_ctx { + int calls; /* how many times the callback ran */ + int deferrals; /* how many times to answer CHAIN_VERIFY_WANT_E first */ + int reject; /* non-zero to reject the chain */ + int certsSeen; /* certsSz of the last call */ + int leafMatched; /* certs[0] matched the expected leaf DER */ + /* A copy of the expected leaf: the peer may unload its own certificate + * buffer once its side of the handshake completes, which in TLS 1.3 is + * before the server has processed it. */ + unsigned char leafDer[2048]; + unsigned int leafDerSz; +} test_chain_verify_cb_ctx; + +/* Remember the certificate the peer will present as certs[0]. */ +static int test_chain_verify_cb_set_leaf(test_chain_verify_cb_ctx* cbCtx, + const DerBuffer* cert) +{ + if ((cert == NULL) || (cert->length > sizeof(cbCtx->leafDer))) + return -1; + XMEMCPY(cbCtx->leafDer, cert->buffer, cert->length); + cbCtx->leafDerSz = cert->length; + return 0; +} + +static int test_chain_verify_cb(WOLFSSL* ssl, const WOLFSSL_BUFFER_INFO* certs, + int certsSz, void* ctx) +{ + test_chain_verify_cb_ctx* cbCtx = (test_chain_verify_cb_ctx*)ctx; + + (void)ssl; + + cbCtx->calls++; + cbCtx->certsSeen = certsSz; + + if ((certsSz > 0) && (cbCtx->leafDerSz > 0) && + (certs[0].length == cbCtx->leafDerSz) && + (XMEMCMP(certs[0].buffer, cbCtx->leafDer, cbCtx->leafDerSz) == 0)) { + cbCtx->leafMatched = 1; + } + + if (cbCtx->calls <= cbCtx->deferrals) + return WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E); + if (cbCtx->reject) + return -1; + + return 0; +} + +/* Which end of the connection verifies with the callback. */ +#define TEST_CVC_CLIENT 0 +#define TEST_CVC_SERVER 1 + +/* Set up a handshake that the verifying end cannot complete on its own. + * Client side: the client's CA store is emptied, so verifying the server's + * certificate fails with ASN_NO_SIGNER_E. Server side: the client presents a + * certificate the server has no CA for, and the server insists on one. + * Installing the chain verify callback on that end is then the only thing + * that can make the handshake succeed. Leaves the objects allocated so the + * caller can inspect them. Pass cbCtx as NULL to install no callback + * (negative control). */ +static int test_chain_verify_cb_run(test_chain_verify_cb_ctx* cbCtx, int side, + method_provider client_method, method_provider server_method, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + struct test_memio_ctx* test_ctx) +{ + EXPECT_DECLS; + WOLFSSL* verifier = NULL; + WOLFSSL* peer = NULL; + + XMEMSET(test_ctx, 0, sizeof(*test_ctx)); + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + client_method, server_method), 0); + + if (side == TEST_CVC_CLIENT) { + ExpectIntEQ(wolfSSL_CTX_UnloadCAs(*ctx_c), WOLFSSL_SUCCESS); + /* explicit, since OPENSSL_COMPATIBLE_DEFAULTS turns verification off + * on clients by default */ + wolfSSL_set_verify(*ssl_c, WOLFSSL_VERIFY_PEER, NULL); + verifier = *ssl_c; + peer = *ssl_s; + } + else { + ExpectIntEQ(wolfSSL_use_certificate_file(*ssl_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(*ssl_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + wolfSSL_set_verify(*ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + verifier = *ssl_s; + peer = *ssl_c; + } + + if (EXPECT_SUCCESS() && (cbCtx != NULL)) { + if (side == TEST_CVC_CLIENT) { + /* context-level callback, object-level user context */ + ExpectIntEQ(wolfSSL_CTX_SetChainVerifyCb(*ctx_c, + test_chain_verify_cb), WOLFSSL_SUCCESS); + wolfSSL_SetChainVerifyCtx(verifier, cbCtx); + } + else { + /* object-level callback, context-level user context */ + ExpectIntEQ(wolfSSL_SetChainVerifyCb(verifier, + test_chain_verify_cb), WOLFSSL_SUCCESS); + wolfSSL_CTX_SetChainVerifyCtx(*ctx_s, cbCtx); + } + ExpectPtrEq(wolfSSL_GetChainVerifyCtx(verifier), cbCtx); + + /* certs[0] must be the peer's own certificate. */ + ExpectIntEQ(test_chain_verify_cb_set_leaf(cbCtx, + peer->buffers.certificate), 0); + } + + return EXPECT_RESULT(); +} + +static void test_chain_verify_cb_free(WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, + WOLFSSL** ssl_c, WOLFSSL** ssl_s) +{ + wolfSSL_free(*ssl_c); + wolfSSL_free(*ssl_s); + wolfSSL_CTX_free(*ctx_c); + wolfSSL_CTX_free(*ctx_s); + *ssl_c = NULL; + *ssl_s = NULL; + *ctx_c = NULL; + *ctx_s = NULL; +} + +/* Negative control first, then the callback accepting after the given number + * of deferrals. */ +static int test_chain_verify_cb_accept(int side, int deferrals, + method_provider client_method, method_provider server_method) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; + int err; + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + cbCtx.deferrals = deferrals; + + /* Negative control: without the callback this same setup must fail, so the + * success below can only come from the callback replacing verification. */ + ExpectIntEQ(test_chain_verify_cb_run(NULL, side, client_method, + server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, &test_ctx), + TEST_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), -1); + /* The client certificate is self-signed, which some builds report as + * such rather than as having no signer. */ + err = wolfSSL_get_error((side == TEST_CVC_CLIENT) ? ssl_c : ssl_s, + WOLFSSL_FATAL_ERROR); + ExpectTrue((err == WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)) || + (err == WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E))); + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); + + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, side, client_method, + server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, &test_ctx), + TEST_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Deferred the requested number of times, then accepted, every call + * seeing the same chain. */ + ExpectIntEQ(cbCtx.calls, deferrals + 1); + ExpectIntGT(cbCtx.certsSeen, 0); + ExpectIntEQ(cbCtx.leafMatched, 1); + + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); + return EXPECT_RESULT(); +} + +#if !defined(WOLFSSL_NO_TLS12) || defined(HAVE_CERTIFICATE_STATUS_REQUEST) +/* The verifying end fails before the callback is consulted, with the given + * error, and the callback is never called. */ +static int test_chain_verify_cb_expect_fail(test_chain_verify_cb_ctx* cbCtx, + WOLFSSL* verifier, WOLFSSL* ssl_c, WOLFSSL* ssl_s, int err) +{ + EXPECT_DECLS; + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), -1); + ExpectIntEQ(wolfSSL_get_error(verifier, WOLFSSL_FATAL_ERROR), err); + ExpectIntEQ(cbCtx->calls, 0); + + return EXPECT_RESULT(); +} +#endif + +#endif /* HAVE_CHAIN_VERIFY_CB_TESTS */ + +int test_tls12_chain_verify_cb(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && !defined(WOLFSSL_NO_TLS12) + ExpectIntEQ(test_chain_verify_cb_accept(TEST_CVC_CLIENT, 0, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +int test_tls12_chain_verify_cb_server(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + /* Client authentication: the server verifies, deferring twice. */ + ExpectIntEQ(test_chain_verify_cb_accept(TEST_CVC_SERVER, 2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_chain_verify_cb_async(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && defined(WOLFSSL_TLS13) + ExpectIntEQ(test_chain_verify_cb_accept(TEST_CVC_CLIENT, 3, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_chain_verify_cb_server(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && defined(WOLFSSL_TLS13) + ExpectIntEQ(test_chain_verify_cb_accept(TEST_CVC_SERVER, 2, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_chain_verify_cb_reject(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && defined(WOLFSSL_TLS13) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + XMEMSET(&h, 0, sizeof(h)); + cbCtx.deferrals = 1; + cbCtx.reject = 1; + + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, + &ctx_c, &ctx_s, &ssl_c, &ssl_s, &test_ctx), TEST_SUCCESS); + + /* Handshake must fail, and only after the deferral was honoured. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), -1); + ExpectIntEQ(cbCtx.calls, 2); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(CHAIN_VERIFY_CB_E)); + + /* A single generic certificate alert is sent - the callback's reason is + * never leaked to the peer. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.level, alert_fatal); + ExpectIntEQ(h.last_tx.code, bad_certificate); +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + ExpectIntEQ(wolfSSL_get_verify_result(ssl_c), + WOLFSSL_X509_V_ERR_CERT_REJECTED); +#endif + + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls12_chain_verify_cb_bad_der(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method, + &ctx_c, &ctx_s, &ssl_c, &ssl_s, &test_ctx), TEST_SUCCESS); + + /* Break the length of the leaf's outer SEQUENCE. The Certificate message + * still frames correctly, but the certificate no longer decodes. */ + ExpectNotNull(ssl_s->buffers.certificate); + ExpectIntGT(ssl_s->buffers.certificate->length, 4); + if (EXPECT_SUCCESS()) { + ssl_s->buffers.certificate->buffer[2] = 0xFF; + ssl_s->buffers.certificate->buffer[3] = 0xFF; + } + + /* wolfSSL rejects it on its own - the callback is never consulted. */ + ExpectIntEQ(test_chain_verify_cb_expect_fail(&cbCtx, ssl_c, ssl_c, ssl_s, + WC_NO_ERR_TRACE(ASN_PARSE_E)), TEST_SUCCESS); + + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls12_chain_verify_cb_bad_chain(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; + + /* First with the chain intact: the callback sees both the server's + * certificate and the issuer it sends along. */ + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method, + &ctx_c, &ctx_s, &ssl_c, &ssl_s, &test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, svrCertFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_chain_verify_cb_set_leaf(&cbCtx, + ssl_s->buffers.certificate), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(cbCtx.calls, 1); + ExpectIntEQ(cbCtx.certsSeen, 2); + ExpectIntEQ(cbCtx.leafMatched, 1); + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); + + /* Now break the issuer's outer SEQUENCE length. A chain entry is a 3-byte + * length followed by the DER, so the message still frames. */ + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method, + &ctx_c, &ctx_s, &ssl_c, &ssl_s, &test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, svrCertFile), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl_s->buffers.certChain); + ExpectIntGT(ssl_s->buffers.certChain->length, OPAQUE24_LEN + 4); + if (EXPECT_SUCCESS()) { + ssl_s->buffers.certChain->buffer[OPAQUE24_LEN + 2] = 0xFF; + ssl_s->buffers.certChain->buffer[OPAQUE24_LEN + 3] = 0xFF; + } + + ExpectIntEQ(test_chain_verify_cb_expect_fail(&cbCtx, ssl_c, ssl_c, ssl_s, + WC_NO_ERR_TRACE(ASN_PARSE_E)), TEST_SUCCESS); + + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_chain_verify_cb_postauth(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; + char buf[8]; +#ifdef HAVE_WRITE_DUP + WOLFSSL* ssl_w = NULL; +#endif + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + cbCtx.deferrals = 2; + + /* Client: has a certificate and allows post-handshake authentication. + * The certificate is loaded on the context: without OPENSSL_EXTRA a + * certificate loaded on the SSL object is unloaded when the handshake + * completes, and the post-handshake Certificate would be empty. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_allow_post_handshake_auth(ssl_c), 0); + + /* Server: no CA for the client, the callback verifies instead, and only + * after the handshake. Message grouping (on by default with + * OPENSSL_COMPATIBLE_DEFAULTS) would hold the CertificateRequest back in + * the output buffer. */ + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_POST_HANDSHAKE, NULL); + ExpectIntEQ(wolfSSL_clear_group_messages(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_s, test_chain_verify_cb), + WOLFSSL_SUCCESS); + wolfSSL_SetChainVerifyCtx(ssl_s, &cbCtx); + ExpectIntEQ(test_chain_verify_cb_set_leaf(&cbCtx, + ssl_c->buffers.certificate), 0); + + /* No certificate during the handshake, so no callback yet. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(cbCtx.calls, 0); + + /* The server asks; the client answers with its certificate from inside + * wolfSSL_read(). */ + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* The certificate arrives inside wolfSSL_read(); the callback defers. */ + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)); + ExpectIntEQ(cbCtx.calls, 1); + + /* Only wolfSSL_read() resumes it: a write is refused, and the callback + * is not asked. */ + ExpectIntEQ(wolfSSL_write(ssl_s, "hi", 2), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)); + ExpectIntEQ(cbCtx.calls, 1); + + /* wolfSSL_accept() meanwhile must leave the suspended message alone. */ + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + +#ifdef HAVE_WRITE_DUP + ExpectNotNull(ssl_w = wolfSSL_write_dup(ssl_s)); +#endif + + /* Reading again asks the callback again: still deferred. */ + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)); + ExpectIntEQ(cbCtx.calls, 2); + +#ifdef HAVE_WRITE_DUP + /* The deferral is not a read error handed to the write duplicate. */ + ExpectIntEQ(wolfSSL_write(ssl_w, "hi", 2), 2); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), 2); +#endif + + /* Accepted; CertificateVerify and Finished follow, then nothing to read. */ + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(cbCtx.calls, 3); + ExpectIntEQ(cbCtx.certsSeen, 1); + ExpectIntEQ(cbCtx.leafMatched, 1); + + /* The connection is intact: application data flows. */ + ExpectIntEQ(wolfSSL_write(ssl_c, "hi", 2), 2); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), 2); + +#ifdef HAVE_WRITE_DUP + wolfSSL_free(ssl_w); +#endif + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif + return EXPECT_RESULT(); +} + +int test_chain_verify_cb_min_key(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && !defined(NO_RSA) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; +#ifndef WOLFSSL_NO_TLS12 + method_provider client_method = wolfTLSv1_2_client_method; + method_provider server_method = wolfTLSv1_2_server_method; +#else + method_provider client_method = wolfTLSv1_3_client_method; + method_provider server_method = wolfTLSv1_3_server_method; +#endif + + /* The callback accepts, but the handshake uses the server's key directly, + * so the minimum key size still applies - even with verification turned + * off, which is not the callback's concern. */ + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + client_method, server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + &test_ctx), TEST_SUCCESS); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_SetMinRsaKey_Sz(ssl_c, 4096), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(RSA_KEY_SIZE_E)); + ExpectIntEQ(cbCtx.calls, 1); + + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif + return EXPECT_RESULT(); +} + +int test_chain_verify_cb_dtls(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_UnloadCAs(ctx_c), WOLFSSL_SUCCESS); + + /* DTLS is not supported with the callback: the setters refuse it. */ + ExpectIntEQ(wolfSSL_CTX_SetChainVerifyCb(ctx_c, test_chain_verify_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_c, test_chain_verify_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectNull(wolfSSL_GetChainVerifyCtx(ssl_c)); + + /* A callback that got in regardless fails the handshake before it is + * consulted. */ + if (EXPECT_SUCCESS()) { + ssl_c->chainVerifyCb = test_chain_verify_cb; + ssl_c->chainVerifyCtx = &cbCtx; + } + ExpectIntEQ(test_chain_verify_cb_expect_fail(&cbCtx, ssl_c, ssl_c, ssl_s, + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)), TEST_SUCCESS); + + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif + return EXPECT_RESULT(); +} + +int test_chain_verify_cb_stapling(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHAIN_VERIFY_CB_TESTS) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + test_chain_verify_cb_ctx cbCtx; +#ifndef WOLFSSL_NO_TLS12 + method_provider client_method = wolfTLSv1_2_client_method; + method_provider server_method = wolfTLSv1_2_server_method; +#else + method_provider client_method = wolfTLSv1_3_client_method; + method_provider server_method = wolfTLSv1_3_server_method; +#endif + + /* Stapling requested before the callback: the setters refuse it, on the + * object and on the context. The server side is not affected, since a + * server's stapling is about its own certificate. */ + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(NULL, TEST_CVC_CLIENT, + client_method, server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + &test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl_c, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_c, test_chain_verify_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectIntEQ(wolfSSL_CTX_UseOCSPStapling(ctx_c, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetChainVerifyCb(ctx_c, test_chain_verify_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectIntEQ(wolfSSL_CTX_SetChainVerifyCb(ctx_s, test_chain_verify_cb), + WOLFSSL_SUCCESS); + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); + + /* Stapling requested after the callback: the handshake fails before the + * callback is consulted, whether or not the server staples. */ + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + client_method, server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + &test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl_c, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_chain_verify_cb_expect_fail(&cbCtx, ssl_c, ssl_c, ssl_s, + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)), TEST_SUCCESS); + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); + +#ifdef HAVE_OCSP + /* So is must-staple on its own, both ways round. */ + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(NULL, TEST_CVC_CLIENT, + client_method, server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + &test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPMustStaple(ctx_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetChainVerifyCb(ctx_c, test_chain_verify_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_c, test_chain_verify_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ExpectIntEQ(test_chain_verify_cb_run(&cbCtx, TEST_CVC_CLIENT, + client_method, server_method, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + &test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPMustStaple(ctx_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_chain_verify_cb_expect_fail(&cbCtx, ssl_c, ssl_c, ssl_s, + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)), TEST_SUCCESS); + test_chain_verify_cb_free(&ctx_c, &ctx_s, &ssl_c, &ssl_s); +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 6c3fda89962..61523172602 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -62,6 +62,17 @@ int test_tls12_ecdhe_ecdsa_rsa_client_cert(void); int test_tls12_ecdhe_rsa_ecdsa_client_cert(void); int test_wolfSSL_alert_type_string(void); int test_wolfSSL_alert_desc_string(void); +int test_tls12_chain_verify_cb(void); +int test_tls12_chain_verify_cb_server(void); +int test_tls13_chain_verify_cb_async(void); +int test_tls13_chain_verify_cb_server(void); +int test_tls13_chain_verify_cb_reject(void); +int test_tls12_chain_verify_cb_bad_der(void); +int test_tls12_chain_verify_cb_bad_chain(void); +int test_tls13_chain_verify_cb_postauth(void); +int test_chain_verify_cb_min_key(void); +int test_chain_verify_cb_dtls(void); +int test_chain_verify_cb_stapling(void); int test_record_size_matches_build_message(void); int test_record_size_preserves_build_msg_state(void); int test_record_size_cache_invalidated_on_renegotiation(void); @@ -114,6 +125,17 @@ int test_wolfSSL_get_shared_ciphers(void); test_record_size_preserves_build_msg_state), \ TEST_DECL_GROUP("tls", \ test_record_size_cache_invalidated_on_renegotiation), \ - TEST_DECL_GROUP("tls", test_wolfSSL_get_shared_ciphers) + TEST_DECL_GROUP("tls", test_wolfSSL_get_shared_ciphers), \ + TEST_DECL_GROUP("tls", test_tls12_chain_verify_cb), \ + TEST_DECL_GROUP("tls", test_tls12_chain_verify_cb_server), \ + TEST_DECL_GROUP("tls", test_tls13_chain_verify_cb_async), \ + TEST_DECL_GROUP("tls", test_tls13_chain_verify_cb_server), \ + TEST_DECL_GROUP("tls", test_tls13_chain_verify_cb_reject), \ + TEST_DECL_GROUP("tls", test_tls12_chain_verify_cb_bad_der), \ + TEST_DECL_GROUP("tls", test_tls12_chain_verify_cb_bad_chain), \ + TEST_DECL_GROUP("tls", test_tls13_chain_verify_cb_postauth), \ + TEST_DECL_GROUP("tls", test_chain_verify_cb_min_key), \ + TEST_DECL_GROUP("tls", test_chain_verify_cb_dtls), \ + TEST_DECL_GROUP("tls", test_chain_verify_cb_stapling) #endif /* TESTS_API_TEST_TLS_H */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 449514541cd..20db8795730 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3245,6 +3245,93 @@ static WC_INLINE int test_rpk_memio_setup( #endif /* HAVE_RPK && !NO_TLS && !NO_WOLFSSL_CLIENT && !NO_WOLFSSL_SERVER */ +#if defined(HAVE_RPK) && defined(WOLFSSL_CHAIN_VERIFY_CB) && \ + defined(WOLFSSL_TLS13) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +static int test_chain_verify_cb_rpk_cb(WOLFSSL* ssl, + const WOLFSSL_BUFFER_INFO* certs, int certsSz, void* ctx) +{ + (void)ssl; + (void)certs; + (void)certsSz; + (*(int*)ctx)++; + return 0; +} +#endif + +/* A raw public key is not supported with the chain verify callback. */ +int test_tls13_chain_verify_cb_rpk(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && defined(WOLFSSL_CHAIN_VERIFY_CB) && \ + defined(WOLFSSL_TLS13) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + char certType[MAX_CLIENT_CERT_TYPE_CNT]; + int calls = 0; + int i; + + certType[0] = WOLFSSL_CERT_TYPE_RPK; + + /* First with raw public keys configured before the callback: the setters + * refuse it. Then with the callback set first: the handshake fails before + * the callback is consulted. */ + for (i = 0; i < 2; i++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ( + test_rpk_memio_setup( + &test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, + clntRpkCertFile, WOLFSSL_FILETYPE_ASN1, + svrRpkCertFile, WOLFSSL_FILETYPE_ASN1, + cliKeyFile, CERT_FILETYPE, + svrKeyFile, CERT_FILETYPE ) + , 0); + + if (i == 1) { + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_c, + test_chain_verify_cb_rpk_cb), WOLFSSL_SUCCESS); + wolfSSL_SetChainVerifyCtx(ssl_c, &calls); + } + + /* Both ends negotiate raw public keys only. */ + ExpectIntEQ(wolfSSL_set_client_cert_type(ssl_c, certType, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_server_cert_type(ssl_c, certType, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_client_cert_type(ssl_s, certType, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_server_cert_type(ssl_s, certType, 1), + WOLFSSL_SUCCESS); + + if (i == 0) { + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_c, + test_chain_verify_cb_rpk_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_s, + test_chain_verify_cb_rpk_cb), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + } + else { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(CHAIN_VERIFY_UNSUPPORTED_E)); + ExpectIntEQ(calls, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + int test_tls13_rpk_handshake(void) { EXPECT_DECLS; diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 7877fdc695c..b66b6ab3293 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -120,6 +120,7 @@ int test_tls13_KeyUpdate_sender_limit(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); int test_tls13_x25519_keyshare_masks_reserved_bit(void); +int test_tls13_chain_verify_cb_rpk(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -217,6 +218,7 @@ int test_tls13_x25519_keyshare_masks_reserved_bit(void); TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ - TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit) + TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit), \ + TEST_DECL_GROUP("tls13", test_tls13_chain_verify_cb_rpk) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/tests/utils.c b/tests/utils.c index a8566adc86e..ca5328bfcb5 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -192,6 +192,11 @@ int test_memio_do_handshake(WOLFSSL *ssl_c, WOLFSSL *ssl_s, if (ret < 0) return -1; } + #endif + #ifdef WOLFSSL_CHAIN_VERIFY_CB + else if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + /* application has not reached a verdict yet; retry */ + } #endif else if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE) { @@ -220,6 +225,11 @@ int test_memio_do_handshake(WOLFSSL *ssl_c, WOLFSSL *ssl_s, if (ret < 0) return -1; } + #endif + #ifdef WOLFSSL_CHAIN_VERIFY_CB + else if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) { + /* application has not reached a verdict yet; retry */ + } #endif else if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE) { diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 4c6893fbeb3..ceac7267355 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -253,7 +253,14 @@ enum wolfSSL_ErrorCodes { OCSP_NO_URL = -522, /* Cert advertises no OCSP responder * and no override URL is set */ - WOLFSSL_LAST_E = -522 + CHAIN_VERIFY_WANT_E = -523, /* Chain verify callback has not + * reached a verdict yet */ + CHAIN_VERIFY_CB_E = -524, /* Chain verify callback rejected the + * peer's certificates */ + CHAIN_VERIFY_UNSUPPORTED_E = -525, /* Chain verify callback used with + * DTLS, RPK or OCSP stapling */ + + WOLFSSL_LAST_E = -525 /* codes -1000 to -1999 are reserved for wolfCrypt. */ }; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 426894de5b0..96ef1f09b30 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2880,6 +2880,9 @@ typedef struct ProcPeerCertArgs { word16 fatal:1; word16 verifyErr:1; word16 dCertInit:1; +#ifdef WOLFSSL_CHAIN_VERIFY_CB + word16 chainDecoded:1; /* peer certs already decode-checked */ +#endif #ifdef WOLFSSL_TRUST_PEER_CERT word16 haveTrustPeer:1; /* was cert verified by loaded trusted peer cert */ #endif @@ -4376,6 +4379,10 @@ struct WOLFSSL_CTX { CertVerifyCallback verifyCertCb; void* verifyCertCbArg; #endif /* OPENSSL_ALL */ +#ifdef WOLFSSL_CHAIN_VERIFY_CB + ChainVerifyCb chainVerifyCb; /* replaces peer chain verification */ + void* chainVerifyCtx; /* chain verify callback user ctx */ +#endif #ifdef OPENSSL_EXTRA SSL_Msg_Cb protoMsgCb; /* inspect protocol message callback */ void* protoMsgCtx; /* user set context with msg callback */ @@ -5385,6 +5392,26 @@ enum asyncState { TLS_ASYNC_END }; +/* Handshake message processing can suspend part way through and resume when + * the application re-enters wolfSSL_connect()/wolfSSL_accept(). State for the + * message in flight is held in ssl->async. */ +#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) || \ + defined(WOLFSSL_CHAIN_VERIFY_CB) + #undef WOLFSSL_HAVE_HS_SUSPEND + #define WOLFSSL_HAVE_HS_SUSPEND +#endif + +/* True for error codes that suspend handshake processing instead of failing + * it. Only the codes whose feature is compiled in are recognized. */ +WOLFSSL_LOCAL int IsHsSuspendErr(int err); + +#ifdef WOLFSSL_CHAIN_VERIFY_CB +/* Non-zero when the context or object is configured for something the chain + * verify callback does not support. */ +WOLFSSL_LOCAL int ChainVerifyCbCheckCtx(const WOLFSSL_CTX* ctx); +WOLFSSL_LOCAL int ChainVerifyCbCheckSsl(const WOLFSSL* ssl); +#endif + /* sub-states for build message */ enum buildMsgState { BUILD_MSG_BEGIN = 0, @@ -6477,6 +6504,10 @@ struct WOLFSSL { WC_RNG* rng; void* verifyCbCtx; /* cert verify callback user ctx*/ VerifyCallback verifyCallback; /* cert verification callback */ +#ifdef WOLFSSL_CHAIN_VERIFY_CB + ChainVerifyCb chainVerifyCb; /* overrides the ctx's when set */ + void* chainVerifyCtx; /* chain verify callback user ctx */ +#endif void* heap; /* for user overrides */ #ifdef HAVE_WRITE_DUP WriteDup* dupWrite; /* valid pointer indicates ON */ diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index dd35a5f9291..a1836b38569 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1745,6 +1745,75 @@ WOLFSSL_API int wolfSSL_set_post_handshake_auth(WOLFSSL* ssl, int val); WOLFSSL_API void wolfSSL_SetCertCbCtx(WOLFSSL* ssl, void* ctx); WOLFSSL_API void wolfSSL_CTX_SetCertCbCtx(WOLFSSL_CTX* ctx, void* userCtx); +#ifdef WOLFSSL_CHAIN_VERIFY_CB +/* Replaces wolfSSL's peer certificate verification in its entirety. When set, + * wolfSSL builds no chain, verifies no signature, checks no date, revocation + * status, key usage or host name, and the verify callback set by + * wolfSSL_CTX_set_verify() is not called. The callback owns all of it. It is + * consulted even when verification was turned off with WOLFSSL_VERIFY_NONE, + * and a rejection fails the handshake either way. + * + * wolfSSL still owns the parsing. Every certificate is decoded before the + * callback runs, and malformed DER fails the handshake without the callback + * ever seeing it. So does a certificate the parser refuses regardless of the + * verify mode, such as one with a zero serial number (unless + * WOLFSSL_ASN_ALLOW_0_SERIAL is defined). Content wolfSSL does not understand + * or agree with - an unknown critical extension, an unsupported key or + * signature algorithm, a key usage inconsistent with the basic constraints - + * is not a decoding failure; it is passed through for the callback to judge. + * The only limit on the chain is the compile-time MAX_CHAIN_DEPTH; the verify + * depth does not apply. + * + * Two more things still apply. An empty Certificate message is handled by + * wolfSSL itself (see wolfSSL_CTX_set_verify() and the mutual-auth options) + * and the callback is not called for it, so certsSz is always at least 1. The + * minimum peer key sizes set by wolfSSL_CTX_SetMinRsaKey_Sz() and friends are + * still enforced on the peer's own certificate, even under + * WOLFSSL_VERIFY_NONE, because the handshake uses that key directly. + * + * Not supported with the callback: DTLS, raw public keys (RFC 7250) and OCSP + * stapling. Setting the callback on a context or object already configured + * for one of them fails with CHAIN_VERIFY_UNSUPPORTED_E, and so does the + * handshake of a connection that uses one of them, before its Certificate + * message is parsed. + * + * certs DER certificates in the order the peer sent them, one + * WOLFSSL_BUFFER_INFO each: certs[0] is the peer's own certificate, + * the rest are the chain it supplied. The buffers are wolfSSL's own + * receive buffer: treat them as read-only, and only for the duration + * of the call. + * certsSz Number of entries in certs, always 1 or more. + * ctx Value set with wolfSSL_SetChainVerifyCtx(), or failing that with + * wolfSSL_CTX_SetChainVerifyCtx(). + * + * Return 0 to accept, CHAIN_VERIFY_WANT_E to suspend the handshake, or any + * other value to reject. Rejection fails the handshake with + * CHAIN_VERIFY_CB_E and sends one fatal bad_certificate alert; the returned + * value is not reported to the peer. + * + * After CHAIN_VERIFY_WANT_E the callback is called again with the same + * certificates when the application re-enters wolfSSL_connect(), + * wolfSSL_accept(), wolfSSL_read() or wolfSSL_write(). A certificate received + * after the handshake (TLS 1.3 post-handshake authentication) arrives inside + * wolfSSL_read() and is resumed by calling wolfSSL_read() again; until then + * wolfSSL_write() fails with CHAIN_VERIFY_WANT_E as well. + */ +typedef int (*ChainVerifyCb)(WOLFSSL* ssl, const WOLFSSL_BUFFER_INFO* certs, + int certsSz, void* ctx); + +/* The callback and user context set on an SSL/TLS object take precedence over + * those set on its context. The setters return WOLFSSL_SUCCESS, BAD_FUNC_ARG, + * or CHAIN_VERIFY_UNSUPPORTED_E when the context or object is configured for + * DTLS, raw public keys or OCSP stapling. */ +WOLFSSL_API int wolfSSL_CTX_SetChainVerifyCb(WOLFSSL_CTX* ctx, + ChainVerifyCb cb); +WOLFSSL_API int wolfSSL_SetChainVerifyCb(WOLFSSL* ssl, ChainVerifyCb cb); +WOLFSSL_API void wolfSSL_CTX_SetChainVerifyCtx(WOLFSSL_CTX* ctx, + void* userCtx); +WOLFSSL_API void wolfSSL_SetChainVerifyCtx(WOLFSSL* ssl, void* ctx); +WOLFSSL_API void* wolfSSL_GetChainVerifyCtx(const WOLFSSL* ssl); +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ + WOLFSSL_ABI WOLFSSL_API int wolfSSL_pending(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_has_pending(const WOLFSSL* ssl); diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index b66ae2cac39..4d621e78bc1 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -5491,11 +5491,12 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #endif #if !defined(WOLFSSL_NO_ASYNC_IO) || defined(WOLFSSL_ASYNC_CRYPT) || \ - defined(WOLFSSL_NONBLOCK_OCSP) + defined(WOLFSSL_NONBLOCK_OCSP) || defined(WOLFSSL_CHAIN_VERIFY_CB) /* Enable asynchronous support in TLS functions to support one or more of * the following: * - re-entry after a network blocking return * - re-entry after OCSP blocking return + * - re-entry after a chain verify callback deferred its verdict * - asynchronous cryptography */ #undef WOLFSSL_ASYNC_IO #define WOLFSSL_ASYNC_IO