summary refs log tree commit diff stats
path: root/tests
diff options
context:
space:
mode:
authorFederico Ceratto <federico.ceratto@gmail.com>2020-03-20 16:11:39 +0000
committerGitHub <noreply@github.com>2020-03-20 17:11:39 +0100
commit5b854442448af96d57135ba7328b0c21f1f80f40 (patch)
treeb4e0e0cf66b15fb9f040e4ee1a11f0ffd3b68ee2 /tests
parent1d665adecde3b3bf16e64068e83c0b3cb0171856 (diff)
downloadNim-5b854442448af96d57135ba7328b0c21f1f80f40.tar.gz
SSL certificate verify GitHub action (#13697)
* Implement SSL/TLS certificate checking #782

* SSL: Add nimDisableCertificateValidation

Remove NIM_SSL_CERT_VALIDATION env var
tests/untestable/thttpclient_ssl.nim ran successfully on Linux with libssl 1.1.1d

* SSL: update integ test to skip flapping tests

* Revert .travis.yml change

* nimDisableCertificateValidation disable imports

Prevent loading symbols that are not defined on older SSL libs

* SSL: disable verification in net.nim

..when nimDisableCertificateValidation is set

* Update changelog

* Fix peername type

* Add define check for windows

* Disable test on windows

* Add exprimental GitHub action CI for SSL

* Test nimDisableCertificateValidation
Diffstat (limited to 'tests')
-rw-r--r--tests/stdlib/thttpclient_ssl.nim126
-rw-r--r--tests/stdlib/thttpclient_ssl_cert.pem29
-rw-r--r--tests/stdlib/thttpclient_ssl_key.pem52
-rw-r--r--tests/untestable/thttpclient_ssl.nim203
-rw-r--r--tests/untestable/thttpclient_ssl_disabled.nim40
-rw-r--r--tests/untestable/thttpclient_ssl_env_var.nim74
6 files changed, 524 insertions, 0 deletions
diff --git a/tests/stdlib/thttpclient_ssl.nim b/tests/stdlib/thttpclient_ssl.nim
new file mode 100644
index 000000000..f247ae442
--- /dev/null
+++ b/tests/stdlib/thttpclient_ssl.nim
@@ -0,0 +1,126 @@
+discard """
+  cmd: "nim $target --threads:on -d:ssl $options $file"
+"""
+
+#            Nim - Basic SSL integration tests
+#        (c) Copyright 2018 Nim contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+## Warning: this test performs local networking.
+## Test with:
+## ./bin/nim c -d:ssl -p:. --threads:on -r tests/stdlib/thttpclient_ssl.nim
+
+when not defined(windows):
+  # Disabled on Windows due to old OpenSSL version
+
+  import
+    httpclient,
+    net,
+    openssl,
+    os,
+    strutils,
+    threadpool,
+    times,
+    unittest
+
+  # bogus self-signed certificate
+  const
+    certFile = "tests/stdlib/thttpclient_ssl_cert.pem"
+    keyFile = "tests/stdlib/thttpclient_ssl_key.pem"
+
+  proc log(msg: string) =
+    when defined(ssldebug):
+      echo "    [" & $epochTime() & "] " & msg
+    # FIXME
+    echo "    [" & $epochTime() & "] " & msg
+    discard
+
+  proc runServer(port: Port): bool {.thread.} =
+    ## Run a trivial HTTPS server in a {.thread.}
+    ## Exit after serving one request
+
+    var socket = newSocket()
+    socket.setSockOpt(OptReusePort, true)
+    socket.bindAddr(port)
+
+    var ctx = newContext(certFile=certFile, keyFile=keyFile)
+
+    ##  Handle one connection
+    socket.listen()
+
+    var client: Socket
+    var address = ""
+
+    log "server: ready"
+    socket.acceptAddr(client, address)
+    log "server: incoming connection"
+
+    var ssl: SslPtr = SSL_new(ctx.context)
+    discard SSL_set_fd(ssl, client.getFd())
+    log "server: accepting connection"
+    if SSL_accept(ssl) <= 0:
+      ERR_print_errors_fp(stderr)
+    else:
+      const reply = "HTTP/1.0 200 OK\r\nServer: test\r\nContent-type: text/html\r\nContent-Length: 0\r\n\r\n"
+      log "server: sending reply"
+      discard SSL_write(ssl, reply.cstring, reply.len)
+
+    log "server: receiving a line"
+    let line = client.recvLine()
+    log "server: received $# bytes" % $line.len
+    log "closing"
+    SSL_free(ssl)
+    close(client)
+    close(socket)
+    log "server: exited"
+
+
+  suite "SSL self signed certificate check":
+
+    test "TCP socket":
+      const port = 12347.Port
+      let t = spawn runServer(port)
+      sleep(100)
+      var sock = newSocket()
+      var ctx = newContext()
+      ctx.wrapSocket(sock)
+      try:
+        log "client: connect"
+        sock.connect("127.0.0.1", port)
+        fail()
+      except:
+        let msg = getCurrentExceptionMsg()
+        check(msg.contains("certificate verify failed"))
+
+    test "HttpClient default: no check":
+      const port = 12345.Port
+      let t = spawn runServer(port)
+      sleep(100)
+
+      var client = newHttpClient()
+      try:
+        log "client: connect"
+        discard client.getContent("https://127.0.0.1:12345")
+      except:
+        let msg = getCurrentExceptionMsg()
+        log "client: unexpected exception: " & msg
+        fail()
+
+    test "HttpClient with CVerifyPeer":
+      const port = 12346.Port
+      let t = spawn runServer(port)
+      sleep(100)
+
+      var client = newHttpClient(sslContext=newContext(verifyMode=CVerifyPeer))
+      try:
+        log "client: connect"
+        discard client.getContent("https://127.0.0.1:12346")
+        log "getContent should have raised an exception"
+        fail()
+      except:
+        let msg = getCurrentExceptionMsg()
+        log "client: exception: " & msg
+        # SSL_shutdown:shutdown while in init
+        check(msg.contains("shutdown while in init") or msg.contains("alert number 48"))
diff --git a/tests/stdlib/thttpclient_ssl_cert.pem b/tests/stdlib/thttpclient_ssl_cert.pem
new file mode 100644
index 000000000..f15c15c52
--- /dev/null
+++ b/tests/stdlib/thttpclient_ssl_cert.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIFCTCCAvGgAwIBAgIURYQOmGzeh3Vy7Gk6Go4uAPwcNwAwDQYJKoZIhvcNAQEL
+BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTE5MDEyMzAwMTgzNFoXDTQ2MDYw
+OTAwMTgzNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEAzoEVEl7yqY+RqIagXDD4JB7LyONDvh8aJvBMnJVBgjaL
+JdkfQjvPGUzUkEbU5nc6u7lqFxzEv7hXrssQCB7TwJwfS2PT1Rj14IFlYPyw4DEe
+P1RVS/awurtv3jwumarVl7LR+IQfo59kJ/P8jZt8H3HscDbyhXcHeOWI6q+XlfdV
+mTUJVvABdUuOiIFjgfFVTpo+CKxy7c5caRDK7g1s9xB1/M9PUfJvHY1WrBWFOZf0
+Bl8iwn+ahuxfIVqsFL9leqLykgi1f4L20p7RaAK95TXCo3CszZm4Fsw9zhzkjoU7
+2h0nuYl197LZvRs3u/JJjzZERmsfVPIs5BtO8/MN1MvRn6hIGU5Q3kOVWqWxSkSl
+njrf+uwUdn/24uSCnygNeDuJzwW/2q4N9YI3oovqNIGpkT3FbAm7UKwI4lwhwmqw
+7WH+92ELj0BinmsMMRPD2OqvK+vzLVqwUIQkYug+Hjys6QGXMlrL0krrj7XOKSc3
+SvZa4j0S/Y5CKkw5xuZXxITsdaV6hGi3d/kuT+1ttOSfIIXJXDEiu4pYRfziKU1a
+8EhHMEajEi6ueLw7QmEPVx398erRwiUuP2y43yZ4mwVwvN3i5jlVztl4XsglDmQ+
+hahstVdMMA34K2rK0U8q8YjdYm+z99NmGEPYrS6Qnpr1xrICN83FOWFI0k7ttyMC
+AwEAAaNTMFEwHQYDVR0OBBYEFLqMY6eP3h3gu+ANs77xDBRnElxyMB8GA1UdIwQY
+MBaAFLqMY6eP3h3gu+ANs77xDBRnElxyMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggIBAJS+wyy0r+tVAlCa6V/xxlCDtW9n6L2nsqJXEjME0VvwGs3m
+ima68LyTQJqCSjjxSotaNOYKzUu4vRA3JssV+fUDR+NpmhpRuM74XmO05HUQkp9U
+dBEHyXp2aRQ9LSdvHo5D+RW+J4sHFb3PbU8NPx/t5Dg7il92S2QJQz1jNl+Nezc6
+2O8Vt1YbvWXfqM47URTpnQbWoo38pI44AgAuW3QagucKWsyounmhx65XcdtLn99g
+oZt496pU+hBpYu/IpXuBKNC4FvOrXTWAPkAbbYP39UFyiKwIyTosK+qdbhBlt1xi
+bBPn6N1W9L2BvUwM8fEB/qBuR9UfcMsIYJsWbbXMfyeF6lbaP7xD01rm+yU5PMMI
+Co40abixMntz4J3T2ixdCptf0He1U/UegOHwG1ZGgQzvOG6qI/xkNktDaSA75KR7
+BvPV1CmZC4ovVo1L4STrwnoRz5J49PNOHi9Okj9zJ99H7nsmsK16oxpIYkYHJWn+
+45jpG8SlDp7oev1OGGk/z+ZOTz+LcNxyvsRQVN8w5zNmjCSWiGqz+UUgppCZg8qd
+ECWokNQ5Lr20t1whynrX5bH0l887WPCQmm5VduRoyKFGhCRBSzcCtowSpiwZglUk
+CV0jgFKoteItdzZgsND5I1GaNOxZlnK3wN4H0pgZv7HlW6SP1OYd2Y67waJ7
+-----END CERTIFICATE-----
diff --git a/tests/stdlib/thttpclient_ssl_key.pem b/tests/stdlib/thttpclient_ssl_key.pem
new file mode 100644
index 000000000..6ab04122c
--- /dev/null
+++ b/tests/stdlib/thttpclient_ssl_key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+6THbQsJpR5AalTVvXs3X1RnCLnHiNYg=
+-----END PRIVATE KEY-----
diff --git a/tests/untestable/thttpclient_ssl.nim b/tests/untestable/thttpclient_ssl.nim
new file mode 100644
index 000000000..3744df92d
--- /dev/null
+++ b/tests/untestable/thttpclient_ssl.nim
@@ -0,0 +1,203 @@
+#
+#
+#            Nim - SSL integration tests
+#        (c) Copyright 2017 Nim contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+## Warning: this test performs external networking.
+## Test with:
+## ./bin/nim c -d:ssl -p:. --threads:on -r tests/untestable/thttpclient_ssl.nim
+##
+## See https://github.com/FedericoCeratto/ssl-comparison/blob/master/README.md
+## for a comparison with other clients.
+
+import
+  httpclient,
+  net,
+  strutils,
+  threadpool,
+  unittest
+
+
+type
+  # bad and dubious tests should not pass SSL validation
+  # "_broken" mark the test as skipped. Some tests have different
+  # behavior depending on OS and SSL version!
+  # TODO: chase and fix the broken tests
+  Category = enum
+    good, bad, dubious, good_broken, bad_broken, dubious_broken
+  CertTest = tuple[url:string, category:Category, desc: string]
+
+const certificate_tests: array[0..55, CertTest] = [
+  ("https://wrong.host.badssl.com/", bad, "wrong.host"),
+  ("https://captive-portal.badssl.com/", bad, "captive-portal"),
+  ("https://expired.badssl.com/", bad, "expired"),
+  ("https://google.com/", good, "good"),
+  ("https://self-signed.badssl.com/", bad, "self-signed"),
+  ("https://untrusted-root.badssl.com/", bad, "untrusted-root"),
+  ("https://revoked.badssl.com/", bad_broken, "revoked"),
+  ("https://pinning-test.badssl.com/", bad_broken, "pinning-test"),
+  ("https://no-common-name.badssl.com/", dubious_broken, "no-common-name"),
+  ("https://no-subject.badssl.com/", dubious_broken, "no-subject"),
+  ("https://incomplete-chain.badssl.com/", dubious, "incomplete-chain"),
+  ("https://sha1-intermediate.badssl.com/", bad_broken, "sha1-intermediate"),
+  ("https://sha256.badssl.com/", good, "sha256"),
+  ("https://sha384.badssl.com/", good, "sha384"),
+  ("https://sha512.badssl.com/", good, "sha512"),
+  ("https://1000-sans.badssl.com/", good, "1000-sans"),
+  ("https://10000-sans.badssl.com/", good_broken, "10000-sans"),
+  ("https://ecc256.badssl.com/", good, "ecc256"),
+  ("https://ecc384.badssl.com/", good, "ecc384"),
+  ("https://rsa2048.badssl.com/", good, "rsa2048"),
+  ("https://rsa8192.badssl.com/", dubious_broken, "rsa8192"),
+  ("http://http.badssl.com/", good, "regular http"),
+  ("https://http.badssl.com/", bad_broken, "http on https URL"),  # FIXME
+  ("https://cbc.badssl.com/", dubious_broken, "cbc"),
+  ("https://rc4-md5.badssl.com/", bad, "rc4-md5"),
+  ("https://rc4.badssl.com/", bad, "rc4"),
+  ("https://3des.badssl.com/", bad, "3des"),
+  ("https://null.badssl.com/", bad, "null"),
+  ("https://mozilla-old.badssl.com/", bad_broken, "mozilla-old"),
+  ("https://mozilla-intermediate.badssl.com/", dubious_broken, "mozilla-intermediate"),
+  ("https://mozilla-modern.badssl.com/", good, "mozilla-modern"),
+  ("https://dh480.badssl.com/", bad, "dh480"),
+  ("https://dh512.badssl.com/", bad, "dh512"),
+  ("https://dh1024.badssl.com/", dubious_broken, "dh1024"),
+  ("https://dh2048.badssl.com/", good, "dh2048"),
+  ("https://dh-small-subgroup.badssl.com/", bad_broken, "dh-small-subgroup"),
+  ("https://dh-composite.badssl.com/", bad_broken, "dh-composite"),
+  ("https://static-rsa.badssl.com/", dubious_broken, "static-rsa"),
+  ("https://tls-v1-0.badssl.com:1010/", dubious_broken, "tls-v1-0"),
+  ("https://tls-v1-1.badssl.com:1011/", dubious_broken, "tls-v1-1"),
+  ("https://invalid-expected-sct.badssl.com/", bad_broken, "invalid-expected-sct"),
+  ("https://hsts.badssl.com/", good, "hsts"),
+  ("https://upgrade.badssl.com/", good, "upgrade"),
+  ("https://preloaded-hsts.badssl.com/", good, "preloaded-hsts"),
+  ("https://subdomain.preloaded-hsts.badssl.com/", bad, "subdomain.preloaded-hsts"),
+  ("https://https-everywhere.badssl.com/", good, "https-everywhere"),
+  ("https://long-extended-subdomain-name-containing-many-letters-and-dashes.badssl.com/", good,
+    "long-extended-subdomain-name-containing-many-letters-and-dashes"),
+  ("https://longextendedsubdomainnamewithoutdashesinordertotestwordwrapping.badssl.com/", good,
+    "longextendedsubdomainnamewithoutdashesinordertotestwordwrapping"),
+  ("https://superfish.badssl.com/", bad, "(Lenovo) Superfish"),
+  ("https://edellroot.badssl.com/", bad, "(Dell) eDellRoot"),
+  ("https://dsdtestprovider.badssl.com/", bad, "(Dell) DSD Test Provider"),
+  ("https://preact-cli.badssl.com/", bad, "preact-cli"),
+  ("https://webpack-dev-server.badssl.com/", bad, "webpack-dev-server"),
+  ("https://mitm-software.badssl.com/", bad, "mitm-software"),
+  ("https://sha1-2016.badssl.com/", dubious, "sha1-2016"),
+  ("https://sha1-2017.badssl.com/", bad, "sha1-2017"),
+]
+
+
+template evaluate(exception_msg: string, category: Category, desc: string) =
+  # Evaluate test outcome. Testes flagged as _broken are evaluated and skipped
+  let raised = (exception_msg.len > 0)
+  let should_not_raise = category in {good, dubious_broken, bad_broken}
+  if should_not_raise xor raised:
+    # we are seeing a known behavior
+    if category in {good_broken, dubious_broken, bad_broken}:
+      skip()
+    if raised:
+      check exception_msg == "No SSL certificate found." or
+        exception_msg == "SSL Certificate check failed." or
+        exception_msg.contains("certificate verify failed") or
+        exception_msg.contains("key too small") or
+        exception_msg.contains "shutdown while in init"
+
+  else:
+    # this is unexpected
+    if raised:
+      echo "         $# ($#) raised: $#" % [desc, $category, exception_msg]
+    else:
+      echo "         $# ($#) did not raise" % [desc, $category]
+    if category in {good, dubious, bad}:
+      fail()
+
+
+suite "SSL certificate check - httpclient":
+
+  for i, ct in certificate_tests:
+
+    test ct.desc:
+      var ctx = newContext(verifyMode=CVerifyPeer)
+      var client = newHttpClient(sslContext=ctx)
+      let exception_msg =
+        try:
+          let a = $client.getContent(ct.url)
+          ""
+        except:
+          getCurrentExceptionMsg()
+
+      evaluate(exception_msg, ct.category, ct.desc)
+
+
+
+# threaded tests
+
+
+type
+  TTOutcome = ref object
+    desc, exception_msg: string
+    category: Category
+
+proc run_t_test(ct: CertTest): TTOutcome {.thread.} =
+  ## Run test in a {.thread.} - return by ref
+  result = TTOutcome(desc:ct.desc, exception_msg:"", category: ct.category)
+  try:
+    var ctx = newContext(verifyMode=CVerifyPeer)
+    var client = newHttpClient(sslContext=ctx)
+    let a = $client.getContent(ct.url)
+  except:
+    result.exception_msg = getCurrentExceptionMsg()
+
+
+suite "SSL certificate check - httpclient - threaded":
+
+  # Spawn threads before the "test" blocks
+  var outcomes = newSeq[FlowVar[TTOutcome]](certificate_tests.len)
+  for i, ct in certificate_tests:
+    let t = spawn run_t_test(ct)
+    outcomes[i] = t
+
+  # create "test" blocks and handle thread outputs
+  for t in outcomes:
+    let outcome = ^t  # wait for a thread to terminate
+
+    test outcome.desc:
+
+      evaluate(outcome.exception_msg, outcome.category, outcome.desc)
+
+
+# net tests
+
+
+type NetSocketTest = tuple[hostname: string, port: Port, category:Category, desc: string]
+const net_tests:array[0..3, NetSocketTest] = [
+  ("imap.gmail.com", 993.Port, good, "IMAP"),
+  ("wrong.host.badssl.com", 443.Port, bad, "wrong.host"),
+  ("captive-portal.badssl.com", 443.Port, bad, "captive-portal"),
+  ("expired.badssl.com", 443.Port, bad, "expired"),
+]
+# TODO: ("null.badssl.com", 443.Port, bad_broken, "null"),
+
+
+suite "SSL certificate check - sockets":
+
+  for ct in net_tests:
+
+    test ct.desc:
+
+      var sock = newSocket()
+      var ctx = newContext()
+      ctx.wrapSocket(sock)
+      let exception_msg =
+        try:
+          sock.connect(ct.hostname, ct.port)
+          ""
+        except:
+          getCurrentExceptionMsg()
+
+      evaluate(exception_msg, ct.category, ct.desc)
diff --git a/tests/untestable/thttpclient_ssl_disabled.nim b/tests/untestable/thttpclient_ssl_disabled.nim
new file mode 100644
index 000000000..4d4ede4de
--- /dev/null
+++ b/tests/untestable/thttpclient_ssl_disabled.nim
@@ -0,0 +1,40 @@
+#
+#            Nim - SSL integration tests
+#        (c) Copyright 2017 Nim contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+## Warning: this test performs external networking.
+## Compile and run with:
+## ./bin/nim c -d:nimDisableCertificateValidation -d:ssl -r -p:. tests/untestable/thttpclient_ssl_disabled.nim
+
+import httpclient,
+  net,
+  unittest,
+  ospaths
+
+from strutils import contains
+
+const expired = "https://expired.badssl.com/"
+
+doAssert defined(nimDisableCertificateValidation)
+
+suite "SSL certificate check - disabled":
+
+  test "httpclient in insecure mode":
+    var ctx = newContext(verifyMode = CVerifyPeer)
+    var client = newHttpClient(sslContext = ctx)
+    let a = $client.getContent(expired)
+
+  test "httpclient in insecure mode":
+    var ctx = newContext(verifyMode = CVerifyPeerUseEnvVars)
+    var client = newHttpClient(sslContext = ctx)
+    let a = $client.getContent(expired)
+
+  test "net socket in insecure mode":
+    var sock = newSocket()
+    var ctx = newContext(verifyMode = CVerifyPeerUseEnvVars)
+    ctx.wrapSocket(sock)
+    sock.connect("expired.badssl.com", 443.Port)
+    sock.close
diff --git a/tests/untestable/thttpclient_ssl_env_var.nim b/tests/untestable/thttpclient_ssl_env_var.nim
new file mode 100644
index 000000000..32af43579
--- /dev/null
+++ b/tests/untestable/thttpclient_ssl_env_var.nim
@@ -0,0 +1,74 @@
+#
+#            Nim - SSL integration tests
+#        (c) Copyright 2017 Nim contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+## Warning: this test performs external networking.
+## Compile with:
+## ./bin/nim c -d:ssl -p:. tests/untestable/thttpclient_ssl_env_var.nim
+##
+## Test with:
+##  SSL_CERT_FILE=BogusInexistentFileName tests/untestable/thttpclient_ssl_env_var
+##  SSL_CERT_DIR=BogusInexistentDirName tests/untestable/thttpclient_ssl_env_var
+
+import httpclient, unittest, ospaths
+from net import newSocket, newContext, wrapSocket, connect, close, Port,
+  CVerifyPeerUseEnvVars
+from strutils import contains
+
+const
+  expired = "https://expired.badssl.com/"
+  good = "https://google.com/"
+
+
+suite "SSL certificate check":
+
+  test "httpclient with inexistent file":
+    if existsEnv("SSL_CERT_FILE"):
+      var ctx = newContext(verifyMode=CVerifyPeerUseEnvVars)
+      var client = newHttpClient(sslContext=ctx)
+      checkpoint("Client created")
+      check client.getContent("https://google.com").contains("doctype")
+      checkpoint("Google ok")
+      try:
+        let a = $client.getContent(good)
+        echo "Connection should have failed"
+        fail()
+      except:
+        echo getCurrentExceptionMsg()
+        check getCurrentExceptionMsg().contains("certificate verify failed")
+
+    elif existsEnv("SSL_CERT_DIR"):
+      try:
+        var ctx = newContext(verifyMode=CVerifyPeerUseEnvVars)
+        var client = newHttpClient(sslContext=ctx)
+        echo "Should have raised 'No SSL/TLS CA certificates found.'"
+        fail()
+      except:
+        check getCurrentExceptionMsg() ==
+          "No SSL/TLS CA certificates found."
+
+  test "net socket with inexistent file":
+    if existsEnv("SSL_CERT_FILE"):
+      var sock = newSocket()
+      var ctx = newContext(verifyMode=CVerifyPeerUseEnvVars)
+      ctx.wrapSocket(sock)
+      checkpoint("Socket created")
+      try:
+        sock.connect("expired.badssl.com", 443.Port)
+        fail()
+      except:
+        sock.close
+        check getCurrentExceptionMsg().contains("certificate verify failed")
+
+    elif existsEnv("SSL_CERT_DIR"):
+      var sock = newSocket()
+      checkpoint("Socket created")
+      try:
+        var ctx = newContext(verifyMode=CVerifyPeerUseEnvVars) # raises here
+        fail()
+      except:
+        check getCurrentExceptionMsg() ==
+          "No SSL/TLS CA certificates found."