diff --git a/lib/pure/net.nim b/lib/pure/net.nim index bd022e3918..1d20fc1b63 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -544,18 +544,20 @@ proc fromSockAddr*(sa: Sockaddr_storage | SockAddr | Sockaddr_in | Sockaddr_in6, fromSockAddrAux(cast[ptr Sockaddr_storage](unsafeAddr sa), sl, address, port) when defineSsl: - CRYPTO_malloc_init() - doAssert SslLibraryInit() == 1 - SSL_load_error_strings() - ERR_load_BIO_strings() - OpenSSL_add_all_algorithms() + # OpenSSL >= 1.1.0 does not need explicit init. + when not useOpenssl3: + CRYPTO_malloc_init() + doAssert SslLibraryInit() == 1 + SSL_load_error_strings() + ERR_load_BIO_strings() + OpenSSL_add_all_algorithms() proc sslHandle*(self: Socket): SslPtr = ## Retrieve the ssl pointer of `socket`. ## Useful for interfacing with `openssl`. self.sslHandle - proc raiseSSLError*(s = "") = + proc raiseSSLError*(s = "") {.raises: [SslError].}= ## Raises a new SSL error. if s != "": raise newException(SslError, s) @@ -620,9 +622,8 @@ when defineSsl: caDir = "", caFile = ""): SslContext = ## Creates an SSL context. ## - ## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1 - ## are available with the addition of `protSSLv23` which allows for - ## compatibility with all of them. + ## Protocol version is currently ignored by default and TLS is used. + ## With `-d:openssl10`, only SSLv23 and TLSv1 may be used. ## ## There are three options for verify mode: ## `CVerifyNone`: certificates are not verified; @@ -649,16 +650,24 @@ when defineSsl: ## or using ECDSA: ## - `openssl ecparam -out mykey.pem -name secp256k1 -genkey` ## - `openssl req -new -key mykey.pem -x509 -nodes -days 365 -out mycert.pem` - var newCTX: SslCtx - case protVersion - of protSSLv23: - newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support. - of protSSLv2: - raiseSSLError("SSLv2 is no longer secure and has been deprecated, use protSSLv23") - of protSSLv3: - raiseSSLError("SSLv3 is no longer secure and has been deprecated, use protSSLv23") - of protTLSv1: - newCTX = SSL_CTX_new(TLSv1_method()) + var mtd: PSSL_METHOD + when defined(openssl10): + case protVersion + of protSSLv23: + mtd = SSLv23_method() + of protSSLv2: + raiseSSLError("SSLv2 is no longer secure and has been deprecated, use protSSLv23") + of protSSLv3: + raiseSSLError("SSLv3 is no longer secure and has been deprecated, use protSSLv23") + of protTLSv1: + mtd = TLSv1_method() + else: + mtd = TLS_method() + if mtd == nil: + raiseSSLError("Failed to create TLS context") + var newCTX = SSL_CTX_new(mtd) + if newCTX == nil: + raiseSSLError("Failed to create TLS context") if newCTX.SSL_CTX_set_cipher_list(cipherList) != 1: raiseSSLError() @@ -812,24 +821,28 @@ when defineSsl: if SSL_set_fd(socket.sslHandle, socket.fd) != 1: raiseSSLError() - proc checkCertName(socket: Socket, hostname: string) = + proc checkCertName(socket: Socket, hostname: string) {.raises: [SslError], tags:[RootEffect].} = ## Check if the certificate Subject Alternative Name (SAN) or Subject CommonName (CN) matches hostname. ## Wildcards match only in the left-most label. ## When name starts with a dot it will be matched by a certificate valid for any subdomain when not defined(nimDisableCertificateValidation) and not defined(windows): assert socket.isSsl - let certificate = socket.sslHandle.SSL_get_peer_certificate() - if certificate.isNil: - raiseSSLError("No SSL certificate found.") + try: + let certificate = socket.sslHandle.SSL_get_peer_certificate() + if certificate.isNil: + raiseSSLError("No SSL certificate found.") - const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = 0x1.cuint - # https://www.openssl.org/docs/man1.1.1/man3/X509_check_host.html - let match = certificate.X509_check_host(hostname.cstring, hostname.len.cint, - X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, nil) - # https://www.openssl.org/docs/man1.1.1/man3/SSL_get_peer_certificate.html - X509_free(certificate) - if match != 1: - raiseSSLError("SSL Certificate check failed.") + const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = 0x1.cuint + # https://www.openssl.org/docs/man1.1.1/man3/X509_check_host.html + let match = certificate.X509_check_host(hostname.cstring, hostname.len.cint, + X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, nil) + # https://www.openssl.org/docs/man1.1.1/man3/SSL_get_peer_certificate.html + X509_free(certificate) + if match != 1: + raiseSSLError("SSL Certificate check failed.") + + except LibraryError: + raiseSSLError("SSL import failed") proc wrapConnectedSocket*(ctx: SslContext, socket: Socket, handshake: SslHandshakeType, @@ -856,6 +869,7 @@ when defineSsl: let ret = SSL_connect(socket.sslHandle) socketError(socket, ret) when not defined(nimDisableCertificateValidation) and not defined(windows): + # FIXME: this should be skipped on CVerifyNone if hostname.len > 0 and not isIpAddress(hostname): socket.checkCertName(hostname) of handshakeAsServer: @@ -1311,7 +1325,7 @@ when defined(nimdoc) or (defined(posix) and not useNimNetLite): (sizeof(socketAddr.sun_family) + path.len).SockLen) != 0'i32: raiseOSError(osLastError()) -when defined(ssl): +when defineSsl: proc gotHandshake*(socket: Socket): bool = ## Determines whether a handshake has occurred between a client (`socket`) ## and the server that `socket` is connected to. @@ -2044,7 +2058,7 @@ proc dial*(address: string, port: Port, raise newException(IOError, "Couldn't resolve address: " & address) proc connect*(socket: Socket, address: string, - port = Port(0)) {.tags: [ReadIOEffect].} = + port = Port(0)) {.tags: [ReadIOEffect, RootEffect].} = ## Connects socket to `address`:`port`. `Address` can be an IP address or a ## host name. If `address` is a host name, this function will try each IP ## of that host name. `htons` is already performed on `port` so you must @@ -2119,7 +2133,7 @@ proc connectAsync(socket: Socket, name: string, port = Port(0), if not success: raiseOSError(lastError) proc connect*(socket: Socket, address: string, port = Port(0), - timeout: int) {.tags: [ReadIOEffect, WriteIOEffect].} = + timeout: int) {.tags: [ReadIOEffect, WriteIOEffect, RootEffect].} = ## Connects to server as specified by `address` on port specified by `port`. ## ## The `timeout` parameter specifies the time in milliseconds to allow for diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index c756fa938a..3bed34fab2 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -7,33 +7,41 @@ # distribution, for details about the copyright. # -## OpenSSL support +## OpenSSL wrapper. Supports OpenSSL >= 1.1.0 dynamically (as default) or statically linked +## using `--dynlibOverride:ssl`. ## -## When OpenSSL is dynamically linked, the wrapper provides partial forward and backward -## compatibility for OpenSSL versions above and below 1.1.0 -## -## OpenSSL can also be statically linked using `--dynlibOverride:ssl` for OpenSSL >= 1.1.0. -## If you want to statically link against OpenSSL 1.0.x, you now have to -## define the `openssl10` symbol via `-d:openssl10`. +## `-d:sslVersion=1.2.3` can be used to force an SSL version. +## This version must be included in the library name. +## `-d:useOpenssl3` may be set for OpenSSL 3 instead. +## +## There is also limited support for OpenSSL 1.0.x which may require `-d:openssl10`. ## ## Build and test examples: ## ## .. code-block:: +## ./bin/nim c -d:ssl -p:. -r tests/stdlib/tssl.nim +## ./bin/nim c -d:ssl --threads:on -p:. -r tests/stdlib/thttpclient_ssl.nim ## ./bin/nim c -d:ssl -p:. -r tests/untestable/tssl.nim ## ./bin/nim c -d:ssl -p:. --dynlibOverride:ssl --passl:-lcrypto --passl:-lssl -r tests/untestable/tssl.nim +## ./bin/nim r --putenv:NIM_TESTAMENT_REMOTE_NETWORKING:1 -d:ssl -p:testament/lib --threads:on tests/untestable/thttpclient_ssl_remotenetwork.nim + +# https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html +# +from strutils import startsWith when defined(nimHasStyleChecks): {.push styleChecks: off.} const useWinVersion = defined(windows) or defined(nimdoc) -# To force openSSL version use -d:sslVersion=1.0.0 +# To force openSSL version use -d:sslVersion=1.2.3 # See: #10281, #10230 # General issue: # Other dynamic libraries (like libpg) load different openSSL version then what nim loads. # Having two different openSSL loaded version causes a crash. # Use this compile time define to force the openSSL version that your other dynamic libraries want. const sslVersion {.strdefine.}: string = "" +const useOpenssl3* {.booldefine.} = sslVersion.startsWith('3') when sslVersion != "": when defined(macosx): const @@ -72,10 +80,11 @@ elif useWinVersion: from winlean import SocketHandle else: + # same list of versions but ordered differently? when defined(osx): - const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.47|.48|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + const versions = "(.3|.1.1|.38|.39|.41|.43|.44|.45|.46|.47|.48|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" else: - const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)" + const versions = "(.3|.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)" when defined(macosx): const @@ -266,7 +275,9 @@ proc TLSv1_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.} # and support SSLv3, TLSv1, TLSv1.1 and TLSv1.2 # SSLv23_method(), SSLv23_server_method(), SSLv23_client_method() are removed in 1.1.0 -when compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks): +const useStaticLink = compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks) + +when useStaticLink: # Static linking when defined(openssl10): @@ -277,6 +288,8 @@ when compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks): proc getOpenSSLVersion*(): culong = SSLeay() + + proc ERR_load_BIO_strings*() {.cdecl, dynlib: DLLUtilName, importc.} else: proc OPENSSL_init_ssl*(opts: uint64, settings: uint8): cint {.cdecl, dynlib: DLLSSLName, importc, discardable.} proc SSL_library_init*(): cint {.discardable.} = @@ -299,10 +312,13 @@ when compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks): # Static linking against OpenSSL < 1.1.0 is not supported discard + proc ERR_load_BIO_strings*() = + discard + when defined(libressl) or defined(openssl10): proc SSL_state(ssl: SslPtr): cint {.cdecl, dynlib: DLLSSLName, importc.} proc SSL_in_init*(ssl: SslPtr): cint {.inline.} = - SSl_state(ssl) and SSL_ST_INIT + SSL_state(ssl) and SSL_ST_INIT else: proc SSL_in_init*(ssl: SslPtr): cint {.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_set_ciphersuites*(ctx: SslCtx, str: cstring): cint {.cdecl, dynlib: DLLSSLName, importc.} @@ -312,9 +328,10 @@ when compileOption("dynlibOverride", "ssl") or defined(noOpenSSLHacks): proc SSLv23_client_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} proc SSLv2_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} proc SSLv3_method*(): PSSL_METHOD {.cdecl, dynlib: DLLSSLName, importc.} + proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl, dynlib: DLLUtilName, importc.} else: - # Here we're trying to stay compatible with openssl 1.0.* and 1.1.*. Some + # Here we're trying to stay compatible between openssl versions. Some # symbols are loaded dynamically and we don't use them if not found. proc thisModule(): LibHandle {.inline.} = var thisMod {.global.}: LibHandle @@ -322,9 +339,12 @@ else: result = thisMod - proc sslModule(): LibHandle {.inline.} = + proc sslModule(): LibHandle {.inline, raises: [LibraryError], tags:[RootEffect].} = var sslMod {.global.}: LibHandle - if sslMod.isNil: sslMod = loadLibPattern(DLLSSLName) + try: + if sslMod.isNil: sslMod = loadLibPattern(DLLSSLName) + except: + raise newException(LibraryError, "Could not load SSL using " & DLLSSLName) result = sslMod @@ -349,26 +369,30 @@ else: if result.isNil and alternativeName.len > 0: result = symAddr(thisDynlib, alternativeName) - proc sslSymNullable(name: string, alternativeName = ""): pointer = + proc sslSymNullable(name: string, alternativeName = ""): pointer {.raises: [LibraryError], tags:[RootEffect].} = sslModule().symNullable(name, alternativeName) - proc sslSymThrows(name: string, alternativeName = ""): pointer = + proc sslSymThrows(name: string, alternativeName = ""): pointer {.raises: [LibraryError].} = result = sslSymNullable(name, alternativeName) if result.isNil: raiseInvalidLibrary(name) proc utilSymNullable(name: string, alternativeName = ""): pointer = utilModule().symNullable(name, alternativeName) - proc loadPSSLMethod(method1, method2: string): PSSL_METHOD = + proc loadPSSLMethod(method1, method2: string): PSSL_METHOD {.raises: [LibraryError], tags:[RootEffect].} = ## Load from OpenSSL if available, otherwise ## let methodSym = sslSymNullable(method1, method2) if methodSym.isNil: raise newException(LibraryError, "Could not load " & method1 & " nor " & method2) - let method2Proc = cast[proc(): PSSL_METHOD {.cdecl, gcsafe.}](methodSym) + let method2Proc = cast[proc(): PSSL_METHOD {.cdecl, gcsafe, raises: [].}](methodSym) return method2Proc() + proc CRYPTO_set_mem_functions(a,b,c: pointer) = + let theProc = cast[proc(a,b,c: pointer) {.cdecl.}](utilModule().symNullable("CRYPTO_set_mem_functions")) + if not theProc.isNil: theProc(a, b, c) + proc SSL_library_init*(): cint {.discardable.} = ## Initialize SSL using OPENSSL_init_ssl for OpenSSL >= 1.1.0 otherwise ## SSL_library_init @@ -385,6 +409,10 @@ else: let theProc = cast[proc() {.cdecl.}](sslSymNullable("SSL_load_error_strings")) if not theProc.isNil: theProc() + proc ERR_load_BIO_strings*() = + let theProc = cast[proc() {.cdecl.}](utilModule().symNullable("ERR_load_BIO_strings")) + if not theProc.isNil: theProc() + proc SSLv23_client_method*(): PSSL_METHOD = loadPSSLMethod("SSLv23_client_method", "TLS_client_method") @@ -439,8 +467,6 @@ else: theProc = cast[typeof(theProc)](sslSymThrows("SSL_CTX_set_ciphersuites")) theProc(ctx, str) -proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLUtilName, importc.} - proc SSL_new*(context: SslCtx): SslPtr{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_free*(ssl: SslPtr){.cdecl, dynlib: DLLSSLName, importc.} proc SSL_get_SSL_CTX*(ssl: SslPtr): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} @@ -544,10 +570,10 @@ proc i2d_X509*(cert: PX509): string = if length.int <= 0: raise newException(Exception, "X.509 certificate encoding failed") -when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL): - proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl, - dynlib: DLLUtilName, importc.} +const + useNimsAlloc = not defined(nimNoAllocForSSL) and not defined(gcDestructors) +when not useWinVersion and not defined(macosx) and not defined(android) and useNimsAlloc: proc allocWrapper(size: int): pointer {.cdecl.} = allocShared(size) proc reallocWrapper(p: pointer; newSize: int): pointer {.cdecl.} = if p == nil: @@ -557,9 +583,11 @@ when not useWinVersion and not defined(macosx) and not defined(android) and not proc deallocWrapper(p: pointer) {.cdecl.} = if p != nil: deallocShared(p) -proc CRYPTO_malloc_init*() = - when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL): + proc CRYPTO_malloc_init*() = CRYPTO_set_mem_functions(allocWrapper, reallocWrapper, deallocWrapper) +else: + proc CRYPTO_malloc_init*() = + discard proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cint, larg: clong, parg: pointer): clong{. cdecl, dynlib: DLLSSLName, importc.} @@ -802,8 +830,21 @@ when defined(nimHasStyleChecks): # On old openSSL version some of these symbols are not available when not defined(nimDisableCertificateValidation) and not defined(windows): - proc SSL_get_peer_certificate*(ssl: SslCtx): PX509{.cdecl, dynlib: DLLSSLName, - importc.} + # SSL_get_peer_certificate removed in 3.0 + # SSL_get1_peer_certificate added in 3.0 + when useOpenssl3: + proc SSL_get1_peer_certificate*(ssl: SslCtx): PX509 {.cdecl, dynlib: DLLSSLName, importc.} + proc SSL_get_peer_certificate*(ssl: SslCtx): PX509 = + SSL_get1_peer_certificate(ssl) + elif useStaticLink: + proc SSL_get_peer_certificate*(ssl: SslCtx): PX509 {.cdecl, dynlib: DLLSSLName, importc.} + else: + proc SSL_get_peer_certificate*(ssl: SslCtx): PX509 = + let methodSym = sslSymNullable("SSL_get_peer_certificate", "SSL_get1_peer_certificate") + if methodSym.isNil: + raise newException(LibraryError, "Could not load SSL_get_peer_certificate or SSL_get1_peer_certificate") + let method2Proc = cast[proc(ssl: SslCtx): PX509 {.cdecl, gcsafe, raises: [].}](methodSym) + return method2Proc(ssl) proc X509_get_subject_name*(a: PX509): PX509_NAME{.cdecl, dynlib: DLLSSLName, importc.}