Skip to content

Commit 39fb751

Browse files
authored
Reject inverted protocol version range in SSLContext (#142)
OpenSSL validates each boundary individually but does not cross-check min against max. A caller could set min above max without raising, producing a context where every session fails its handshake. Both setters now compare the new value against the existing opposite boundary before calling SSL_CTX_ctrl, so the context is never left with an inverted range. SSLAutoVersion (0) bypasses the check, since a zero boundary means the library picks. Closes #129
1 parent 381d59a commit 39fb751

3 files changed

Lines changed: 129 additions & 8 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Fix SSLContext accepting an inverted protocol version range
2+
3+
`set_min_proto_version(TLS1u3Version())` followed by `set_max_proto_version(TLS1u2Version())` passed without raising. Every session from that context then failed its handshake with "no protocols available." Both setters now raise when the new value would leave the minimum above the maximum.

ssl/net/_test.pony

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ actor \nodoc\ Main is TestList
6060
test(_TestSSLContextALPNResolverRootedBySession)
6161
test(_TestSSLContextALPNResolverUnreferenced)
6262
test(_TestSSLContextALPNSetClientProtocolsAfterDispose)
63+
test(_TestSSLContextSetMinProtoVersionInvertedRange)
64+
test(_TestSSLContextSetMaxProtoVersionInvertedRange)
6365
test(_TestSSLContextSetMinProtoVersionAfterDispose)
6466
test(_TestSSLContextSetMaxProtoVersionAfterDispose)
6567
test(_TestSSLContextGetMinProtoVersionAfterDispose)
@@ -3406,6 +3408,106 @@ class \nodoc\ iso _TestSSLContextALPNSetClientProtocolsAfterDispose is UnitTest
34063408
ctx.alpn_set_client_protocols(["h2"]),
34073409
"alpn_set_client_protocols() on a disposed context should return false")
34083410

3411+
class \nodoc\ iso _TestSSLContextSetMinProtoVersionInvertedRange is UnitTest
3412+
"""
3413+
`set_min_proto_version` raises when the new minimum is above the current
3414+
maximum, and does not change the minimum.
3415+
3416+
An equal range is valid: it pins the context to one protocol version.
3417+
`SSLAutoVersion` bypasses the check, since a zero boundary means the library
3418+
picks.
3419+
"""
3420+
fun name(): String =>
3421+
"net/ssl/SSLContext.set_min_proto_version/inverted_range"
3422+
3423+
fun apply(h: TestHelper) =>
3424+
let ctx = SSLContext
3425+
3426+
try
3427+
ctx.set_max_proto_version(TLS1u2Version())?
3428+
else
3429+
h.fail("set_max_proto_version(TLS1u2Version) should not raise")
3430+
return
3431+
end
3432+
3433+
try
3434+
ctx.set_min_proto_version(TLS1u3Version())?
3435+
h.fail(
3436+
"set_min_proto_version(TLS1u3Version) should raise when max is "
3437+
+ "TLS 1.2")
3438+
end
3439+
3440+
h.assert_eq[ILong](
3441+
TLS1u2Version().ilong(),
3442+
ctx.get_min_proto_version(),
3443+
"min should still be TLS 1.2 after the rejected call")
3444+
3445+
try
3446+
ctx.set_min_proto_version(SSLAutoVersion())?
3447+
else
3448+
h.fail("set_min_proto_version(SSLAutoVersion) should not raise")
3449+
end
3450+
3451+
try
3452+
ctx.set_min_proto_version(TLS1u2Version())?
3453+
else
3454+
h.fail(
3455+
"set_min_proto_version(TLS1u2Version) should not raise when max is "
3456+
+ "TLS 1.2")
3457+
end
3458+
3459+
ctx.dispose()
3460+
3461+
class \nodoc\ iso _TestSSLContextSetMaxProtoVersionInvertedRange is UnitTest
3462+
"""
3463+
`set_max_proto_version` raises when the new maximum is below the current
3464+
minimum, and does not change the maximum.
3465+
3466+
An equal range is valid: it pins the context to one protocol version.
3467+
`SSLAutoVersion` bypasses the check, since a zero boundary means the library
3468+
picks.
3469+
"""
3470+
fun name(): String =>
3471+
"net/ssl/SSLContext.set_max_proto_version/inverted_range"
3472+
3473+
fun apply(h: TestHelper) =>
3474+
let ctx = SSLContext
3475+
3476+
try
3477+
ctx.set_min_proto_version(TLS1u3Version())?
3478+
else
3479+
h.fail("set_min_proto_version(TLS1u3Version) should not raise")
3480+
return
3481+
end
3482+
3483+
try
3484+
ctx.set_max_proto_version(TLS1u2Version())?
3485+
h.fail(
3486+
"set_max_proto_version(TLS1u2Version) should raise when min is "
3487+
+ "TLS 1.3")
3488+
end
3489+
3490+
h.assert_eq[ILong](
3491+
SSLAutoVersion().ilong(),
3492+
ctx.get_max_proto_version(),
3493+
"max should still be auto after the rejected call")
3494+
3495+
try
3496+
ctx.set_max_proto_version(TLS1u3Version())?
3497+
else
3498+
h.fail(
3499+
"set_max_proto_version(TLS1u3Version) should not raise when min is "
3500+
+ "TLS 1.3")
3501+
end
3502+
3503+
try
3504+
ctx.set_max_proto_version(SSLAutoVersion())?
3505+
else
3506+
h.fail("set_max_proto_version(SSLAutoVersion) should not raise")
3507+
end
3508+
3509+
ctx.dispose()
3510+
34093511
class \nodoc\ iso _TestSSLContextSetMinProtoVersionAfterDispose is UnitTest
34103512
"""
34113513
`set_min_proto_version` on a disposed context raises an error rather than

ssl/net/ssl_context.pony

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -332,19 +332,27 @@ class val SSLContext
332332

333333
fun ref set_min_proto_version(version: ULong) ? =>
334334
"""
335-
Set minimum protocol version. Set to SSLAutoVersion, 0,
336-
to automatically manage lowest version. Raises an error if the context has
337-
been disposed.
335+
Set minimum protocol version. Set to SSLAutoVersion, 0, to automatically
336+
manage lowest version.
337+
338+
Raises an error if the context has been disposed, if the version is above
339+
the current maximum, or if the SSL library rejects the version.
338340
339341
Supported versions: SSL3Version, TLS1Version, TLS1u1Version,
340342
TLS1u2Version, TLS1u3Version, DTLS1Version,
341343
DTLS1u2Version
342344
"""
343345
if _ctx.is_null() then error end
344346

347+
let new_min = version.ilong()
348+
let max_v = get_max_proto_version()
349+
if (new_min != 0) and (max_v != 0) and (new_min > max_v) then
350+
error
351+
end
352+
345353
let result =
346354
@SSL_CTX_ctrl(
347-
_ctx, _SSLCtrlSetMinProtoVersion(), version.ilong(), Pointer[None])
355+
_ctx, _SSLCtrlSetMinProtoVersion(), new_min, Pointer[None])
348356
if result == 0 then
349357
error
350358
end
@@ -365,19 +373,27 @@ class val SSLContext
365373

366374
fun ref set_max_proto_version(version: ULong) ? =>
367375
"""
368-
Set maximum protocol version. Set to SSLAutoVersion, 0,
369-
to automatically manage higest version. Raises an error if the context has
370-
been disposed.
376+
Set maximum protocol version. Set to SSLAutoVersion, 0, to automatically
377+
manage highest version.
378+
379+
Raises an error if the context has been disposed, if the version is below
380+
the current minimum, or if the SSL library rejects the version.
371381
372382
Supported versions: SSL3Version, TLS1Version, TLS1u1Version,
373383
TLS1u2Version, TLS1u3Version, DTLS1Version,
374384
DTLS1u2Version
375385
"""
376386
if _ctx.is_null() then error end
377387

388+
let new_max = version.ilong()
389+
let min_v = get_min_proto_version()
390+
if (new_max != 0) and (min_v != 0) and (new_max < min_v) then
391+
error
392+
end
393+
378394
let result =
379395
@SSL_CTX_ctrl(
380-
_ctx, _SSLCtrlSetMaxProtoVersion(), version.ilong(), Pointer[None])
396+
_ctx, _SSLCtrlSetMaxProtoVersion(), new_max, Pointer[None])
381397
if result == 0 then
382398
error
383399
end

0 commit comments

Comments
 (0)