Skip to content

Commit ae05a49

Browse files
committed
JSSE: extract peek logic to internal function. Adds flag to allow
skipping TLS record peek if continuing with the same TLS record. Update bufferUnderflow guards. move/add TLS record header constants to WolfSSL check header bytes for plausible record header before peeking for bufferUnderflow
1 parent 162cf78 commit ae05a49

3 files changed

Lines changed: 112 additions & 37 deletions

File tree

native/com_wolfssl_WolfSSL.h

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/java/com/wolfssl/WolfSSL.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,23 @@ public enum TLS_VERSION {
349349
/** Maximum SSL record size (16KB) as defined by the protocol. */
350350
public static final int MAX_RECORD_SIZE = 16384;
351351

352+
/** TLS record header is: type(1) + version(2) + length(2) */
353+
public static final int TLS_RECORD_HEADER_LEN = 5;
354+
/** TLS record header length high byte offset */
355+
public static final int TLS_RECORD_LEN_HI_OFF = 3;
356+
/** TLS record header length lower byte offset */
357+
public static final int TLS_RECORD_LEN_LO_OFF = 4;
358+
/** TLS record header protocol version offset */
359+
public static final int TLS_RECORD_VERS_OFF = 1;
360+
/** TLS record header protocol version major */
361+
public static final int TLS_RECORD_VERS_MAJOR = 0x03;
362+
/** TLS record header valid content type
363+
* (RFC 8446 B.1, RFC 6520), (change_cipher_spec) */
364+
public static final int TLS_RECORD_CONT_MIN = 20;
365+
/** TLS record header valid content type
366+
* (RFC 8446 B.1, RFC 6520), (heartbeat) */
367+
public static final int TLS_RECORD_CONT_MAX = 24;
368+
352369
/* ------------------ TLS extension specific ------------------------ */
353370
/** SNI Host name type, for UseSNI() */
354371
public static final int WOLFSSL_SNI_HOST_NAME = 0;

src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public class WolfSSLEngine extends SSLEngine {
110110

111111
/* session stored (WOLFSSL_SESSION), relevant on client side */
112112
private boolean sessionStored = false;
113+
private boolean contPartialRecord = false;
113114

114115
/* TLS 1.3 session ticket received (on client side) */
115116
private boolean sessionTicketReceived = false;
@@ -142,11 +143,6 @@ public class WolfSSLEngine extends SSLEngine {
142143
* and expanded only when a larger output window requires it. */
143144
private byte[] recvAppDataBuf = new byte[WolfSSL.MAX_RECORD_SIZE];
144145

145-
/* TLS record header is: type(1) + version(2) + length(2) */
146-
private static final int TLS_RECORD_HEADER_LEN = 5;
147-
private static final int TLS_RECORD_LEN_HI_OFF = 3;
148-
private static final int TLS_RECORD_LEN_LO_OFF = 4;
149-
150146
/* Default size of internalIOSendBuf, 16k to match TLS record size.
151147
* TODO - add upper bound on I/O send buf resize allocations. */
152148
private static final int INTERNAL_IOSEND_BUF_SZ = WolfSSL.MAX_RECORD_SIZE;
@@ -365,7 +361,7 @@ private List<SNIServerName> parseRequestedServerNamesFromNetData() {
365361

366362
synchronized (netDataLock) {
367363
if (this.netData == null ||
368-
this.netData.remaining() < TLS_RECORD_HEADER_LEN) {
364+
this.netData.remaining() < WolfSSL.TLS_RECORD_HEADER_LEN) {
369365
return null;
370366
}
371367
in = this.netData.asReadOnlyBuffer();
@@ -1289,6 +1285,56 @@ private byte[] getRecvAppDataBuf(int minSz) {
12891285
return this.recvAppDataBuf;
12901286
}
12911287

1288+
/**
1289+
* Peek at the record header and
1290+
* return BUFFER_UNDERFLOW before calling into
1291+
* JNI when the full record is not yet present.
1292+
* Without this, native wolfSSL consumes partial
1293+
* record bytes via the I/O callback, violating
1294+
* the JSSE contract that BUFFER_UNDERFLOW must
1295+
* report bytesConsumed() == 0.
1296+
*
1297+
* @param in input buffer.
1298+
* @param inRemaining bytes remaining in input buffer.
1299+
*
1300+
* @return true if buffer underflow detected, false otherwise.
1301+
*/
1302+
private boolean peekTlsRecordHeader(ByteBuffer in, int inRemaining) {
1303+
boolean bufferUnderflow = false;
1304+
/* DTLS still relies on the native WANT_READ path. */
1305+
if (inRemaining > 0 && (this.ssl.dtls() == 0)) {
1306+
int pos = in.position();
1307+
if (inRemaining < WolfSSL.TLS_RECORD_HEADER_LEN) {
1308+
/* Not enough for TLS record header */
1309+
bufferUnderflow = true;
1310+
}
1311+
else {
1312+
/* Check if header is plausible. Content type between
1313+
* change_cipher_spec and heartbeat and
1314+
* version major corresponds to TLS. */
1315+
if ((in.get(pos) & 0xFF) >= WolfSSL.TLS_RECORD_CONT_MIN &&
1316+
(in.get(pos) & 0xFF) <= WolfSSL.TLS_RECORD_CONT_MAX &&
1317+
(in.get(pos + WolfSSL.TLS_RECORD_VERS_OFF)
1318+
& 0xFF) == WolfSSL.TLS_RECORD_VERS_MAJOR) {
1319+
/* Peek at record length from header
1320+
* bytes 3-4 (big-endian). */
1321+
int recLen =
1322+
((in.get(pos + WolfSSL.TLS_RECORD_LEN_HI_OFF)
1323+
& 0xFF) << 8) |
1324+
(in.get(pos + WolfSSL.TLS_RECORD_LEN_LO_OFF)
1325+
& 0xFF);
1326+
/* Prevent overflow of inRemaining. Fall through if recLen
1327+
* exceeds max packet buffer size. */
1328+
if (recLen <= this.getSession().getPacketBufferSize() &&
1329+
inRemaining < WolfSSL.TLS_RECORD_HEADER_LEN + recLen) {
1330+
bufferUnderflow = true;
1331+
}
1332+
}
1333+
}
1334+
}
1335+
return bufferUnderflow;
1336+
}
1337+
12921338
@Override
12931339
public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer out)
12941340
throws SSLException {
@@ -1313,6 +1359,7 @@ public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer[] out,
13131359
long dtlsPrevDropCount = 0;
13141360
long dtlsCurrDropCount = 0;
13151361
int prevSessionTicketCount = 0;
1362+
boolean bufferUnderflow = false;
13161363
final int tmpRet;
13171364

13181365
/* Set initial status for SSLEngineResult return */
@@ -1463,38 +1510,25 @@ else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP &&
14631510
if (this.handshakeFinished == false) {
14641511
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
14651512
() -> "starting or continuing handshake");
1466-
ret = DoHandshake(false);
1513+
/* Client only: TLS 1.3 ssl_accept() I/O callback
1514+
* patterns conflict with the pre-peek mid-handshake.
1515+
* Post-handshake server unwrap always applies peek. */
1516+
if (!this.contPartialRecord) {
1517+
bufferUnderflow =
1518+
this.getUseClientMode() &&
1519+
peekTlsRecordHeader(in, inRemaining);
1520+
}
1521+
if (bufferUnderflow) {
1522+
status = SSLEngineResult.Status.BUFFER_UNDERFLOW;
1523+
}
1524+
else {
1525+
ret = DoHandshake(false);
1526+
}
14671527
}
14681528
else {
1469-
/* TLS-only: peek at the record header and
1470-
* return BUFFER_UNDERFLOW before calling into
1471-
* JNI when the full record is not yet present.
1472-
* Without this, native wolfSSL consumes partial
1473-
* record bytes via the I/O callback, violating
1474-
* the JSSE contract that BUFFER_UNDERFLOW must
1475-
* report bytesConsumed() == 0. DTLS still
1476-
* relies on the native WANT_READ path. */
1477-
boolean bufferUnderflow = false;
1478-
if (inRemaining > 0 && (this.ssl.dtls() == 0)) {
1479-
synchronized (netDataLock) {
1480-
int pos = in.position();
1481-
if (inRemaining < TLS_RECORD_HEADER_LEN) {
1482-
/* Not enough for TLS record header */
1483-
bufferUnderflow = true;
1484-
} else {
1485-
/* Peek at record length from header
1486-
* bytes 3-4 (big-endian) */
1487-
int recLen =
1488-
((in.get(pos + TLS_RECORD_LEN_HI_OFF)
1489-
& 0xFF) << 8) |
1490-
(in.get(pos + TLS_RECORD_LEN_LO_OFF)
1491-
& 0xFF);
1492-
if (inRemaining <
1493-
TLS_RECORD_HEADER_LEN + recLen) {
1494-
bufferUnderflow = true;
1495-
}
1496-
}
1497-
}
1529+
if (!this.contPartialRecord) {
1530+
bufferUnderflow =
1531+
peekTlsRecordHeader(in, inRemaining);
14981532
}
14991533

15001534
/* Serve stashed data from previous
@@ -1697,7 +1731,8 @@ else if (ret < 0 &&
16971731
}
16981732
}
16991733
else if (!this.handshakeFinished && (ret == 0) &&
1700-
(err == 0 || err == WolfSSL.SSL_ERROR_ZERO_RETURN)) {
1734+
(err == 0 || err == WolfSSL.SSL_ERROR_ZERO_RETURN)
1735+
&& !bufferUnderflow) {
17011736

17021737
boolean gotCloseNotify = false;
17031738
synchronized (ioLock) {
@@ -2650,6 +2685,11 @@ protected synchronized int internalRecvCb(ByteBuffer toRead, int sz) {
26502685
if (this.ssl.dtls() == 1 && this.handshakeFinished) {
26512686
this.nativeWantsToRead = 1;
26522687
}
2688+
/* Buffer exhausted mid-record: skip the header peek on the
2689+
* next unwrap() to pass continuation bytes to DoHandshake. */
2690+
if (this.netData != null && this.netData.remaining() == 0) {
2691+
this.contPartialRecord = true;
2692+
}
26532693
return WolfSSL.WOLFSSL_CBIO_ERR_WANT_READ;
26542694
}
26552695

@@ -2676,6 +2716,10 @@ protected synchronized int internalRecvCb(ByteBuffer toRead, int sz) {
26762716
toRead.position(toReadPos);
26772717
}
26782718

2719+
/* Reset to false when all requested bytes were delivered;
2720+
* set to true when the buffer was exhausted mid-record. */
2721+
this.contPartialRecord = (max < sz);
2722+
26792723
return max;
26802724
}
26812725
}

0 commit comments

Comments
 (0)