Skip to content

Commit 23072bc

Browse files
committed
update regression test to better test when inRemaining == 0 and add on additional continuation bytes testing
add partial header unwrap test and oversized record test
1 parent ae05a49 commit 23072bc

2 files changed

Lines changed: 282 additions & 26 deletions

File tree

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

Lines changed: 1 addition & 0 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+
/* Skip record header peek in next unwrap, continue same TLS record */
113114
private boolean contPartialRecord = false;
114115

115116
/* TLS 1.3 session ticket received (on client side) */

src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java

Lines changed: 281 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,13 +3047,13 @@ public void testBufferUnderflowPartialRecord()
30473047
}
30483048

30493049
@Test
3050-
public void testHandshakeUnwrapConsumedNotBufferUnderflow()
3050+
public void testHandshakeUnwrapIncrementalStatus()
30513051
throws NoSuchProviderException, NoSuchAlgorithmException,
30523052
KeyManagementException, KeyStoreException,
30533053
CertificateException, IOException,
30543054
UnrecoverableKeyException {
30553055

3056-
System.out.print("\tTest unwrap consumed != BUFFER_UNDERFLOW");
3056+
System.out.print("\tTest unwrap incremental status");
30573057

30583058
if (!enabledProtocols.contains("TLSv1.3")) {
30593059
System.out.println("\t... skipped");
@@ -3127,42 +3127,297 @@ public void testHandshakeUnwrapConsumedNotBufferUnderflow()
31273127
srvFlight.flip();
31283128

31293129
int total = srvFlight.remaining();
3130-
if (total < 2) {
3130+
/* Parse first TLS record header to find exact boundary:
3131+
* type[1] + version[2] + length[2] = 5 bytes. */
3132+
if (total < 5) {
31313133
error("\t... failed");
3132-
fail("server flight too small to split: " + total);
3134+
fail("server flight too small: " + total);
3135+
}
3136+
int firstRecPayload =
3137+
((srvFlight.get(srvFlight.position() + 3) & 0xFF) << 8)
3138+
| (srvFlight.get(srvFlight.position() + 4) & 0xFF);
3139+
int firstRecordLen = 5 + firstRecPayload;
3140+
if (total < firstRecordLen) {
3141+
error("\t... failed");
3142+
fail("first TLS record extends beyond server flight");
31333143
}
31343144

3135-
/* Feed the first half of the server flight to
3136-
* client.unwrap(). wolfSSL will consume these bytes through
3137-
* the I/O callback, exhaust the buffer, and return
3138-
* SSL_ERROR_WANT_READ. With the fix, BUFFER_UNDERFLOW must
3139-
* NOT be set because inRemaining > 0 (data was provided). */
3140-
int half = total / 2;
3141-
byte[] halfBytes = new byte[half];
3142-
srvFlight.get(halfBytes);
3143-
ByteBuffer firstHalf = ByteBuffer.wrap(halfBytes);
3145+
/* Feed exactly the first TLS record to client.unwrap().
3146+
* Peek confirms it is complete, DoHandshake() runs,
3147+
* wolfSSL consumes all bytes and returns WANT_READ. */
3148+
byte[] firstRecBytes = new byte[firstRecordLen];
3149+
srvFlight.get(firstRecBytes);
3150+
ByteBuffer firstRecord = ByteBuffer.wrap(firstRecBytes);
31443151

31453152
ByteBuffer cliAppBuf =
31463153
ByteBuffer.allocateDirect(appBufSize);
3147-
result = client.unwrap(firstHalf, cliAppBuf);
3154+
result = client.unwrap(firstRecord, cliAppBuf);
31483155

3149-
/* BUFFER_UNDERFLOW must not be returned when input was
3150-
* consumed - regression for inRemaining == 0 guard fix */
3151-
if (result.getStatus() ==
3152-
SSLEngineResult.Status.BUFFER_UNDERFLOW) {
3156+
/* wolfSSL processed first record but needs more data:
3157+
* status must be OK, not BUFFER_UNDERFLOW. */
3158+
if (result.getStatus() != SSLEngineResult.Status.OK) {
3159+
error("\t... failed");
3160+
fail("expected Status.OK, got: " +
3161+
result.getStatus());
3162+
}
3163+
3164+
/* Handshake needs more data to continue. */
3165+
if (result.getHandshakeStatus() !=
3166+
SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
31533167
error("\t... failed");
3154-
fail("unwrap() with consumed handshake data must not " +
3155-
"return BUFFER_UNDERFLOW (regression: inRemaining" +
3156-
" == 0 guard), bytesConsumed=" +
3168+
fail("expected NEED_UNWRAP, got: " +
3169+
result.getHandshakeStatus());
3170+
}
3171+
3172+
/* All bytes consumed via I/O callback. */
3173+
if (result.bytesConsumed() != firstRecordLen) {
3174+
error("\t... failed");
3175+
fail("expected " + firstRecordLen + " bytes " +
3176+
"consumed, got: " + result.bytesConsumed());
3177+
}
3178+
3179+
/* No application data produced during handshake. */
3180+
if (result.bytesProduced() != 0) {
3181+
error("\t... failed");
3182+
fail("expected 0 bytes produced, got: " +
3183+
result.bytesProduced());
3184+
}
3185+
3186+
/* Server flight must contain more than one record so we
3187+
* can feed continuation bytes and exercise contPartialRecord.
3188+
* TLSv1.3 server flight (ServerHello + EncryptedExtensions +
3189+
* Certificate + CertificateVerify + Finished) always spans
3190+
* multiple TLS records. */
3191+
if (srvFlight.remaining() == 0) {
3192+
error("\t... failed");
3193+
fail("server flight is a single record; cannot " +
3194+
"test contPartialRecord continuation path");
3195+
}
3196+
3197+
/* contPartialRecord=true from first unwrap skips
3198+
* peek; DoHandshake() gets raw continuation bytes. */
3199+
int contLen = Math.min(3, srvFlight.remaining());
3200+
byte[] contBytes = new byte[contLen];
3201+
srvFlight.get(contBytes);
3202+
ByteBuffer contBuf = ByteBuffer.wrap(contBytes);
3203+
ByteBuffer cliAppBuf2 =
3204+
ByteBuffer.allocateDirect(appBufSize);
3205+
result = client.unwrap(contBuf, cliAppBuf2);
3206+
3207+
/* wolfSSL consumed continuation bytes, needs more:
3208+
* must be Status.OK, not BUFFER_UNDERFLOW. */
3209+
if (result.getStatus() !=
3210+
SSLEngineResult.Status.OK) {
3211+
error("\t... failed");
3212+
fail("expected Status.OK for continuation " +
3213+
"bytes, got: " + result.getStatus());
3214+
}
3215+
3216+
/* Handshake still needs more data. */
3217+
if (result.getHandshakeStatus() !=
3218+
SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
3219+
error("\t... failed");
3220+
fail("expected NEED_UNWRAP for continuation " +
3221+
"bytes, got: " +
3222+
result.getHandshakeStatus());
3223+
}
3224+
3225+
/* All continuation bytes consumed. */
3226+
if (result.bytesConsumed() != contLen) {
3227+
error("\t... failed");
3228+
fail("expected " + contLen + " continuation " +
3229+
"bytes consumed, got: " +
31573230
result.bytesConsumed());
31583231
}
31593232

3160-
/* Input was non-empty so at least some bytes must have
3161-
* been consumed */
3162-
if (result.bytesConsumed() == 0) {
3233+
/* No application data produced. */
3234+
if (result.bytesProduced() != 0) {
3235+
error("\t... failed");
3236+
fail("expected 0 bytes produced, got: " +
3237+
result.bytesProduced());
3238+
}
3239+
3240+
pass("\t... passed");
3241+
}
3242+
3243+
@Test
3244+
public void testHandshakeUnwrapPartialHeader()
3245+
throws NoSuchProviderException, NoSuchAlgorithmException,
3246+
KeyManagementException, KeyStoreException,
3247+
CertificateException, IOException,
3248+
UnrecoverableKeyException {
3249+
3250+
/* Test that feeding <5 bytes to client.unwrap() during
3251+
* handshake returns BUFFER_UNDERFLOW with 0 bytes consumed.
3252+
* Exercises the inRemaining < TLS_RECORD_HEADER_LEN branch
3253+
* of peekTlsRecordHeader(). */
3254+
System.out.print("\tTest unwrap partial header underflow");
3255+
3256+
if (!enabledProtocols.contains("TLSv1.3")) {
3257+
System.out.println("\t... skipped");
3258+
return;
3259+
}
3260+
3261+
SSLContext ctx13 = tf.createSSLContext("TLSv1.3", engineProvider);
3262+
SSLEngine server = ctx13.createSSLEngine();
3263+
SSLEngine client = ctx13.createSSLEngine("localhost", 11111);
3264+
3265+
server.setUseClientMode(false);
3266+
server.setNeedClientAuth(false);
3267+
client.setUseClientMode(true);
3268+
3269+
server.beginHandshake();
3270+
client.beginHandshake();
3271+
3272+
int packetBufSize = client.getSession().getPacketBufferSize();
3273+
int appBufSize = client.getSession().getApplicationBufferSize();
3274+
3275+
/* Wrap ClientHello to put client into NEED_UNWRAP state */
3276+
ByteBuffer cliToSrv =
3277+
ByteBuffer.allocateDirect(packetBufSize);
3278+
ByteBuffer emptyApp = ByteBuffer.allocate(0);
3279+
SSLEngineResult result = client.wrap(emptyApp, cliToSrv);
3280+
if (result.getStatus() != SSLEngineResult.Status.OK) {
3281+
error("\t... failed");
3282+
fail("client wrap (ClientHello) failed: " +
3283+
result.getStatus());
3284+
}
3285+
3286+
if (client.getHandshakeStatus() !=
3287+
SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
3288+
error("\t... failed");
3289+
fail("expected NEED_UNWRAP after ClientHello, got: " +
3290+
client.getHandshakeStatus());
3291+
}
3292+
3293+
/* Feed 2 bytes: less than the 5-byte TLS record header.
3294+
* Peek must signal BUFFER_UNDERFLOW without calling into
3295+
* wolfSSL, so 0 bytes are consumed. */
3296+
ByteBuffer partialHeader =
3297+
ByteBuffer.wrap(new byte[] { 0x16, 0x03 });
3298+
ByteBuffer cliAppBuf =
3299+
ByteBuffer.allocateDirect(appBufSize);
3300+
result = client.unwrap(partialHeader, cliAppBuf);
3301+
3302+
if (result.getStatus() !=
3303+
SSLEngineResult.Status.BUFFER_UNDERFLOW) {
3304+
error("\t... failed");
3305+
fail("expected BUFFER_UNDERFLOW for partial header, " +
3306+
"got: " + result.getStatus());
3307+
}
3308+
3309+
if (result.bytesConsumed() != 0) {
3310+
error("\t... failed");
3311+
fail("BUFFER_UNDERFLOW must consume 0 bytes, " +
3312+
"consumed: " + result.bytesConsumed());
3313+
}
3314+
3315+
/* Engine must remain in NEED_UNWRAP after underflow. */
3316+
if (result.getHandshakeStatus() !=
3317+
SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
3318+
error("\t... failed");
3319+
fail("expected NEED_UNWRAP after underflow, got: " +
3320+
result.getHandshakeStatus());
3321+
}
3322+
3323+
pass("\t... passed");
3324+
}
3325+
3326+
@Test
3327+
public void testHandshakeUnwrapOversizedRecord()
3328+
throws NoSuchProviderException, NoSuchAlgorithmException,
3329+
KeyManagementException, KeyStoreException,
3330+
CertificateException, IOException,
3331+
UnrecoverableKeyException {
3332+
3333+
/* Test that a partial record whose header claims
3334+
* recLen > MAX_RECORD_SIZE (but <= packetBufferSize - 5)
3335+
* returns BUFFER_UNDERFLOW with 0 bytes consumed. Prior to
3336+
* fixing the MAX_RECORD_SIZE cap, this range bypassed the
3337+
* peek entirely and wolfSSL would consume partial bytes. */
3338+
System.out.print("\tTest unwrap oversized record underflow");
3339+
3340+
if (!enabledProtocols.contains("TLSv1.3")) {
3341+
System.out.println("\t... skipped");
3342+
return;
3343+
}
3344+
3345+
SSLContext ctx13 = tf.createSSLContext("TLSv1.3", engineProvider);
3346+
SSLEngine server = ctx13.createSSLEngine();
3347+
SSLEngine client = ctx13.createSSLEngine("localhost", 11111);
3348+
3349+
server.setUseClientMode(false);
3350+
server.setNeedClientAuth(false);
3351+
client.setUseClientMode(true);
3352+
3353+
server.beginHandshake();
3354+
client.beginHandshake();
3355+
3356+
int packetBufSize = client.getSession().getPacketBufferSize();
3357+
int appBufSize = client.getSession().getApplicationBufferSize();
3358+
3359+
/* Wrap ClientHello to put client into NEED_UNWRAP state */
3360+
ByteBuffer cliToSrv =
3361+
ByteBuffer.allocateDirect(packetBufSize);
3362+
ByteBuffer emptyApp = ByteBuffer.allocate(0);
3363+
SSLEngineResult result = client.wrap(emptyApp, cliToSrv);
3364+
if (result.getStatus() != SSLEngineResult.Status.OK) {
3365+
error("\t... failed");
3366+
fail("client wrap (ClientHello) failed: " +
3367+
result.getStatus());
3368+
}
3369+
3370+
if (client.getHandshakeStatus() !=
3371+
SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
3372+
error("\t... failed");
3373+
fail("expected NEED_UNWRAP after ClientHello, got: " +
3374+
client.getHandshakeStatus());
3375+
}
3376+
3377+
/* recLen = MAX_RECORD_SIZE + 1 must fit within
3378+
* packetBufferSize (header not counted). Skip if not,
3379+
* which would indicate an unusual build configuration. */
3380+
int oversizedRecLen = WolfSSL.MAX_RECORD_SIZE + 1;
3381+
if (oversizedRecLen > packetBufSize - 5) {
3382+
System.out.println("\t... skipped");
3383+
return;
3384+
}
3385+
3386+
/* Craft a 5-byte TLS record header: content type
3387+
* handshake(0x16), version TLS 1.2 record layer(0x0303),
3388+
* length = MAX_RECORD_SIZE + 1. Valid content-type and
3389+
* version bytes ensure the plausibility guard in
3390+
* peekTlsRecordHeader() does not filter the header before
3391+
* the length check. */
3392+
byte recLenHi = (byte) ((oversizedRecLen >> 8) & 0xFF);
3393+
byte recLenLo = (byte) (oversizedRecLen & 0xFF);
3394+
ByteBuffer fakeHeader = ByteBuffer.wrap(new byte[] {
3395+
0x16, 0x03, 0x03, recLenHi, recLenLo
3396+
});
3397+
3398+
ByteBuffer cliAppBuf =
3399+
ByteBuffer.allocateDirect(appBufSize);
3400+
result = client.unwrap(fakeHeader, cliAppBuf);
3401+
3402+
if (result.getStatus() !=
3403+
SSLEngineResult.Status.BUFFER_UNDERFLOW) {
3404+
error("\t... failed");
3405+
fail("expected BUFFER_UNDERFLOW for oversized partial " +
3406+
"record, got: " + result.getStatus());
3407+
}
3408+
3409+
if (result.bytesConsumed() != 0) {
3410+
error("\t... failed");
3411+
fail("BUFFER_UNDERFLOW must consume 0 bytes, " +
3412+
"consumed: " + result.bytesConsumed());
3413+
}
3414+
3415+
/* Engine must remain in NEED_UNWRAP after underflow. */
3416+
if (result.getHandshakeStatus() !=
3417+
SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
31633418
error("\t... failed");
3164-
fail("unwrap() consumed 0 bytes from a non-empty " +
3165-
"handshake buffer");
3419+
fail("expected NEED_UNWRAP after underflow, got: " +
3420+
result.getHandshakeStatus());
31663421
}
31673422

31683423
pass("\t... passed");

0 commit comments

Comments
 (0)