Skip to content

Commit 77933ce

Browse files
committed
net, openssl: support setting the minimum supported SSL/TLS version
This commit brings minimum supported TLS version setting to Nim, which replaces the exact version system used by `net.newContext`. For more information consult the changelog entry associated with this change.
1 parent 32083c7 commit 77933ce

File tree

3 files changed

+130
-21
lines changed

3 files changed

+130
-21
lines changed

changelog.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,20 @@
108108
users from the use of weak and insecure ciphers while still provides
109109
adequate compatiblity with the majority of the Internet.
110110

111+
- `net.newContext` drops support for setting the _exact_ version of SSL/TLS.
112+
This has been replaced by the ability to set the _minimum_ supported version.
113+
114+
The **default** minimum supported version is now `protTLS`. Details about
115+
this constant can be found below.
116+
117+
- `net.SslProtVersion` now specify the minimum version to use with `newContext`.
118+
`protSSLv2`, `protSSLv3` and `protSSLv23` has now been deprecated in favor of
119+
explicit TLS versions.
120+
121+
A new constant `protTLS` is provided, which tracks the latest recommended TLS
122+
version with reasonable security and compatibility with the Internet. This
123+
constant is the replacement for the old `protSSLv23`.
124+
111125
## Language changes
112126
- In the newruntime it is now allowed to assign discriminator field without restrictions as long as case object doesn't have custom destructor. Discriminator value doesn't have to be a constant either. If you have custom destructor for case object and you do want to freely assign discriminator fields, it is recommended to refactor object into 2 objects like this:
113127

lib/pure/net.nim

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const defineSsl = defined(ssl) or defined(nimdoc)
7777

7878
when defineSsl:
7979
import openssl
80+
import dynlib
8081

8182
# Note: The enumerations are mapped to Window's constants.
8283

@@ -90,7 +91,14 @@ when defineSsl:
9091
CVerifyNone, CVerifyPeer, CVerifyPeerUseEnvVars
9192

9293
SslProtVersion* = enum
93-
protSSLv2, protSSLv3, protTLSv1, protSSLv23
94+
## Minimum supported SSL/TLS versions
95+
protSSLv2 {.deprecated.}, ## **Deprecated:** SSLv2
96+
protSSLv3 {.deprecated.}, ## **Deprecated:** SSLv3
97+
protSSLv23 {.deprecated.}, ## **Deprecated:** Any SSL/TLS version, same as protTLSv1
98+
protTLSv1, ## TLSv1
99+
protTLSv1_1, ## TLSv1.1
100+
protTLSv1_2, ## TLSv1.2
101+
protTLSv1_3 ## TLSv1.3
94102

95103
SslContext* = ref object
96104
context*: SslCtx
@@ -111,6 +119,10 @@ when defineSsl:
111119
serverGetPskFunc: SslServerGetPskFunc
112120
clientGetPskFunc: SslClientGetPskFunc
113121

122+
const protTLS* = protTLSv1_2
123+
## The recommended minimum TLS version with adequate security and
124+
## compatibility with the Internet.
125+
114126
else:
115127
type
116128
SslContext* = void # TODO: Workaround #4797.
@@ -473,7 +485,7 @@ when defineSsl:
473485
ERR_load_BIO_strings()
474486
OpenSSL_add_all_algorithms()
475487

476-
proc raiseSSLError*(s = "") =
488+
proc raiseSSLError*(s = "") {.noreturn.} =
477489
## Raises a new SSL error.
478490
if s != "":
479491
raise newException(SslError, s)
@@ -533,14 +545,13 @@ when defineSsl:
533545
if SSL_CTX_check_private_key(ctx) != 1:
534546
raiseSSLError("Verification of private key file failed.")
535547

536-
proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
548+
proc newContext*(minProtVersion = protTLS, verifyMode = CVerifyPeer,
537549
certFile = "", keyFile = "", cipherList = CiphersIntermediate,
538550
caDir = "", caFile = ""): SSLContext =
539-
## Creates an SSL context.
551+
## Creates an SSL/TLS context.
540552
##
541-
## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1
542-
## are available with the addition of ``protSSLv23`` which allows for
543-
## compatibility with all of them.
553+
## The minimum supported SSL/TLS version can be specified with
554+
## `minProtVersion`. See `SslProtVersion <#SslProtVersion>`_.
544555
##
545556
## There are three options for verify mode:
546557
## ``CVerifyNone``: certificates are not verified;
@@ -567,16 +578,48 @@ when defineSsl:
567578
## or using ECDSA:
568579
## - ``openssl ecparam -out mykey.pem -name secp256k1 -genkey``
569580
## - ``openssl req -new -key mykey.pem -x509 -nodes -days 365 -out mycert.pem``
570-
var newCTX: SslCtx
571-
case protVersion
572-
of protSSLv23:
573-
newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support.
574-
of protSSLv2:
575-
raiseSSLError("SSLv2 is no longer secure and has been deprecated, use protSSLv23")
576-
of protSSLv3:
577-
raiseSSLError("SSLv3 is no longer secure and has been deprecated, use protSSLv23")
578-
of protTLSv1:
579-
newCTX = SSL_CTX_new(TLSv1_method())
581+
if minProtVersion in {protSSLv2, protSSLv3}:
582+
raiseSSLError("SSLv2/3 is no longer secure and has been deprecated, use protTLS")
583+
584+
var newCTX = SSL_CTX_new(SSLv23_method())
585+
let minVersion: cint =
586+
case minProtVersion
587+
of protTLSv1, protSSLv23:
588+
TLS1_VERSION
589+
of protTLSv1_1:
590+
TLS1_1_VERSION
591+
of protTLSv1_2:
592+
TLS1_2_VERSION
593+
of protTLSv1_3:
594+
TLS1_3_VERSION
595+
of protSSLv2, protSSLv3:
596+
doAssert false, "unreachable!"
597+
high(cint) # XXX: required since doAssert doesn't satisfy the noreturn check.
598+
599+
let useFallback: bool =
600+
when not defined(openssl10) or defined(libressl):
601+
try:
602+
if getOpenSSLVersion() < 0x010100000:
603+
true
604+
elif newCTX.SSL_CTX_set_min_proto_version(minVersion) != 1:
605+
raiseSSLError()
606+
else:
607+
false
608+
except LibraryError:
609+
# We are dealing with a super old LibreSSL
610+
true
611+
else:
612+
true
613+
614+
if useFallback:
615+
var flags: clong = SSL_OP_NO_SSLv2 or SSL_OP_NO_SSLv3
616+
if minProtVersion > protTLSv1:
617+
flags = flags or SSL_OP_NO_TLSv1
618+
if minProtVersion > protTLSv1_1:
619+
flags = flags or SSL_OP_NO_TLSv1_1
620+
if minProtVersion > protTLSv1_2:
621+
flags = flags or SSL_OP_NO_TLSv1_2
622+
discard SSL_CTX_set_options(newCTX, flags)
580623

581624
if newCTX.SSL_CTX_set_cipher_list(cipherList) != 1:
582625
raiseSSLError()

lib/wrappers/openssl.nim

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,15 @@ const
175175
SSL_CTRL_GET_SESS_CACHE_SIZE* = 43
176176
SSL_CTRL_SET_SESS_CACHE_MODE* = 44
177177
SSL_CTRL_GET_SESS_CACHE_MODE* = 45
178+
SSL_CTRL_CLEAR_OPTIONS* = 47
178179
SSL_CTRL_GET_MAX_CERT_LIST* = 50
179180
SSL_CTRL_SET_MAX_CERT_LIST* = 51 #* Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success
180181
# * when just a single record has been written): *
181182
SSL_CTRL_SET_TLSEXT_SERVERNAME_CB = 53
182183
SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG = 54
183184
SSL_CTRL_SET_TLSEXT_HOSTNAME = 55
184185
SSL_CTRL_SET_ECDH_AUTO* = 94
186+
SSL_CTRL_SET_MIN_PROTO_VERSION* = 123
185187
TLSEXT_NAMETYPE_host_name* = 0
186188
SSL_TLSEXT_ERR_OK* = 0
187189
SSL_TLSEXT_ERR_ALERT_WARNING* = 1
@@ -198,8 +200,17 @@ const
198200
SSL_OP_NO_SSLv2* = 0x01000000
199201
SSL_OP_NO_SSLv3* = 0x02000000
200202
SSL_OP_NO_TLSv1* = 0x04000000
201-
SSL_OP_NO_TLSv1_1* = 0x08000000
203+
SSL_OP_NO_TLSv1_1* = 0x10000000
204+
SSL_OP_NO_TLSv1_2* = 0x08000000
205+
SSL_OP_NO_SSL_MASK* = SSL_OP_NO_SSLv2 or SSL_OP_NO_SSLv3 or SSL_OP_NO_TLSv1 or
206+
SSL_OP_NO_TLSv1_1 or SSL_OP_NO_TLSv1_2
202207
SSL_OP_ALL* = 0x000FFFFF
208+
SSL2_VERSION* = 0x0002
209+
SSL3_VERSION* = 0x0300
210+
TLS1_VERSION* = 0x0301
211+
TLS1_1_VERSION* = 0x0302
212+
TLS1_2_VERSION* = 0x0303
213+
TLS1_3_VERSION* = 0x0304
203214
SSL_VERIFY_NONE* = 0x00000000
204215
SSL_VERIFY_PEER* = 0x00000001
205216
SSL_ST_CONNECT* = 0x1000
@@ -257,6 +268,9 @@ proc TLSv1_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.}
257268
# and support SSLv3, TLSv1, TLSv1.1 and TLSv1.2
258269
# SSLv23_method(), SSLv23_server_method(), SSLv23_client_method() are removed in 1.1.0
259270

271+
proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cint, larg: clong, parg: pointer): clong{.
272+
cdecl, dynlib: DLLSSLName, importc.}
273+
260274
when compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks):
261275
# Static linking
262276

@@ -294,9 +308,23 @@ when compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks):
294308
proc SSL_state(ssl: SslPtr): cint {.cdecl, dynlib: DLLSSLName, importc.}
295309
proc SSL_in_init*(ssl: SslPtr): cint {.inline.} =
296310
SSl_state(ssl) and SSL_ST_INIT
311+
312+
proc SSL_CTX_set_options*(ctx: SslCtx, op: clong): clong {.inline.} =
313+
SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, nil)
314+
315+
proc SSL_CTX_clear_options*(ctx: SslCtx, op: clong): clong {.inline.} =
316+
SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, nil)
317+
318+
when defined(libressl):
319+
proc SSL_CTX_set_min_proto_version*(ctx: SslCtx, version: cint): cint {.cdecl, dynlib: DLLSSLName, importc.}
297320
else:
298321
proc SSL_in_init*(ssl: SslPtr): cint {.cdecl, dynlib: DLLSSLName, importc.}
299322
proc SSL_CTX_set_ciphersuites*(ctx: SslCtx, str: cstring): cint {.cdecl, dynlib: DLLSSLName, importc.}
323+
proc SSL_CTX_set_options*(ctx: SslCtx, op: clong): clong {.cdecl, dynlib: DLLSSLName, importc.}
324+
proc SSL_CTX_clear_options*(ctx: SslCtx, op: clong): clong {.cdecl, dynlib: DLLSSLName, importc.}
325+
326+
proc SSL_CTX_set_min_proto_version*(ctx: SslCtx, version: cint): cint =
327+
cint SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, clong version, nil)
300328

301329
template OpenSSL_add_all_algorithms*() = discard
302330

@@ -402,13 +430,40 @@ else:
402430
let theProc = cast[proc() {.cdecl.}](sslSymNullable("OPENSSL_add_all_algorithms_conf"))
403431
if not theProc.isNil: theProc()
404432

433+
proc SSL_CTX_set_options*(ctx: SslCtx, op: clong): clong =
434+
let theProc {.global.} = cast[proc(ctx: SslCtx, op: clong): clong {.cdecl, gcsafe.}](sslSymNullable("SSL_CTX_set_options"))
435+
if not theProc.isNil:
436+
theProc(ctx, op)
437+
else:
438+
SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, nil)
439+
440+
proc SSL_CTX_clear_options*(ctx: SslCtx, op: clong): clong =
441+
let theProc {.global.} = cast[proc(ctx: SslCtx, op: clong): clong {.cdecl, gcsafe.}](sslSymNullable("SSL_CTX_clear_options"))
442+
if not theProc.isNil:
443+
theProc(ctx, op)
444+
else:
445+
SSL_CTX_ctrl(ctx, SSL_CTRL_CLEAR_OPTIONS, op, nil)
446+
405447
proc getOpenSSLVersion*(): culong =
406448
## Return OpenSSL version as unsigned long or 0 if not available
407449
let theProc = cast[proc(): culong {.cdecl, gcsafe.}](utilSymNullable("OpenSSL_version_num", "SSLeay"))
408450
result =
409451
if theProc.isNil: 0.culong
410452
else: theProc()
411453

454+
proc SSL_CTX_set_min_proto_version*(ctx: SslCtx, version: cint): cint =
455+
## Set the minimum supported protocol version.
456+
const MainProc = "SSL_CTX_set_min_proto_version"
457+
let theProc {.global.} = cast[proc(ctx: SslCtx, version: cint): cint {.cdecl, gcsafe.}](sslSymNullable(MainProc))
458+
if not theProc.isNil:
459+
theProc(ctx, version)
460+
elif getOpenSSLVersion() == 0x020000000:
461+
# For LibreSSL this is provided as a function, throw if it couldn't be
462+
# found.
463+
raiseInvalidLibrary(MainProc)
464+
else:
465+
cint SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, clong version, nil)
466+
412467
proc SSL_in_init*(ssl: SslPtr): cint =
413468
# A compatibility wrapper for `SSL_in_init()` for OpenSSL 1.0, 1.1 and LibreSSL
414469
const MainProc = "SSL_in_init"
@@ -551,9 +606,6 @@ proc CRYPTO_malloc_init*() =
551606
when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL):
552607
CRYPTO_set_mem_functions(allocWrapper, reallocWrapper, deallocWrapper)
553608

554-
proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cint, larg: clong, parg: pointer): clong{.
555-
cdecl, dynlib: DLLSSLName, importc.}
556-
557609
proc SSL_CTX_callback_ctrl(ctx: SslCtx, typ: cint, fp: PFunction): int{.
558610
cdecl, dynlib: DLLSSLName, importc.}
559611

0 commit comments

Comments
 (0)