diff --git a/src/internal.c b/src/internal.c index cd897621c54..ccde1dcaa47 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19960,15 +19960,27 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #ifdef HAVE_SECURE_RENEGOTIATION + /* Not gated on the extension being negotiated: a session + * records this hash for a later resumption, which may + * negotiate it and renegotiate. */ if (args->fatal == 0 && !IsAtLeastTLSv1_3(ssl->version) - && ssl->secure_renegotiation - && ssl->secure_renegotiation->enabled) { - - if (IsEncryptionOn(ssl, 0)) { + && ssl->secure_renegotiation) { + byte certHash[KEYID_SIZE]; + int hashRet; + + /* Hash the whole certificate, not just the subject, so a + * different certificate for the same name is caught. */ + hashRet = CalcHashId(args->dCert->source, + args->dCert->maxIdx, certHash); + if (hashRet != 0) { + args->fatal = 1; + ret = hashRet; + } + else if (IsEncryptionOn(ssl, 0)) { /* compare against previous time */ - if (ssl->secure_renegotiation->subject_hash_set) { - if (XMEMCMP(args->dCert->subjectHash, - ssl->secure_renegotiation->subject_hash, + if (ssl->secure_renegotiation->peer_cert_hash_set) { + if (XMEMCMP(certHash, + ssl->secure_renegotiation->peer_cert_hash, KEYID_SIZE) != 0) { WOLFSSL_MSG( "Peer sent different cert during scr, fatal"); @@ -19977,13 +19989,22 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, WOLFSSL_ERROR_VERBOSE(ret); } } + else if (ssl->secure_renegotiation->peer_cert_unknown) { + /* Resumed a session that does not say which cert + * the peer had, so this one can't be checked. */ + WOLFSSL_MSG( + "Peer cert of resumed session unknown, fatal"); + args->fatal = 1; + ret = SCR_DIFFERENT_CERT_E; + WOLFSSL_ERROR_VERBOSE(ret); + } } /* cache peer's hash */ if (args->fatal == 0) { - XMEMCPY(ssl->secure_renegotiation->subject_hash, - args->dCert->subjectHash, KEYID_SIZE); - ssl->secure_renegotiation->subject_hash_set = 1; + XMEMCPY(ssl->secure_renegotiation->peer_cert_hash, + certHash, KEYID_SIZE); + ssl->secure_renegotiation->peer_cert_hash_set = 1; } } #endif /* HAVE_SECURE_RENEGOTIATION */ @@ -30136,6 +30157,65 @@ int IsSCR(WOLFSSL* ssl) } +#ifdef HAVE_SECURE_RENEGOTIATION +/* Get what a session made from this connection records about the peer + * certificate, for the renegotiation check of a later resumption. + * + * @param [in] ssl SSL/TLS object. + * @param [out] state enum ScrPeerCertState value. + * @param [out] hash Peer certificate hash. KEYID_SIZE bytes. + */ +void ScrPeerCertToSession(const WOLFSSL* ssl, byte* state, byte* hash) +{ + const SecureRenegotiation* scr = ssl->secure_renegotiation; + + XMEMSET(hash, 0, KEYID_SIZE); + /* Without the object no peer certificate was hashed. */ + if ((scr == NULL) || IsAtLeastTLSv1_3(ssl->version)) { + *state = SCR_PEER_CERT_UNKNOWN; + } + else if (scr->peer_cert_hash_set) { + XMEMCPY(hash, scr->peer_cert_hash, KEYID_SIZE); + *state = SCR_PEER_CERT_HASH; + } + else if (scr->peer_cert_unknown) { + *state = SCR_PEER_CERT_UNKNOWN; + } + else { + *state = SCR_PEER_CERT_NONE; + } +} + +/* Take the peer certificate a renegotiation must match from the session being + * resumed, as a resumption has no Certificate message to take it from. + * + * @param [in, out] ssl SSL/TLS object resuming ssl->session. + */ +void ScrPeerCertFromSession(WOLFSSL* ssl) +{ + SecureRenegotiation* scr = ssl->secure_renegotiation; + + if (scr == NULL) + return; + + scr->peer_cert_hash_set = 0; + scr->peer_cert_unknown = 0; + switch (ssl->session->scrPeerCertState) { + case SCR_PEER_CERT_HASH: + XMEMCPY(scr->peer_cert_hash, ssl->session->scrPeerCertHash, + KEYID_SIZE); + scr->peer_cert_hash_set = 1; + break; + case SCR_PEER_CERT_NONE: + /* A certificate may be given for the first time. */ + break; + default: + scr->peer_cert_unknown = 1; + break; + } +} +#endif /* HAVE_SECURE_RENEGOTIATION */ + #if !defined(NO_TLS) && defined(WOLFSSL_TLS13) && \ !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) /* @@ -36734,6 +36814,9 @@ static int IsTls13CipherSuite(byte first, byte second) /* SERVER: peer auth based on session secret. */ ssl->options.peerAuthGood = (ret == 0); ssl->options.serverState = SERVER_HELLODONE_COMPLETE; + #ifdef HAVE_SECURE_RENEGOTIATION + ScrPeerCertFromSession(ssl); + #endif return ret; } @@ -42823,6 +42906,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) /* SERVER: peer auth based on session secret. */ ssl->options.peerAuthGood = (ret == 0); ssl->options.clientState = CLIENT_KEYEXCHANGE_COMPLETE; + #ifdef HAVE_SECURE_RENEGOTIATION + ScrPeerCertFromSession(ssl); + #endif } if (ret != 0) WOLFSSL_ERROR_VERBOSE(ret); @@ -42969,6 +43055,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ssl->options.peerAuthGood = (ret == 0); ssl->options.clientState = CLIENT_KEYEXCHANGE_COMPLETE; } + #ifdef HAVE_SECURE_RENEGOTIATION + if (ret == 0 && ssl->options.resuming) + ScrPeerCertFromSession(ssl); + #endif } @@ -44521,6 +44611,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->peerAuthOk = (byte)SSL_PEER_CERT_VERIFIED(ssl); it->isPSK = (byte)ssl->options.usingPSK_cipher; + #ifdef HAVE_SECURE_RENEGOTIATION + ScrPeerCertToSession(ssl, &it->scrPeerCertState, it->scrPeerCertHash); + #endif #ifdef WOLFSSL_EARLY_DATA c32toa(ssl->options.maxEarlyDataSz, it->maxEarlyDataSz); @@ -45098,6 +45191,11 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) /* After the DupSession above, which would otherwise overwrite it. */ ssl->session->peerAuthOk = it->peerAuthOk; ssl->session->isPSK = it->isPSK; +#ifdef HAVE_SECURE_RENEGOTIATION + ssl->session->scrPeerCertState = it->scrPeerCertState; + XMEMCPY(ssl->session->scrPeerCertHash, it->scrPeerCertHash, + KEYID_SIZE); +#endif if (!IsAtLeastTLSv1_3(ssl->version)) { if (ssl->arrays == NULL) @@ -45189,6 +45287,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->haveEMS = (byte)sess->haveEMS; it->peerAuthOk = sess->peerAuthOk; it->isPSK = sess->isPSK; +#ifdef HAVE_SECURE_RENEGOTIATION + it->scrPeerCertState = sess->scrPeerCertState; + XMEMCPY(it->scrPeerCertHash, sess->scrPeerCertHash, KEYID_SIZE); +#endif c32toa(sess->ticketAdd, it->ageAdd); c16toa(sess->namedGroup, it->namedGroup); if (sess->ticketNonce.len <= MAX_TICKET_NONCE_STATIC_SZ) { diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 55c7dbed0a3..385a03a52c9 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -2957,6 +2957,10 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) /* peerAuthOk and isPSK */ size += OPAQUE8_LEN; #endif +#ifdef HAVE_SECURE_RENEGOTIATION + /* scrPeerCertState | scrPeerCertHash */ + size += OPAQUE8_LEN + KEYID_SIZE; +#endif if (p != NULL) { unsigned char *data; @@ -3054,6 +3058,11 @@ int wolfSSL_i2d_SSL_SESSION(WOLFSSL_SESSION* sess, unsigned char** p) #endif /* !NO_WOLFSSL_SERVER && !NO_TLS */ #ifndef NO_WOLFSSL_SERVER data[idx++] = (byte)(sess->peerAuthOk | (sess->isPSK << 1)); +#endif +#ifdef HAVE_SECURE_RENEGOTIATION + data[idx++] = sess->scrPeerCertState; + XMEMCPY(data + idx, sess->scrPeerCertHash, KEYID_SIZE); + idx += KEYID_SIZE; #endif } #endif /* HAVE_EXT_CACHE */ @@ -3375,6 +3384,15 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, s->isPSK = (data[idx] & 0x02) != 0; idx++; #endif +#ifdef HAVE_SECURE_RENEGOTIATION + if (i - idx < OPAQUE8_LEN + KEYID_SIZE) { + ret = BUFFER_ERROR; + goto end; + } + s->scrPeerCertState = data[idx++]; + XMEMCPY(s->scrPeerCertHash, data + idx, KEYID_SIZE); + idx += KEYID_SIZE; +#endif if (sess != NULL) { wolfSSL_FreeSession(NULL, *sess); @@ -3966,6 +3984,10 @@ void SetupSession(WOLFSSL* ssl) session->isPSK = 0; } #endif +#ifdef HAVE_SECURE_RENEGOTIATION + ScrPeerCertToSession(ssl, &session->scrPeerCertState, + session->scrPeerCertHash); +#endif #ifdef WOLFSSL_SESSION_ID_CTX /* If using compatibility layer then check for and copy over session context * id. */ diff --git a/tests/api.c b/tests/api.c index 3874969255e..40d166e0d8a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -47090,6 +47090,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls12_chacha20_poly1305_bad_tag), TEST_DECL(test_tls13_null_cipher_bad_hmac), TEST_DECL(test_scr_verify_data_mismatch), + TEST_DECL(test_scr_different_cert), + TEST_DECL(test_scr_resumed_different_cert), TEST_DECL(test_scr_no_renegotiation_option), TEST_DECL(test_helloRequest_no_renegotiation_option), TEST_DECL(test_helloRequest_advertise_only_refused), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index a4eefb08ee2..47e92c5e92d 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -1320,6 +1320,438 @@ int test_scr_verify_data_mismatch(void) return EXPECT_RESULT(); } +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_CERT_GEN) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_RSA) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) +/* Make a server certificate for subjKey signed by the 2048-bit test CA, with + * the subject of the test server certificate, or that subject with commonName + * cn when cn is not NULL. Returns the DER length, or < 0 on failure. */ +static int gen_scr_server_cert(byte* out, int outSz, RsaKey* subjKey, + RsaKey* caKey, WC_RNG* rng, const char* cn) +{ + Cert cert; + + if (wc_InitCert(&cert) != 0) + return -1; + cert.sigType = CTC_SHA256wRSA; + if (wc_SetSubjectBuffer(&cert, server_cert_der_2048, + (int)sizeof_server_cert_der_2048) != 0) + return -1; + if (cn != NULL) + XSTRNCPY(cert.subject.commonName, cn, CTC_NAME_SIZE - 1); + if (wc_SetIssuerBuffer(&cert, ca_cert_der_2048, + (int)sizeof_ca_cert_der_2048) != 0) + return -1; + if (wc_MakeCert(&cert, out, (word32)outSz, subjKey, NULL, rng) < 0) + return -1; + return wc_SignCert(cert.bodySz, cert.sigType, out, (word32)outSz, caKey, + NULL, rng); +} +#endif + +/* During a TLS 1.2 secure renegotiation the peer must present the same + * certificate as in the initial handshake. A certificate with the same + * subject but a different key, or a re-issued certificate, must be refused + * with SCR_DIFFERENT_CERT_E just like one with a different subject. */ +int test_scr_different_cert(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_CERT_GEN) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_RSA) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) + enum { SCR_SAME, SCR_OTHER_DN, SCR_OTHER_KEY, SCR_REISSUED, SCR_NUM }; + WC_RNG rng; + RsaKey caKey, srvKey, altKey; + int rngI = 0, caI = 0, srvI = 0, altI = 0; + word32 idx; + byte* certs[SCR_NUM] = { NULL, NULL, NULL, NULL }; + int certSz[SCR_NUM] = { 0, 0, 0, 0 }; + int i; + + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) rngI = 1; + ExpectIntEQ(wc_InitRsaKey(&caKey, NULL), 0); + if (EXPECT_SUCCESS()) caI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(ca_key_der_2048, &idx, &caKey, + sizeof_ca_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&srvKey, NULL), 0); + if (EXPECT_SUCCESS()) srvI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(server_key_der_2048, &idx, &srvKey, + sizeof_server_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&altKey, NULL), 0); + if (EXPECT_SUCCESS()) altI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(client_key_der_2048, &idx, &altKey, + sizeof_client_key_der_2048), 0); + + for (i = 0; i < SCR_NUM; i++) { + ExpectNotNull(certs[i] = (byte*)XMALLOC(FOURK_BUF, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + } + /* SCR_SAME is the initial certificate; the others replace it. */ + ExpectIntGT(certSz[SCR_SAME] = gen_scr_server_cert(certs[SCR_SAME], + FOURK_BUF, &srvKey, &caKey, &rng, NULL), 0); + ExpectIntGT(certSz[SCR_OTHER_DN] = gen_scr_server_cert(certs[SCR_OTHER_DN], + FOURK_BUF, &srvKey, &caKey, &rng, "other.wolfssl.com"), 0); + ExpectIntGT(certSz[SCR_OTHER_KEY] = gen_scr_server_cert( + certs[SCR_OTHER_KEY], FOURK_BUF, &altKey, &caKey, &rng, NULL), 0); + ExpectIntGT(certSz[SCR_REISSUED] = gen_scr_server_cert(certs[SCR_REISSUED], + FOURK_BUF, &srvKey, &caKey, &rng, NULL), 0); + + for (i = 0; i < SCR_NUM && EXPECT_SUCCESS(); i++) { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + byte data; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = test_ctx.s_ciphers = + "ECDHE-RSA-AES128-GCM-SHA256"; + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, + wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_certificate_buffer(ssl_s, certs[SCR_SAME], + certSz[SCR_SAME], WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_buffer(ssl_s, server_key_der_2048, + sizeof_server_key_der_2048, WOLFSSL_FILETYPE_ASN1), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Server switches certificate before the renegotiation. */ + ExpectIntEQ(wolfSSL_use_certificate_buffer(ssl_s, certs[i], certSz[i], + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + if (i == SCR_OTHER_KEY) { + ExpectIntEQ(wolfSSL_use_PrivateKey_buffer(ssl_s, + client_key_der_2048, sizeof_client_key_der_2048, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + } + + ExpectIntEQ(wolfSSL_Rehandshake(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_read(ssl_s, &data, 1), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + if (i == SCR_SAME) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), + WC_NO_ERR_TRACE(SCR_DIFFERENT_CERT_E)); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + + for (i = 0; i < SCR_NUM; i++) + XFREE(certs[i], NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (altI) wc_FreeRsaKey(&altKey); + if (srvI) wc_FreeRsaKey(&srvKey); + if (caI) wc_FreeRsaKey(&caKey); + if (rngI) wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_CERT_GEN) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_RSA) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) && \ + !defined(NO_SESSION_CACHE) +/* How a test of renegotiating a resumed session is run. */ +typedef struct ScrResumeCase { + byte useTicket; /* resume with a session ticket, not a session ID */ + byte checkServer; /* 0: client checks the server cert, 1: the reverse */ + byte serialize; /* resume from an i2d/d2i copy of the session */ + byte forget; /* the session does not record the peer cert */ + const byte* cert; /* cert of the checked side, NULL for none */ + int certSz; + const byte* key; + int keySz; + const byte* renegCert; /* cert of the checked side when renegotiating */ + int renegCertSz; + const byte* renegKey; + int renegKeySz; + int expErr; /* 0 for success, else the error expected */ +} ScrResumeCase; + +/* Load the checked side's certificate and key, and on a server have it ask for + * the client's certificate. Returns 0 on success. */ +static int scr_resume_load(WOLFSSL* ssl_c, WOLFSSL* ssl_s, byte checkServer, + const byte* cert, int certSz, const byte* key, int keySz) +{ + EXPECT_DECLS; + WOLFSSL* ssl = checkServer ? ssl_c : ssl_s; + + if (cert != NULL) { + ExpectIntEQ(wolfSSL_use_certificate_buffer(ssl, cert, certSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_buffer(ssl, key, keySz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + } + if (checkServer) { + wolfSSL_set_verify(ssl_s, (cert != NULL) ? WOLFSSL_VERIFY_PEER : + WOLFSSL_VERIFY_NONE, NULL); + } + return EXPECT_SUCCESS() ? 0 : -1; +} + +/* Do a full handshake, resume it on a new connection and then renegotiate that + * connection, the checked side presenting renegCert. */ +static int scr_resume_run(const ScrResumeCase* tc) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_SESSION *session = NULL; + byte data; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "ECDHE-RSA-AES128-GCM-SHA256"; + + /* The first connection does a full handshake, the second resumes it. */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (i == 0 && tc->checkServer) { + ExpectIntEQ(wolfSSL_CTX_load_verify_buffer(ctx_s, ca_cert_der_2048, + sizeof_ca_cert_der_2048, WOLFSSL_FILETYPE_ASN1), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_s), WOLFSSL_SUCCESS); + #ifdef HAVE_SESSION_TICKET + if (tc->useTicket) + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + #endif + ExpectIntEQ(scr_resume_load(ssl_c, ssl_s, tc->checkServer, tc->cert, + tc->certSz, tc->key, tc->keySz), 0); + if (i == 1) + ExpectIntEQ(wolfSSL_set_session(ssl_c, session), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_c), i); + + if (i == 0) { + ExpectNotNull(session = wolfSSL_get1_session(ssl_c)); + #ifdef HAVE_SESSION_TICKET + if (tc->useTicket) + ExpectIntGT(session->ticketLen, 0); + #endif + #if defined(OPENSSL_EXTRA) && defined(HAVE_EXT_CACHE) + if (tc->serialize) { + WOLFSSL_SESSION* copy = NULL; + unsigned char* der = NULL; + const unsigned char* p; + int sz = 0; + + ExpectIntGT(sz = wolfSSL_i2d_SSL_SESSION(session, &der), 0); + p = der; + ExpectNotNull(copy = wolfSSL_d2i_SSL_SESSION(NULL, &p, sz)); + XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL); + wolfSSL_SESSION_free(session); + session = copy; + } + #endif + if (tc->forget && session != NULL) + session->scrPeerCertState = SCR_PEER_CERT_UNKNOWN; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + } + } + + ExpectIntEQ(scr_resume_load(ssl_c, ssl_s, tc->checkServer, tc->renegCert, + tc->renegCertSz, tc->renegKey, tc->renegKeySz), 0); + ExpectIntEQ(wolfSSL_Rehandshake(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_read(ssl_s, &data, 1), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + if (tc->expErr == 0) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(tc->checkServer ? ssl_s : ssl_c, 0), + tc->expErr); + } + + wolfSSL_SESSION_free(session); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* A resumed handshake has no Certificate message, so the certificate a + * renegotiation of it must match is the one of the session resumed. */ +int test_scr_resumed_different_cert(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_CERT_GEN) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_RSA) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) && \ + !defined(NO_SESSION_CACHE) + enum { SCR_SAME, SCR_OTHER_DN, SCR_OTHER_KEY, SCR_NUM }; + WC_RNG rng; + RsaKey caKey, srvKey, altKey; + int rngI = 0, caI = 0, srvI = 0, altI = 0; + word32 idx; + byte* certs[SCR_NUM] = { NULL, NULL, NULL }; + int certSz[SCR_NUM] = { 0, 0, 0 }; + const byte* srvK = server_key_der_2048; + int srvKSz = (int)sizeof_server_key_der_2048; + const byte* altK = client_key_der_2048; + int altKSz = (int)sizeof_client_key_der_2048; + const int diff = WC_NO_ERR_TRACE(SCR_DIFFERENT_CERT_E); + int t; + int c; + + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) rngI = 1; + ExpectIntEQ(wc_InitRsaKey(&caKey, NULL), 0); + if (EXPECT_SUCCESS()) caI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(ca_key_der_2048, &idx, &caKey, + sizeof_ca_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&srvKey, NULL), 0); + if (EXPECT_SUCCESS()) srvI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(server_key_der_2048, &idx, &srvKey, + sizeof_server_key_der_2048), 0); + ExpectIntEQ(wc_InitRsaKey(&altKey, NULL), 0); + if (EXPECT_SUCCESS()) altI = 1; + idx = 0; + ExpectIntEQ(wc_RsaPrivateKeyDecode(client_key_der_2048, &idx, &altKey, + sizeof_client_key_der_2048), 0); + + for (c = 0; c < SCR_NUM; c++) { + ExpectNotNull(certs[c] = (byte*)XMALLOC(FOURK_BUF, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + } + ExpectIntGT(certSz[SCR_SAME] = gen_scr_server_cert(certs[SCR_SAME], + FOURK_BUF, &srvKey, &caKey, &rng, NULL), 0); + ExpectIntGT(certSz[SCR_OTHER_DN] = gen_scr_server_cert(certs[SCR_OTHER_DN], + FOURK_BUF, &srvKey, &caKey, &rng, "other.wolfssl.com"), 0); + ExpectIntGT(certSz[SCR_OTHER_KEY] = gen_scr_server_cert( + certs[SCR_OTHER_KEY], FOURK_BUF, &altKey, &caKey, &rng, NULL), 0); + + /* Session ID and ticket resumption, the client and the server checking. */ + for (t = 0; t < 4 && EXPECT_SUCCESS(); t++) { + ScrResumeCase tc; + byte useTicket = (byte)(t & 1); + byte checkServer = (byte)(t >> 1); + + #ifndef HAVE_SESSION_TICKET + if (useTicket) + continue; + #endif + for (c = 0; c < SCR_NUM && EXPECT_SUCCESS(); c++) { + XMEMSET(&tc, 0, sizeof(tc)); + tc.useTicket = useTicket; + tc.checkServer = checkServer; + tc.cert = certs[SCR_SAME]; + tc.certSz = certSz[SCR_SAME]; + tc.key = srvK; + tc.keySz = srvKSz; + tc.renegCert = certs[c]; + tc.renegCertSz = certSz[c]; + tc.renegKey = (c == SCR_OTHER_KEY) ? altK : srvK; + tc.renegKeySz = (c == SCR_OTHER_KEY) ? altKSz : srvKSz; + tc.expErr = (c == SCR_SAME) ? 0 : diff; + ExpectIntEQ(scr_resume_run(&tc), TEST_SUCCESS); + } + + /* No client cert at first: one may be asked for when renegotiating. */ + if (checkServer) { + XMEMSET(&tc, 0, sizeof(tc)); + tc.useTicket = useTicket; + tc.checkServer = checkServer; + tc.renegCert = certs[SCR_SAME]; + tc.renegCertSz = certSz[SCR_SAME]; + tc.renegKey = srvK; + tc.renegKeySz = srvKSz; + ExpectIntEQ(scr_resume_run(&tc), TEST_SUCCESS); + } + } + +#if defined(OPENSSL_EXTRA) && defined(HAVE_EXT_CACHE) + /* The certificate survives serializing the session. */ + if (EXPECT_SUCCESS()) { + ScrResumeCase tc; + + XMEMSET(&tc, 0, sizeof(tc)); + tc.serialize = 1; + tc.cert = certs[SCR_SAME]; + tc.certSz = certSz[SCR_SAME]; + tc.key = srvK; + tc.keySz = srvKSz; + tc.renegCert = certs[SCR_OTHER_KEY]; + tc.renegCertSz = certSz[SCR_OTHER_KEY]; + tc.renegKey = altK; + tc.renegKeySz = altKSz; + tc.expErr = diff; + ExpectIntEQ(scr_resume_run(&tc), TEST_SUCCESS); + } +#endif + + /* A session that does not record the certificate can't be checked, so + * even the same certificate is refused. */ + if (EXPECT_SUCCESS()) { + ScrResumeCase tc; + + XMEMSET(&tc, 0, sizeof(tc)); + tc.forget = 1; + tc.cert = certs[SCR_SAME]; + tc.certSz = certSz[SCR_SAME]; + tc.key = srvK; + tc.keySz = srvKSz; + tc.renegCert = certs[SCR_SAME]; + tc.renegCertSz = certSz[SCR_SAME]; + tc.renegKey = srvK; + tc.renegKeySz = srvKSz; + tc.expErr = diff; + ExpectIntEQ(scr_resume_run(&tc), TEST_SUCCESS); + } + + for (c = 0; c < SCR_NUM; c++) + XFREE(certs[c], NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (altI) wc_FreeRsaKey(&altKey); + if (srvI) wc_FreeRsaKey(&srvKey); + if (caI) wc_FreeRsaKey(&caKey); + if (rngI) wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + /* F-4144: WOLFSSL_OP_NO_RENEGOTIATION on the server must refuse a * client-initiated renegotiation with a no_renegotiation *warning* while * keeping the established connection alive, rather than aborting it. */ diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 3ba806a662b..cb76f6fc14e 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -38,6 +38,8 @@ int test_tls_ems_server_disable_secret_cb(void); int test_tls12_chacha20_poly1305_bad_tag(void); int test_tls13_null_cipher_bad_hmac(void); int test_scr_verify_data_mismatch(void); +int test_scr_different_cert(void); +int test_scr_resumed_different_cert(void); int test_scr_no_renegotiation_option(void); int test_helloRequest_no_renegotiation_option(void); int test_helloRequest_advertise_only_refused(void); @@ -88,6 +90,8 @@ int test_wolfSSL_custom_ext_add_null(void); TEST_DECL_GROUP("tls", test_tls_ems_resumption_downgrade), \ TEST_DECL_GROUP("tls", test_tls_ems_resumption_server_downgrade), \ TEST_DECL_GROUP("tls", test_scr_verify_data_mismatch), \ + TEST_DECL_GROUP("tls", test_scr_different_cert), \ + TEST_DECL_GROUP("tls", test_scr_resumed_different_cert), \ TEST_DECL_GROUP("tls", test_scr_no_renegotiation_option), \ TEST_DECL_GROUP("tls", test_helloRequest_no_renegotiation_option), \ TEST_DECL_GROUP("tls", test_helloRequest_advertise_only_refused), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 3ea829c0b4e..af27924212b 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3966,21 +3966,34 @@ typedef struct SecureRenegotiation { WC_BITFIELD startScr:1; /* server requested client to start scr */ WC_BITFIELD renegInfoSeen:1; /* renegotiation_info ext seen this * handshake (RFC 5746 3.7) */ - WC_BITFIELD subject_hash_set:1; /* if peer cert hash is set */ + WC_BITFIELD peer_cert_hash_set:1; /* if peer cert hash is set */ #ifdef HAVE_SECURE_RENEGOTIATION WC_BITFIELD advertiseOnly:1; /* extension advertised for the RFC * 5746 initial-handshake check only; * refuse peer-initiated renegotiation */ + WC_BITFIELD peer_cert_unknown:1; /* resumed session that does not + * record its peer cert, so no + * peer cert can be checked */ #endif enum key_cache_state cache_status; /* track key cache state */ byte client_verify_data[TLS_FINISHED_SZ]; /* cached */ byte server_verify_data[TLS_FINISHED_SZ]; /* cached */ - byte subject_hash[KEYID_SIZE]; /* peer cert hash */ + byte peer_cert_hash[KEYID_SIZE]; /* peer cert hash */ Keys tmp_keys; /* can't overwrite real keys yet */ } SecureRenegotiation; WOLFSSL_LOCAL int TLSX_UseSecureRenegotiation(TLSX** extensions, void* heap); +#ifdef HAVE_SECURE_RENEGOTIATION +/* What a session records about the peer certificate for the secure + * renegotiation check (scrPeerCertState). */ +enum ScrPeerCertState { + SCR_PEER_CERT_UNKNOWN = 0, /* not recorded */ + SCR_PEER_CERT_NONE = 1, /* peer sent no certificate */ + SCR_PEER_CERT_HASH = 2 /* scrPeerCertHash is the peer cert hash */ +}; +#endif + #ifdef HAVE_SERVER_RENEGOTIATION_INFO WOLFSSL_LOCAL int TLSX_AddEmptyRenegotiationInfo(TLSX** extensions, void* heap); #endif @@ -4049,6 +4062,10 @@ typedef struct InternalTicket { byte sessionCtxSz; /* sessionCtx length */ byte sessionCtx[ID_LEN]; /* app specific context id */ #endif /* OPENSSL_EXTRA */ +#ifdef HAVE_SECURE_RENEGOTIATION + byte scrPeerCertState; /* enum ScrPeerCertState */ + byte scrPeerCertHash[KEYID_SIZE]; /* peer cert hash */ +#endif #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) byte peerCertLen[OPAQUE16_LEN]; /* peer cert length */ @@ -5434,6 +5451,11 @@ struct WOLFSSL_SESSION { /* A PSK authenticated the client. Server sessions only. */ byte isPSK:1; #endif +#ifdef HAVE_SECURE_RENEGOTIATION + /* Peer certificate to hold a renegotiation of a resumption to. */ + byte scrPeerCertState; /* enum ScrPeerCertState */ + byte scrPeerCertHash[KEYID_SIZE]; +#endif }; #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ @@ -8053,6 +8075,11 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); WOLFSSL_LOCAL int DtlsCheckOrder(WOLFSSL* ssl, int order); #endif WOLFSSL_LOCAL int IsSCR(WOLFSSL* ssl); +#ifdef HAVE_SECURE_RENEGOTIATION + WOLFSSL_LOCAL void ScrPeerCertToSession(const WOLFSSL* ssl, byte* state, + byte* hash); + WOLFSSL_LOCAL void ScrPeerCertFromSession(WOLFSSL* ssl); +#endif WOLFSSL_LOCAL int IsDtlsNotSctpMode(WOLFSSL* ssl); WOLFSSL_LOCAL int IsDtlsNotSrtpMode(WOLFSSL* ssl);