diff --git a/examples/provider/DtlsClientEngine.java b/examples/provider/DtlsClientEngine.java new file mode 100644 index 0000000..4664701 --- /dev/null +++ b/examples/provider/DtlsClientEngine.java @@ -0,0 +1,704 @@ +/* DtlsClientEngine.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.security.KeyStore; +import java.security.Security; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManagerFactory; + +import com.wolfssl.WolfSSL; +import com.wolfssl.provider.jsse.WolfSSLProvider; + +/** + * Simple DTLS 1.3 example client using SSLEngine. + * This class demonstrates how to use SSLEngine with DTLS 1.3 + * to establish a secure connection to a server, send some data, + * receive a response, and then close the connection. + */ +public class DtlsClientEngine { + + private static final int SOCKET_TIMEOUT = 10000; /* 10 seconds */ + private static final int BUFFER_SIZE = 1024; + private static final int MAX_HANDSHAKE_LOOPS = 60; + private static final int MAX_PACKET_SIZE = 16384; + + private String host = "localhost"; + private int port = 11113; + + private String clientJKS = "../../examples/provider/client.jks"; + private String clientPswd = "wolfSSL test"; + private String caJKS = "../../examples/provider/ca-server.jks"; + private String caPswd = "wolfSSL test"; + + private SSLContext ctx; + private SSLEngine engine; + private DatagramSocket socket; + private InetSocketAddress serverAddress; + + /* Application and network buffers for data processing */ + private ByteBuffer appOutBuffer; + private ByteBuffer appInBuffer; + private ByteBuffer netOutBuffer; + private ByteBuffer netInBuffer; + + public DtlsClientEngine() { + /* Default constructor */ + } + + public DtlsClientEngine(String host, int port) { + this.host = host; + this.port = port; + } + + /** + * Run the DTLS client + */ + public void run() { + try { + /* Register wolfJSSE as first priority provider */ + Security.insertProviderAt(new WolfSSLProvider(), 1); + + /* Create socket and server address */ + socket = new DatagramSocket(); + socket.setSoTimeout(SOCKET_TIMEOUT); + + serverAddress = new InetSocketAddress(host, port); + System.out.println( + "Client socket created, connecting to " + host + ":" + port); + + /* Set up SSLContext and SSLEngine */ + setupSSL(); + + /* Initialize buffer sizes based on SSLSession */ + SSLSession session = engine.getSession(); + int appBufferSize = session.getApplicationBufferSize(); + int netBufferSize = session.getPacketBufferSize(); + + appOutBuffer = ByteBuffer.allocate(appBufferSize); + appInBuffer = ByteBuffer.allocate(appBufferSize); + netOutBuffer = ByteBuffer.allocate(netBufferSize); + netInBuffer = ByteBuffer.allocate(netBufferSize); + + /* Perform handshake */ + doHandshake(); + + /* Allow the engine state to stabilize after handshake */ + System.out.println( + "Pausing after handshake to allow connection to stabilize..."); + try { + Thread.sleep(1000); /* 1 second pause */ + } catch (InterruptedException e) { + /* Ignore interruption */ + } + + /* Send application data */ + String message = "Hello from DTLS 1.3 Client!"; + System.out.println("Sending application data: " + message); + sendData(message.getBytes()); + + /* Allow time for server to process and respond */ + System.out.println( + "Waiting for server response (allowing time for processing)..."); + try { + Thread.sleep(2000); /* 2 second pause */ + } catch (InterruptedException e) { + /* Ignore interruption */ + } + + /* Receive and process response data */ + System.out.println("Now attempting to receive server response..."); + try { + /* Receive the application data packet directly */ + byte[] data = new byte[MAX_PACKET_SIZE]; + DatagramPacket packet = new DatagramPacket(data, data.length); + + /* Set timeout for this operation */ + socket.setSoTimeout(10000); /* 10 seconds */ + System.out.println("Waiting for application data packet from server..."); + socket.receive(packet); + + int length = packet.getLength(); + System.out.println("Received packet of " + length + " bytes"); + + if (length > 0) { + /* Show the raw bytes for debugging */ + System.out.print("Raw bytes: "); + for (int i = 0; i < Math.min(length, 20); i++) { + System.out.printf("%02X ", packet.getData()[i] & 0xFF); + } + System.out.println(length > 20 ? "..." : ""); + + /* Process with SSLEngine */ + netInBuffer.clear(); + netInBuffer.put(packet.getData(), 0, length); + netInBuffer.flip(); + + appInBuffer.clear(); + SSLEngineResult result = + engine.unwrap(netInBuffer, appInBuffer); + System.out.println("Unwrap result: " + result.getStatus() + + ", consumed: " + result.bytesConsumed() + + ", produced: " + result.bytesProduced()); + + if (result.bytesProduced() > 0) { + /* Success! We got application data */ + appInBuffer.flip(); + byte[] responseData = new byte[appInBuffer.remaining()]; + appInBuffer.get(responseData); + String responseText = new String(responseData); + System.out.println( + "Successfully decrypted data: " + responseText); + } else { + System.out.println( + "No application data produced from this packet. " + + "Status: " + result.getStatus()); + + /* Try again in case we need another packet */ + System.out.println( + "Attempting to receive another packet..."); + try { + byte[] secondData = new byte[MAX_PACKET_SIZE]; + DatagramPacket secondPacket = + new DatagramPacket(secondData, + secondData.length); + socket.setSoTimeout(5000); /* 5 seconds */ + socket.receive(secondPacket); + + netInBuffer.clear(); + netInBuffer.put(secondPacket.getData(), 0, + secondPacket.getLength()); + netInBuffer.flip(); + + appInBuffer.clear(); + SSLEngineResult secondResult = + engine.unwrap(netInBuffer, appInBuffer); + System.out.println( + "Second unwrap result: " + + secondResult.getStatus() + + ", consumed: " + secondResult.bytesConsumed() + + ", produced: " + secondResult.bytesProduced()); + + if (secondResult.bytesProduced() > 0) { + appInBuffer.flip(); + byte[] secondResponseData = + new byte[appInBuffer.remaining()]; + appInBuffer.get(secondResponseData); + String secondResponseText = + new String(secondResponseData); + System.out.println( + "Successfully decrypted data: " + + secondResponseText); + } + } catch (SocketTimeoutException e) { + System.out.println( + "No additional packets received (timeout)"); + } + } + } else { + System.out.println("Empty packet received"); + } + + } catch (Exception e) { + System.err.println("Error receiving server response: " + + e.getMessage()); + e.printStackTrace(); + } + + /* Close connection */ + closeConnection(); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Set up the SSLContext and SSLEngine for DTLSv1.3 + */ + private void setupSSL() throws Exception { + /* Trust manager (certificates) */ + KeyStore cert = KeyStore.getInstance("JKS"); + cert.load(new FileInputStream(caJKS), caPswd.toCharArray()); + TrustManagerFactory tm = TrustManagerFactory.getInstance( + "SunX509", "wolfJSSE"); + tm.init(cert); + + /* Load private key */ + KeyStore pKey = KeyStore.getInstance("JKS"); + pKey.load(new FileInputStream(clientJKS), clientPswd.toCharArray()); + KeyManagerFactory km = KeyManagerFactory.getInstance( + "SunX509", "wolfJSSE"); + km.init(pKey, clientPswd.toCharArray()); + + /* Create SSLContext configured for DTLS 1.3 */ + ctx = SSLContext.getInstance("DTLSv1.3", "wolfJSSE"); + ctx.init(km.getKeyManagers(), tm.getTrustManagers(), null); + + /* Create SSLEngine */ + engine = ctx.createSSLEngine(host, port); + engine.setUseClientMode(true); + + /* Enable endpoint identification if available */ + try { + SSLParameters params = engine.getSSLParameters(); + engine.setSSLParameters(params); + } catch (Exception e) { + System.out.println( + "DEBUG: Exception setting SSL parameters: " + e.getMessage()); + } + + System.out.println("DTLS 1.3 Client Engine created"); + } + + /** + * Perform the DTLS handshake + */ + private void doHandshake() throws Exception { + System.out.println("Starting DTLS handshake..."); + + /* Set appropriate timeout for handshake */ + socket.setSoTimeout(SOCKET_TIMEOUT); + + engine.beginHandshake(); + HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + int loops = 0; + + while (handshakeStatus != HandshakeStatus.FINISHED && + handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) { + + if (loops++ > MAX_HANDSHAKE_LOOPS) { + throw new RuntimeException( + "Too many handshake loops, possible handshake failure"); + } + + switch (handshakeStatus) { + case NEED_WRAP: + handshakeStatus = handleWrap(); + break; + + case NEED_UNWRAP: + handshakeStatus = handleUnwrap(); + break; + + case NEED_TASK: + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + task.run(); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + + default: + throw new IllegalStateException( + "Invalid handshake status: " + handshakeStatus); + } + } + + System.out.println("DTLS handshake completed successfully"); + + /* Process session ticket */ + System.out.println("Processing post-handshake session tickets..."); + try { + /* Set a timeout for receiving the session ticket */ + socket.setSoTimeout(5000); + + /* Receive the ticket */ + byte[] data = new byte[MAX_PACKET_SIZE]; + DatagramPacket packet = new DatagramPacket(data, data.length); + socket.receive(packet); + + /* Process the packet - the session ticket */ + if (packet.getLength() > 0) { + System.out.println("Received post-handshake packet of " + + packet.getLength() + " bytes, processing..."); + + /* Process with SSLEngine */ + netInBuffer.clear(); + netInBuffer.put(packet.getData(), 0, packet.getLength()); + netInBuffer.flip(); + + appInBuffer.clear(); + SSLEngineResult result = engine.unwrap(netInBuffer, appInBuffer); + System.out.println("Processed post-handshake packet: " + + result.getStatus() + ", consumed: " + + result.bytesConsumed() + ", produced: " + + result.bytesProduced()); + } + } catch (SocketTimeoutException e) { + System.out.println( + "No post-handshake messages received (timeout)"); + } catch (Exception e) { + System.out.println( + "Error processing post-handshake messages: " + e.getMessage()); + } + + /* Add a small delay after handshake to ensure both sides are ready */ + System.out.println("Pausing briefly before sending data..."); + try { + Thread.sleep(200); /* 200ms pause */ + } catch (InterruptedException e) { + /* Ignore interruption */ + } + } + + /** + * Handle wrap operations during handshake + */ + private HandshakeStatus handleWrap() throws Exception { + netOutBuffer.clear(); + SSLEngineResult result = engine.wrap(appOutBuffer, netOutBuffer); + + switch (result.getStatus()) { + case OK: + sendPacket(netOutBuffer); + return result.getHandshakeStatus(); + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = engine.getSession().getPacketBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + netOutBuffer = newBuffer; + return engine.getHandshakeStatus(); + + default: + throw new SSLException( + "Unexpected wrap result: " + result.getStatus()); + } + } + + /** + * Handle unwrap operations during handshake + */ + private HandshakeStatus handleUnwrap() throws Exception { + if (netInBuffer.position() == 0) { + /* No data in the buffer, receive a packet */ + receivePacket(netInBuffer); + } + + netInBuffer.flip(); + SSLEngineResult result = engine.unwrap(netInBuffer, appInBuffer); + netInBuffer.compact(); + + switch (result.getStatus()) { + case OK: + return result.getHandshakeStatus(); + + case BUFFER_UNDERFLOW: + /* Need more data, receive another packet */ + receivePacket(netInBuffer); + return engine.getHandshakeStatus(); + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = engine.getSession().getApplicationBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + appInBuffer = newBuffer; + return engine.getHandshakeStatus(); + + default: + throw new SSLException( + "Unexpected unwrap result: " + result.getStatus()); + } + } + + /** + * Send application data to the server + */ + private void sendData(byte[] data) throws Exception { + appOutBuffer.clear(); + appOutBuffer.put(data); + appOutBuffer.flip(); + + while (appOutBuffer.hasRemaining()) { + netOutBuffer.clear(); + SSLEngineResult result = engine.wrap(appOutBuffer, netOutBuffer); + + switch (result.getStatus()) { + case OK: + sendPacket(netOutBuffer); + break; + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = engine.getSession().getPacketBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + netOutBuffer = newBuffer; + break; + + default: + throw new SSLException( + "Unexpected wrap result: " + result.getStatus()); + } + } + } + + /** + * Receive application data from the server + */ + private byte[] receiveData() throws Exception { + int attempts = 0; + int maxAttempts = 10; /* Increase max attempts */ + while (attempts++ < maxAttempts) { /* Try more times to get data */ + netInBuffer.clear(); + try { + /* Temporarily increase socket timeout for expected + * application data */ + int originalTimeout = socket.getSoTimeout(); + /* Longer timeout for app data - 20 seconds */ + socket.setSoTimeout(20000); + + receivePacket(netInBuffer); + + /* Restore original timeout */ + socket.setSoTimeout(originalTimeout); + + netInBuffer.flip(); + appInBuffer.clear(); + + System.out.println("DEBUG: Before unwrap - netInBuffer " + + "position: " + netInBuffer.position() + ", limit: " + + netInBuffer.limit()); + + /* Try again if we have data but unwrap consumes nothing */ + SSLEngineResult result = null; + try { + result = engine.unwrap(netInBuffer, appInBuffer); + System.out.println( + "DEBUG: Unwrap result: " + result.getStatus() + + ", bytesConsumed: " + result.bytesConsumed() + + ", bytesProduced: " + result.bytesProduced()); + + /* If nothing was consumed but we have data, + * try a different approach */ + if (result.bytesConsumed() == 0 && netInBuffer.hasRemaining()) { + System.out.println("DEBUG: Unwrap consumed 0 bytes, " + + "trying a second unwrap operation"); + + /* Try a second unwrap with the same data */ + try { + appInBuffer.clear(); + SSLEngineResult result2 = engine.unwrap(netInBuffer, appInBuffer); + System.out.println( + "DEBUG: Second unwrap result: " + + result2.getStatus() + + ", bytesConsumed: " + result2.bytesConsumed() + + ", bytesProduced: " + result2.bytesProduced()); + + /* If second attempt produced data, + * use this result */ + if (result2.bytesProduced() > 0) { + result = result2; + } else { + /* Otherwise try from scratch with new packet */ + netInBuffer.clear(); + continue; + } + } catch (Exception e) { + System.out.println("DEBUG: Exception during " + + "second unwrap: " + e.getMessage()); + /* Continue with a new packet */ + netInBuffer.clear(); + continue; + } + } + } catch (Exception e) { + System.out.println("DEBUG: Exception during unwrap: " + + e.getMessage()); + /* Continue to try again */ + continue; + } + + switch (result.getStatus()) { + case OK: + appInBuffer.flip(); + int remaining = appInBuffer.remaining(); + System.out.println("DEBUG: Received " + remaining + + " bytes of application data"); + + /* If we got application data, return it */ + if (remaining > 0) { + byte[] data = new byte[remaining]; + appInBuffer.get(data); + return data; + } else { + /* Otherwise, keep trying to get more packets */ + System.out.println("DEBUG: Received 0 " + + "application bytes, trying again..."); + continue; + } + + case BUFFER_UNDERFLOW: + /* Need more data */ + continue; + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = + engine.getSession().getApplicationBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + appInBuffer = newBuffer; + break; + + default: + throw new SSLException( + "Unexpected unwrap result: " + result.getStatus()); + } + } catch (SocketTimeoutException e) { + System.out.println("Socket timeout, retrying... (attempt " + + attempts + " of " + maxAttempts + ")"); + } + } + + throw new IOException("Failed to receive data after multiple attempts"); + } + + /** + * Close the SSL connection properly + */ + private void closeConnection() throws Exception { + System.out.println("Closing connection..."); + + engine.closeOutbound(); + + while (!engine.isOutboundDone()) { + /* Get the close message */ + netOutBuffer.clear(); + SSLEngineResult result = engine.wrap(appOutBuffer, netOutBuffer); + + /* Check result status */ + if (result.getStatus() != SSLEngineResult.Status.OK) { + throw new SSLException( + "Error closing outbound: " + result.getStatus()); + } + + /* Send the close message to the server */ + sendPacket(netOutBuffer); + } + + System.out.println("Connection closed"); + socket.close(); + } + + /** + * Send a packet to the server + */ + private void sendPacket(ByteBuffer buffer) throws IOException { + buffer.flip(); + int len = buffer.remaining(); + byte[] data = new byte[len]; + buffer.get(data); + + DatagramPacket packet = new DatagramPacket(data, len, serverAddress); + socket.send(packet); + System.out.println("DEBUG: Sent packet with " + len + " bytes to " + + serverAddress.getAddress() + ":" + serverAddress.getPort()); + } + + /** + * Receive a packet from the server + */ + private void receivePacket(ByteBuffer buffer) throws IOException { + byte[] data = new byte[MAX_PACKET_SIZE]; + DatagramPacket packet = new DatagramPacket(data, data.length); + + try { + socket.receive(packet); + + int packetLength = packet.getLength(); + System.out.println("DEBUG: Received packet with " + packetLength + + " bytes from " + packet.getAddress() + ":" + packet.getPort()); + + if (packetLength > 0) { + /* Ensure the packet is from our server */ + if (packet.getAddress().equals(serverAddress.getAddress()) && + packet.getPort() == serverAddress.getPort()) { + buffer.put(data, 0, packetLength); + } else { + System.out.println("WARNING: Received packet from " + + "unexpected source: " + packet.getAddress() + ":" + + packet.getPort() + " (expected: " + + serverAddress.getAddress() + ":" + + serverAddress.getPort() + ")"); + } + } else { + System.out.println("WARNING: Received empty packet!"); + } + } catch (SocketTimeoutException e) { + System.out.println("DEBUG: Socket timeout in receivePacket()"); + throw e; /* Rethrow for proper handling */ + } + } + + /** + * Main method, parse cmd line args and run new instance of DtlsClientEngine + */ + public static void main(String[] args) { + String host = "localhost"; + int port = 11113; + + /* Parse command line arguments */ + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + + if (arg.equals("-h") && i + 1 < args.length) { + host = args[++i]; + } else if (arg.equals("-p") && i + 1 < args.length) { + port = Integer.parseInt(args[++i]); + } else if (arg.equals("-?")) { + printUsage(); + return; + } + } + + DtlsClientEngine client = new DtlsClientEngine(host, port); + client.run(); + } + + /** + * Print usage information + */ + private static void printUsage() { + System.out.println("DTLS 1.3 Client Engine Example"); + System.out.println(" -h host Host to connect to"); + System.out.println(" (default: localhost)"); + System.out.println(" -p port Port to connect to (default: 11113)"); + System.out.println(" -? Print this help menu"); + } +} + diff --git a/examples/provider/DtlsClientEngine.sh b/examples/provider/DtlsClientEngine.sh new file mode 100755 index 0000000..e22c8a1 --- /dev/null +++ b/examples/provider/DtlsClientEngine.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd ./examples/build +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../lib/:/usr/local/lib +java -classpath ../../lib/wolfssl.jar:../../lib/wolfssl-jsse.jar:./ -Dsun.boot.library.path=../../lib/ DtlsClientEngine "$@" diff --git a/examples/provider/DtlsServerEngine.java b/examples/provider/DtlsServerEngine.java new file mode 100644 index 0000000..7f356a2 --- /dev/null +++ b/examples/provider/DtlsServerEngine.java @@ -0,0 +1,472 @@ +/* DtlsServerEngine.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.security.KeyStore; +import java.security.Security; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManagerFactory; + +import com.wolfssl.WolfSSL; +import com.wolfssl.provider.jsse.WolfSSLProvider; + +/** + * Simple DTLS 1.3 example server using SSLEngine. + * This class demonstrates how to use SSLEngine with DTLS 1.3 + * to accept a connection from a client, receive data, echo it back, + * and then close the connection. + */ +public class DtlsServerEngine { + + private static final int MAX_HANDSHAKE_LOOPS = 60; + private static final int MAX_PACKET_SIZE = 16384; + private static final int SOCKET_TIMEOUT = 5000; /* 5 seconds */ + + private int port = 11113; + + private String serverJKS = "../../examples/provider/server.jks"; + private String serverPswd = "wolfSSL test"; + private String caJKS = "../../examples/provider/ca-client.jks"; + private String caPswd = "wolfSSL test"; + + private SSLContext ctx; + private SSLEngine engine; + private DatagramSocket socket; + private InetSocketAddress clientAddress; + + /* Application and network buffers for data processing */ + private ByteBuffer appOutBuffer; + private ByteBuffer appInBuffer; + private ByteBuffer netOutBuffer; + private ByteBuffer netInBuffer; + + public DtlsServerEngine() { + /* Default constructor */ + } + + public DtlsServerEngine(int port) { + this.port = port; + } + + /** + * Run the DTLS server + */ + public void run() { + try { + /* Register wolfJSSE as first priority provider */ + Security.insertProviderAt(new WolfSSLProvider(), 1); + + /* Create socket without timeout for initial connection */ + socket = new DatagramSocket(port); + System.out.println("DTLS 1.3 Server listening on port " + port); + + /* Set up SSL context and engine */ + setupSSL(); + + /* Initialize buffer sizes based on SSLSession */ + SSLSession session = engine.getSession(); + int appBufferSize = session.getApplicationBufferSize(); + int netBufferSize = session.getPacketBufferSize(); + + appOutBuffer = ByteBuffer.allocate(appBufferSize); + appInBuffer = ByteBuffer.allocate(appBufferSize); + netOutBuffer = ByteBuffer.allocate(netBufferSize); + netInBuffer = ByteBuffer.allocate(netBufferSize); + + /* Wait for client connection and perform handshake */ + waitForClientAndHandshake(); + + /* Receive and process data from client */ + byte[] clientData = receiveData(); + String clientMessage = new String(clientData); + System.out.println("Received from client: " + clientMessage); + + /* Echo data back to client */ + System.out.println("Echoing message back to client"); + sendData(clientData); + + /* Close connection */ + closeConnection(); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Set up the SSLContext and SSLEngine for DTLSv1.3 + */ + private void setupSSL() throws Exception { + /* Server KeyStore */ + KeyStore serverKeystore = KeyStore.getInstance("JKS"); + serverKeystore.load(new FileInputStream(serverJKS), + serverPswd.toCharArray()); + + /* Server TrustStore */ + KeyStore serverTruststore = KeyStore.getInstance("JKS"); + serverTruststore.load(new FileInputStream(caJKS), + caPswd.toCharArray()); + + /* Server TrustManagerFactory, init with TrustStore */ + TrustManagerFactory serverTm = TrustManagerFactory.getInstance( + "SunX509", "wolfJSSE"); + serverTm.init(serverTruststore); + + /* Server KeyManagerFactory, init with KeyStore */ + KeyManagerFactory serverKm = KeyManagerFactory.getInstance( + "SunX509", "wolfJSSE"); + serverKm.init(serverKeystore, serverPswd.toCharArray()); + + /* Create SSLContext configured for DTLS 1.3 */ + ctx = SSLContext.getInstance("DTLSv1.3", "wolfJSSE"); + ctx.init(serverKm.getKeyManagers(), serverTm.getTrustManagers(), null); + + /* Create server-side SSLEngine with client auth enabled */ + engine = ctx.createSSLEngine(); + engine.setUseClientMode(false); + engine.setNeedClientAuth(true); + + /* Set SSL parameters if needed */ + try { + SSLParameters params = engine.getSSLParameters(); + engine.setSSLParameters(params); + } catch (Exception e) { + System.out.println( + "DEBUG: Exception setting SSL parameters: " + e.getMessage()); + } + + System.out.println("DTLS 1.3 Server Engine created"); + } + + /** + * Wait for a client connection and perform the DTLS handshake + */ + private void waitForClientAndHandshake() throws Exception { + System.out.println("Waiting for client connection..."); + + /* Wait for initial message from client */ + byte[] buffer = new byte[MAX_PACKET_SIZE]; + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + socket.receive(packet); + + /* Store client address for future communication */ + clientAddress = new InetSocketAddress( + packet.getAddress(), packet.getPort()); + System.out.println("Client connected from " + clientAddress); + + /* Put received data into the network buffer */ + netInBuffer.put(packet.getData(), 0, packet.getLength()); + + /* Begin handshake */ + engine.beginHandshake(); + HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + int loops = 0; + + while (handshakeStatus != HandshakeStatus.FINISHED && + handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) { + + if (loops++ > MAX_HANDSHAKE_LOOPS) { + throw new RuntimeException( + "Too many handshake loops, possible handshake failure"); + } + + switch (handshakeStatus) { + case NEED_UNWRAP: + handshakeStatus = handleUnwrap(); + break; + + case NEED_WRAP: + handshakeStatus = handleWrap(); + break; + + case NEED_TASK: + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + task.run(); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + + default: + throw new IllegalStateException( + "Invalid handshake status: " + handshakeStatus); + } + } + + System.out.println("DTLS handshake completed successfully"); + + /* Add a small delay after handshake to ensure both sides are ready */ + System.out.println( + "Pausing briefly before processing application data..."); + try { + Thread.sleep(200); /* 200ms pause */ + } catch (InterruptedException e) { + /* Ignore interruption */ + } + } + + /** + * Handle wrap operations during handshake + */ + private HandshakeStatus handleWrap() throws Exception { + netOutBuffer.clear(); + SSLEngineResult result = engine.wrap(appOutBuffer, netOutBuffer); + + switch (result.getStatus()) { + case OK: + sendPacket(netOutBuffer); + return result.getHandshakeStatus(); + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = engine.getSession().getPacketBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + netOutBuffer = newBuffer; + return engine.getHandshakeStatus(); + + default: + throw new SSLException( + "Unexpected wrap result: " + result.getStatus()); + } + } + + /** + * Handle unwrap operations during handshake + */ + private HandshakeStatus handleUnwrap() throws Exception { + if (netInBuffer.position() == 0) { + /* No data in the buffer, receive a packet */ + receivePacket(netInBuffer); + } + + netInBuffer.flip(); + SSLEngineResult result = engine.unwrap(netInBuffer, appInBuffer); + netInBuffer.compact(); + + switch (result.getStatus()) { + case OK: + return result.getHandshakeStatus(); + + case BUFFER_UNDERFLOW: + /* Need more data, receive another packet */ + receivePacket(netInBuffer); + return engine.getHandshakeStatus(); + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = engine.getSession().getApplicationBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + appInBuffer = newBuffer; + return engine.getHandshakeStatus(); + + default: + throw new SSLException( + "Unexpected unwrap result: " + result.getStatus()); + } + } + + /** + * Send application data to the client + */ + private void sendData(byte[] data) throws Exception { + appOutBuffer.clear(); + appOutBuffer.put(data); + appOutBuffer.flip(); + + while (appOutBuffer.hasRemaining()) { + netOutBuffer.clear(); + SSLEngineResult result = engine.wrap(appOutBuffer, netOutBuffer); + + switch (result.getStatus()) { + case OK: + sendPacket(netOutBuffer); + break; + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = engine.getSession().getPacketBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + netOutBuffer = newBuffer; + break; + + default: + throw new SSLException( + "Unexpected wrap result: " + result.getStatus()); + } + } + } + + /** + * Receive application data from the client + */ + private byte[] receiveData() throws Exception { + int attempts = 0; + while (attempts++ < 3) { /* Try a few times to get data */ + netInBuffer.clear(); + try { + receivePacket(netInBuffer); + + netInBuffer.flip(); + appInBuffer.clear(); + + SSLEngineResult result = + engine.unwrap(netInBuffer, appInBuffer); + + switch (result.getStatus()) { + case OK: + appInBuffer.flip(); + byte[] data = new byte[appInBuffer.remaining()]; + appInBuffer.get(data); + return data; + + case BUFFER_UNDERFLOW: + /* Need more data */ + continue; + + case BUFFER_OVERFLOW: + /* Increase the buffer size and try again */ + int newSize = + engine.getSession().getApplicationBufferSize(); + ByteBuffer newBuffer = ByteBuffer.allocate(newSize); + appInBuffer = newBuffer; + break; + + default: + throw new SSLException( + "Unexpected unwrap result: " + result.getStatus()); + } + } catch (SocketTimeoutException e) { + System.out.println("Socket timeout, retrying..."); + } + } + + throw new IOException("Failed to receive data after multiple attempts"); + } + + /** + * Close the SSL connection properly + */ + private void closeConnection() throws Exception { + System.out.println("Closing connection..."); + + engine.closeOutbound(); + + while (!engine.isOutboundDone()) { + /* Get the close message */ + netOutBuffer.clear(); + SSLEngineResult result = engine.wrap(appOutBuffer, netOutBuffer); + + /* Check result status */ + if (result.getStatus() != SSLEngineResult.Status.OK) { + throw new SSLException( + "Error closing outbound: " + result.getStatus()); + } + + /* Send the close message to the client */ + sendPacket(netOutBuffer); + } + + System.out.println("Connection closed"); + socket.close(); + } + + /** + * Send a packet to the client + */ + private void sendPacket(ByteBuffer buffer) throws IOException { + buffer.flip(); + int len = buffer.remaining(); + byte[] data = new byte[len]; + buffer.get(data); + + DatagramPacket packet = new DatagramPacket(data, len, clientAddress); + socket.send(packet); + System.out.println("DEBUG: Sent packet with " + len + " bytes"); + } + + /** + * Receive a packet from the client + */ + private void receivePacket(ByteBuffer buffer) throws IOException { + /* Set socket timeout for data operations after connection */ + socket.setSoTimeout(SOCKET_TIMEOUT); + + byte[] data = new byte[MAX_PACKET_SIZE]; + DatagramPacket packet = new DatagramPacket(data, data.length); + socket.receive(packet); + System.out.println( + "DEBUG: Received packet with " + packet.getLength() + " bytes"); + + /* Update client address in case it changed */ + clientAddress = new InetSocketAddress( + packet.getAddress(), packet.getPort()); + + buffer.put(data, 0, packet.getLength()); + } + + /** + * Main method, parse cmd line args and run new instance of DtlsServerEngine + */ + public static void main(String[] args) { + int port = 11113; + + /* Parse command line arguments */ + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + + if (arg.equals("-p") && i + 1 < args.length) { + port = Integer.parseInt(args[++i]); + } else if (arg.equals("-?")) { + printUsage(); + return; + } + } + + DtlsServerEngine server = new DtlsServerEngine(port); + server.run(); + } + + /** + * Print usage information + */ + private static void printUsage() { + System.out.println("DTLS 1.3 Server Engine Example"); + System.out.println("Usage: DtlsServerEngine [-p port]"); + System.out.println(" -p port Port to listen on (default: 11113)"); + System.out.println(" -? Print this help menu"); + } +} + diff --git a/examples/provider/DtlsServerEngine.sh b/examples/provider/DtlsServerEngine.sh new file mode 100755 index 0000000..2742ee9 --- /dev/null +++ b/examples/provider/DtlsServerEngine.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd ./examples/build +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../lib/:/usr/local/lib +java -classpath ../../lib/wolfssl.jar:../../lib/wolfssl-jsse.jar:./ -Dsun.boot.library.path=../../lib/ DtlsServerEngine "$@" diff --git a/examples/provider/README.md b/examples/provider/README.md index a220d19..065e9f2 100644 --- a/examples/provider/README.md +++ b/examples/provider/README.md @@ -145,6 +145,28 @@ Example usage: $ ./examples/provider/ThreadedSSLSocketClientServer.sh ``` +## DtlsClientEngine.java and DtlsServerEngine.java + +Example client/server applications that demonstrate how to use SSLEngine with DTLS 1.3. + +**DtlsServerEngine.java** - Example DTLS 1.3 server using SSLEngine +**DtlsClientEngine.java** - Example DTLS 1.3 client using SSLEngine + +These examples show how to implement DTLS 1.3 with SSLEngine for datagram-based +secure communication. Unlike the TCP-based examples, these use DatagramSocket for +UDP transport and handle the complexities of DTLS, including session tickets +and handshake state management. + +Run the examples with the provided bash scripts: + +``` +$ ./examples/provider/DtlsServerEngine.sh +$ ./examples/provider/DtlsClientEngine.sh +``` + +The client connects to the server, sends a message, and receives the echoed response. +Both examples support various command-line options that can be viewed with the -? flag. + ## Support Please contact the wolfSSL support team at support@wolfssl.com with any