ARTICLE AD BOX
Using OpenSSL 3.3.0, we are having trouble with TLS 1.2 renegotiation.
We implemented C++ applications on both the server and client sides, and configured options to enable renegotiation. In practice, when the server initiates renegotiation, the renegotiation itself succeeds on both the server and client.
However, immediately afterward, the SSL_write call on the client side fails. We have the client send messages periodically, and the server is configured to perform a renegotiation after one minute has passed. However, the transmission immediately after the renegotiation completes always fails.
The client is waiting as well, so could there be an issue with how we are performing the send?
Client
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <errno.h> #include <string.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <WS2tcpip.h> #include <string> #include <iostream> #include <filesystem> #include <thread> namespace fs = std::filesystem; #pragma comment (lib, "ws2_32.lib") #define FAIL -1 void LoadCertificates(SSL_CTX* ctx, const char* CertFile, const char* KeyFile, const char* CAFile) { SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); SSL_CTX_set_verify_depth(ctx, 4); } int OpenConnection(const char* hostname, int port) { int sd; struct hostent* host; struct sockaddr_in addr; WSAData data; WORD ver = MAKEWORD(2, 2); int wsResult = WSAStartup(ver, &data); if (wsResult != 0) { printf("winsock error"); return 0; } if ((host = gethostbyname(hostname)) == NULL) { perror(hostname); abort(); } sd = (int)socket(PF_INET, SOCK_STREAM, 0); ZeroMemory(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = *(long*)(host->h_addr); if (connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { closesocket(sd); perror(hostname); abort(); } return sd; } SSL_CTX* InitCTX(void) { SSL_CTX* ctx; OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */ SSL_load_error_strings(); /* Bring in and register error messages */ const SSL_METHOD* method = TLS_client_method(); /* Create new client-method instance */ ctx = SSL_CTX_new(method); /* Create new context */ if (ctx == NULL) { ERR_print_errors_fp(stderr); abort(); } // TLS1.2 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION); #ifdef SSL_OP_NO_RENEGOTIATION SSL_CTX_clear_options(ctx, SSL_OP_NO_RENEGOTIATION); #endif return ctx; } void ShowCerts(SSL* ssl) { X509* cert; char* line; cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */ if (cert != NULL) { printf("Server certificates:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("Subject: %s\n", line); free(line); /* free the malloc'ed string */ line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("Issuer: %s\n", line); free(line); /* free the malloc'ed string */ X509_free(cert); /* free the malloc'ed certificate copy */ } else printf("No certificates.\n"); } int main() { SSL_CTX* ctx; int server; SSL* ssl; char buf[1024]; int bytes; char hostname[] = "127.0.0.1"; char portnum[] = "54000"; int counter = 0; bool running = true; auto current_dir_path = fs::current_path(); auto pem_cert_file_path = current_dir_path / "Client_certificate.pem"; auto pem_pkey_file_path = current_dir_path / "Client_private_key.pem"; auto pem_ca_file_path = current_dir_path / "Server_CA_certificate.pem"; SSL_library_init(); ctx = InitCTX(); LoadCertificates(ctx, pem_cert_file_path.string().c_str(), pem_pkey_file_path.string().c_str(), pem_ca_file_path.string().c_str() ); printf("client certificate loaded\n"); server = OpenConnection(hostname, atoi(portnum)); ssl = SSL_new(ctx); /* create new SSL connection state */ SSL_set_fd(ssl, server); /* attach the socket descriptor */ if (SSL_connect(ssl) == FAIL) /* perform the connection */ { printf("Connection failed\n"); ERR_print_errors_fp(stderr); } else { while (running) { // Send message by 10 seconds std::string msg = "Hello from client #" + std::to_string(++counter); int w = SSL_write(ssl, msg.data(), (int)msg.size()); if (w <= 0) { int err = SSL_get_error(ssl, w); if (err == SSL_ERROR_ZERO_RETURN) { printf("[Client] Server closed connection.\n"); } else { printf("[Client] SSL_write error.\n"); ERR_print_errors_fp(stderr); } break; } printf("[Client] Sent: \"%s\"\n", msg.c_str()); char buf[2048]; int n = SSL_read(ssl, buf, (int)sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; printf("[Client] Received: \"%s\"\n", buf); if (std::string(buf).find("[SERVER] RENEGO-OK") != std::string::npos) { printf("[Client] Server renegotiation confirmed.\n"); } } else { int err = SSL_get_error(ssl, n); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { printf("[Client] WANT READ or WANT WRITE.\n"); } else if (err == SSL_ERROR_ZERO_RETURN) { printf("[Client] Server closed connection.\n"); break; } else { printf("[Client] SSL_read error.\n"); ERR_print_errors_fp(stderr); break; } } std::this_thread::sleep_for(std::chrono::seconds(10)); } SSL_free(ssl); /* release connection state */ } closesocket(server); /* close socket */ SSL_CTX_free(ctx); /* release context */ return 0; }Server
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <errno.h> #include <string.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <WS2tcpip.h> #include <string> #include <iostream> #include <filesystem> #include <thread> namespace fs = std::filesystem; #pragma comment (lib, "ws2_32.lib") #define FAIL -1 int OpenListener(int port) { int sd; struct sockaddr_in addr; WSAData data; WORD ver = MAKEWORD(2, 2); int wsResult = WSAStartup(ver, &data); if (wsResult != 0) { printf("winsock error"); return 0; } sd = (int)socket(PF_INET, SOCK_STREAM, 0); ZeroMemory(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { perror("can't bind port"); abort(); } if (listen(sd, 10) != 0) { perror("Can't configure listening port"); abort(); } listen(sd, SOMAXCONN); return sd; } SSL_CTX* InitServerCTX(void) { SSL_CTX* ctx; OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */ SSL_load_error_strings(); /* load all error messages */ const SSL_METHOD* method = TLS_server_method(); /* Create new server-method instance */ ctx = SSL_CTX_new(method); /* create new context from method */ if (ctx == NULL) { ERR_print_errors_fp(stderr); abort(); } // TLS1.2 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION); #ifdef SSL_OP_NO_RENEGOTIATION SSL_CTX_clear_options(ctx, SSL_OP_NO_RENEGOTIATION); #endif const unsigned char sid_ctx[] = "MyServerSessionContext"; if (!SSL_CTX_set_session_id_context(ctx, sid_ctx, sizeof(sid_ctx) - 1)) { ERR_print_errors_fp(stderr); abort(); } return ctx; } void LoadCertificates(SSL_CTX* ctx, const char* CertFile, const char* KeyFile, const char* CAFile) { SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); SSL_CTX_set_verify_depth(ctx, 4); if (SSL_CTX_load_verify_locations(ctx, CAFile, NULL) != 1) { const char* err = ERR_reason_error_string(ERR_get_error()); printf("%s\n", err); } /* set the local certificate from CertFile */ if (SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0) { const char* err = ERR_reason_error_string(ERR_get_error()); printf("%s\n", err); // ERR_print_errors_fp(stderr); abort(); } /* set the private key from KeyFile (may be the same as CertFile) */ if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0) { const char* err = ERR_reason_error_string(ERR_get_error()); printf("%s\n", err); // ERR_print_errors_fp(stderr); abort(); } /* verify private key */ if (!SSL_CTX_check_private_key(ctx)) { const char* err = ERR_reason_error_string(ERR_get_error()); printf("%s\n", err); fprintf(stderr, "Private key does not match the public certificate\n"); abort(); } } void ShowCerts(SSL* ssl) { X509* cert; char* line; cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */ if (cert != NULL) { printf("Client certificates:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("Subject: %s\n", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("Issuer: %s\n", line); free(line); X509_free(cert); } else printf("No certificates.\n"); } void Servlet(SSL_CTX* ctx, int client) /* Serve the connection */ { char buf[1024]; char reply[1024]; int sd; const char* HTMLecho = "<html><body><pre>%s</pre></body></html>\n\n"; SSL* ssl = SSL_new(ctx); /* get new SSL state with context */ SSL_set_fd(ssl, client); /* set connection socket to SSL state */ if (SSL_accept(ssl) == FAIL) /* do SSL-protocol accept */ { ERR_print_errors_fp(stderr); } else { auto last_reneg = std::chrono::steady_clock::now(); bool running = true; ShowCerts(ssl); /* get any certificates */ while (running) { auto now = std::chrono::steady_clock::now(); if (now - last_reneg >= std::chrono::minutes(1)) { printf("[Server] Trigger renegotiation...\n"); if (SSL_renegotiate(ssl) <= 0) { printf("[Server] SSL_renegotiate failed\n"); ERR_print_errors_fp(stderr); } else { int r; while ((r = SSL_do_handshake(ssl)) != 1) { int ge = SSL_get_error(ssl, r); if (ge == SSL_ERROR_WANT_READ || ge == SSL_ERROR_WANT_WRITE) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); continue; } printf("[Server] SSL_do_handshake error during renegotiation\n"); ERR_print_errors_fp(stderr); break; } if (r == 1) { printf("[Server] Renegotiation completed successfully (TLS1.2)\n"); const char* notice = "[SERVER] RENEGO-OK\n"; SSL_write(ssl, notice, (int)strlen(notice)); last_reneg = now; } } } int n = SSL_read(ssl, buf, sizeof(buf)); /* get request */ if (n > 0) { buf[n] = 0; printf("Client msg: \"%s\"\n", buf); sprintf_s(reply, HTMLecho, buf); /* construct reply */ SSL_write(ssl, reply, (int)strlen(reply)); /* send reply */ } else { int err = SSL_get_error(ssl, n); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { // next roop } else if (err == SSL_ERROR_ZERO_RETURN) { printf("[Server] Peer closed connection.\n"); running = false; } else { printf("[Server] SSL_read error.\n"); ERR_print_errors_fp(stderr); running = false; } } } } sd = SSL_get_fd(ssl); /* get socket connection */ SSL_free(ssl); /* release SSL state */ closesocket(sd); /* close connection */ } int main() { SSL_CTX* ctx; int server; char portnum[] = "54000"; auto current_dir_path = fs::current_path(); auto pem_cert_file_path = current_dir_path / "Server_certificate.pem"; auto pem_pkey_file_path = current_dir_path / "Server_private_key.pem"; auto pem_ca_file_path = current_dir_path / "Client_CA_certificate.pem"; SSL_library_init(); ctx = InitServerCTX(); /* initialize SSL */ LoadCertificates(ctx, pem_cert_file_path.string().c_str(), pem_pkey_file_path.string().c_str(), pem_ca_file_path.string().c_str() ); /* load certs */ printf("Certificate loaded\n"); server = OpenListener(atoi(portnum)); /* create server socket */ printf("Listener opened\n"); auto last_reneg = std::chrono::steady_clock::now(); while (1) { struct sockaddr_in addr; socklen_t len = sizeof(addr); int client = (int)accept(server, (struct sockaddr*)&addr, &len); /* accept connection as usual */ printf("Accepted: Client %d\n", client); std::thread th(&Servlet, ctx, client); th.detach(); } closesocket(server); /* close server socket */ SSL_CTX_free(ctx); /* release context */ }Server Console
Certificate loaded Listener opened Accepted: Client 420 Client certificates: Subject: /CN=Client Issuer: /CN=Client Client msg: "Hello from client #1" Client msg: "Hello from client #2" Client msg: "Hello from client #3" Client msg: "Hello from client #4" Client msg: "Hello from client #5" Client msg: "Hello from client #6" Client msg: "Hello from client #7" [Server] Trigger renegotiation... [Server] Renegotiation completed successfully (TLS1.2) Client msg: "Hello from client #8" [Server] SSL_read error.Client Console
client certificate loaded [Client] Sent: "Hello from client #1" [Client] Received: "<html><body><pre>Hello from client #1</pre></body></html> " [Client] Sent: "Hello from client #2" [Client] Received: "<html><body><pre>Hello from client #2</pre></body></html> " [Client] Sent: "Hello from client #3" [Client] Received: "<html><body><pre>Hello from client #3</pre></body></html> " [Client] Sent: "Hello from client #4" [Client] Received: "<html><body><pre>Hello from client #4</pre></body></html> " [Client] Sent: "Hello from client #5" [Client] Received: "<html><body><pre>Hello from client #5</pre></body></html> " [Client] Sent: "Hello from client #6" [Client] Received: "<html><body><pre>Hello from client #6</pre></body></html> " [Client] Sent: "Hello from client #7" [Client] Received: "<html><body><pre>Hello from client #7</pre></body></html> " [Client] Sent: "Hello from client #8" [Client] Received: "[SERVER] RENEGO-OK " [Client] Server renegotiation confirmed. Client finishing renegotiation...