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
This commit is contained in:
parent
1d665adecd
commit
5b85444244
13 changed files with 872 additions and 12 deletions
91
.github/workflows/ci_ssl.yml
vendored
Normal file
91
.github/workflows/ci_ssl.yml
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
name: Nim SSL CI
|
||||
on:
|
||||
pull_request:
|
||||
# Run only on changes on related files
|
||||
paths:
|
||||
- 'lib/pure/httpclient.nim'
|
||||
- 'lib/pure/net.nim'
|
||||
- 'lib/pure/ssl_certs.nim'
|
||||
- 'lib/wrappers/openssl.nim'
|
||||
- 'tests/stdlib/thttpclient_ssl*'
|
||||
- 'tests/untestable/thttpclient_ssl*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-10.15, windows-2019]
|
||||
cpu: [amd64]
|
||||
name: '${{ matrix.os }} (${{ matrix.cpu }})'
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: 'Checkout csources'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nim-lang/csources
|
||||
path: csources
|
||||
|
||||
- name: 'Install dependencies (Linux amd64)'
|
||||
if: runner.os == 'Linux' && matrix.cpu == 'amd64'
|
||||
run: |
|
||||
sudo apt-fast update -qq
|
||||
DEBIAN_FRONTEND='noninteractive' \
|
||||
sudo apt-fast install --no-install-recommends -y libssl1.1
|
||||
- name: 'Install dependencies (macOS)'
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install make
|
||||
- name: 'Install dependencies (Windows)'
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir dist
|
||||
curl -L https://nim-lang.org/download/mingw64.7z -o dist/mingw64.7z
|
||||
curl -L https://nim-lang.org/download/dlls.zip -o dist/dlls.zip
|
||||
7z x dist/mingw64.7z -odist
|
||||
7z x dist/dlls.zip -obin
|
||||
echo "::add-path::${{ github.workspace }}/dist/mingw64/bin"
|
||||
|
||||
- name: 'Add build binaries to PATH'
|
||||
shell: bash
|
||||
run: echo "::add-path::${{ github.workspace }}/bin"
|
||||
|
||||
- name: 'Build 1-stage compiler from csources'
|
||||
shell: bash
|
||||
run: |
|
||||
ncpu=
|
||||
case '${{ runner.os }}' in
|
||||
'Linux')
|
||||
ncpu=$(nproc)
|
||||
;;
|
||||
'macOS')
|
||||
ncpu=$(sysctl -n hw.ncpu)
|
||||
;;
|
||||
'Windows')
|
||||
ncpu=$NUMBER_OF_PROCESSORS
|
||||
;;
|
||||
esac
|
||||
[[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1
|
||||
|
||||
make -C csources -j $ncpu CC=gcc ucpu='${{ matrix.cpu }}'
|
||||
|
||||
- name: 'Build koch'
|
||||
shell: bash
|
||||
run: nim c koch
|
||||
|
||||
- name: 'Build the real compiler'
|
||||
shell: bash
|
||||
run: ./koch boot
|
||||
|
||||
- name: 'Run SSL nimDisableCertificateValidation integration tests'
|
||||
shell: bash
|
||||
run: nim c -d:nimDisableCertificateValidation -d:ssl -r -p:. tests/untestable/thttpclient_ssl_disabled.nim
|
||||
|
||||
- name: 'Run SSL certificate check integration tests'
|
||||
# Not supported on Windows due to old openssl version
|
||||
if: runner.os != 'Windows'
|
||||
shell: bash
|
||||
run: nim c -d:ssl -p:. --threads:on -r tests/untestable/thttpclient_ssl.nim
|
||||
|
|
@ -29,6 +29,8 @@ install:
|
|||
- cd ..
|
||||
|
||||
build_script:
|
||||
- openssl version
|
||||
- openssl version -d
|
||||
- bin\nim c koch
|
||||
- koch runCI
|
||||
|
||||
|
|
|
|||
|
|
@ -144,6 +144,8 @@ echo f
|
|||
empty. This was required for intuitive behaviour of the strscans module
|
||||
(see bug #13605).
|
||||
- `std/oswalkdir` was buggy, it's now deprecated and reuses `std/os` procs
|
||||
- `net.newContext` now performs SSL Certificate checking on Linux and OSX.
|
||||
Define `nimDisableCertificateValidation` to disable it globally.
|
||||
|
||||
|
||||
## Language additions
|
||||
|
|
|
|||
|
|
@ -119,6 +119,14 @@
|
|||
## You will also have to compile with ``ssl`` defined like so:
|
||||
## ``nim c -d:ssl ...``.
|
||||
##
|
||||
## Certificate validation is NOT performed by default.
|
||||
## This will change in future.
|
||||
##
|
||||
## A set of directories and files from the `ssl_certs <ssl_certs.html>`_
|
||||
## module are scanned to locate CA certificates.
|
||||
##
|
||||
## See `newContext <net.html#newContext>`_ to tweak or disable certificate validation.
|
||||
##
|
||||
## Timeouts
|
||||
## ========
|
||||
##
|
||||
|
|
@ -552,6 +560,7 @@ proc newHttpClient*(userAgent = defUserAgent, maxRedirects = 5,
|
|||
## default is 5.
|
||||
##
|
||||
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
|
||||
## See `SSL/TLS support <##ssl-tls-support>`_
|
||||
##
|
||||
## ``proxy`` specifies an HTTP proxy to use for this HTTP client's
|
||||
## connections.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@
|
|||
{.deadCodeElim: on.} # dce option deprecated
|
||||
import nativesockets, os, strutils, parseutils, times, sets, options,
|
||||
std/monotimes
|
||||
from ospaths import getEnv
|
||||
from ssl_certs import scanSSLCertificates
|
||||
export nativesockets.Port, nativesockets.`$`, nativesockets.`==`
|
||||
export Domain, SockType, Protocol
|
||||
|
||||
|
|
@ -83,7 +85,7 @@ when defineSsl:
|
|||
SslError* = object of Exception
|
||||
|
||||
SslCVerifyMode* = enum
|
||||
CVerifyNone, CVerifyPeer
|
||||
CVerifyNone, CVerifyPeer, CVerifyPeerUseEnvVars
|
||||
|
||||
SslProtVersion* = enum
|
||||
protSSLv2, protSSLv3, protTLSv1, protSSLv23
|
||||
|
|
@ -517,17 +519,30 @@ when defineSsl:
|
|||
raiseSSLError("Verification of private key file failed.")
|
||||
|
||||
proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
|
||||
certFile = "", keyFile = "", cipherList = "ALL"): SslContext =
|
||||
certFile = "", keyFile = "", cipherList = "ALL",
|
||||
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.
|
||||
##
|
||||
## There are currently only two options for verify mode;
|
||||
## one is ``CVerifyNone`` and with it certificates will not be verified
|
||||
## the other is ``CVerifyPeer`` and certificates will be verified for
|
||||
## it, ``CVerifyPeer`` is the safest choice.
|
||||
## There are three options for verify mode:
|
||||
## ``CVerifyNone``: certificates are not verified;
|
||||
## ``CVerifyPeer``: certificates are verified;
|
||||
## ``CVerifyPeerUseEnvVars``: certificates are verified and the optional
|
||||
## environment variables SSL_CERT_FILE and SSL_CERT_DIR are also used to
|
||||
## locate certificates
|
||||
##
|
||||
## The `nimDisableCertificateValidation` define overrides verifyMode and
|
||||
## disables certificate verification globally!
|
||||
##
|
||||
## CA certificates will be loaded, in the following order, from:
|
||||
##
|
||||
## - caFile, caDir, parameters, if set
|
||||
## - if `verifyMode` is set to ``CVerifyPeerUseEnvVars``,
|
||||
## the SSL_CERT_FILE and SSL_CERT_DIR environment variables are used
|
||||
## - a set of files and directories from the `ssl_certs <ssl_certs.html>`_ file.
|
||||
##
|
||||
## The last two parameters specify the certificate file path and the key file
|
||||
## path, a server socket will most likely not work without these.
|
||||
|
|
@ -550,18 +565,41 @@ when defineSsl:
|
|||
|
||||
if newCTX.SSL_CTX_set_cipher_list(cipherList) != 1:
|
||||
raiseSSLError()
|
||||
case verifyMode
|
||||
of CVerifyPeer:
|
||||
newCTX.SSL_CTX_set_verify(SSL_VERIFY_PEER, nil)
|
||||
of CVerifyNone:
|
||||
|
||||
when defined(nimDisableCertificateValidation) or defined(windows):
|
||||
newCTX.SSL_CTX_set_verify(SSL_VERIFY_NONE, nil)
|
||||
else:
|
||||
case verifyMode
|
||||
of CVerifyPeer, CVerifyPeerUseEnvVars:
|
||||
newCTX.SSL_CTX_set_verify(SSL_VERIFY_PEER, nil)
|
||||
of CVerifyNone:
|
||||
newCTX.SSL_CTX_set_verify(SSL_VERIFY_NONE, nil)
|
||||
|
||||
if newCTX == nil:
|
||||
raiseSSLError()
|
||||
|
||||
discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY)
|
||||
newCTX.loadCertificates(certFile, keyFile)
|
||||
|
||||
result = SslContext(context: newCTX, referencedData: initSet[int](),
|
||||
when not defined(nimDisableCertificateValidation) and not defined(windows):
|
||||
if verifyMode != CVerifyNone:
|
||||
# Use the caDir and caFile parameters if set
|
||||
if caDir != "" or caFile != "":
|
||||
if newCTX.SSL_CTX_load_verify_locations(caDir, caFile) != 0:
|
||||
raise newException(IOError, "Failed to load SSL/TLS CA certificate(s).")
|
||||
|
||||
else:
|
||||
# Scan for certs in known locations. For CVerifyPeerUseEnvVars also scan
|
||||
# the SSL_CERT_FILE and SSL_CERT_DIR env vars
|
||||
var found = false
|
||||
for fn in scanSSLCertificates():
|
||||
if newCTX.SSL_CTX_load_verify_locations(fn, "") == 0:
|
||||
found = true
|
||||
break
|
||||
if not found:
|
||||
raise newException(IOError, "No SSL/TLS CA certificates found.")
|
||||
|
||||
result = SSLContext(context: newCTX, referencedData: initSet[int](),
|
||||
extraInternal: new(SslContextExtraInternal))
|
||||
|
||||
proc getExtraInternal(ctx: SslContext): SslContextExtraInternal =
|
||||
|
|
@ -645,6 +683,7 @@ when defineSsl:
|
|||
## This must be called on an unconnected socket; an SSL session will
|
||||
## be started when the socket is connected.
|
||||
##
|
||||
## FIXME:
|
||||
## **Disclaimer**: This code is not well tested, may be very unsafe and
|
||||
## prone to security vulnerabilities.
|
||||
|
||||
|
|
@ -660,7 +699,25 @@ when defineSsl:
|
|||
if SSL_set_fd(socket.sslHandle, socket.fd) != 1:
|
||||
raiseSSLError()
|
||||
|
||||
proc wrapConnectedSocket*(ctx: SslContext, socket: Socket,
|
||||
proc checkCertName(socket: Socket, hostname: string) =
|
||||
## 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.")
|
||||
|
||||
const X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT = 0x1.cuint
|
||||
const size = 1024
|
||||
var peername: string = newString(size)
|
||||
let match = certificate.X509_check_host(hostname.cstring, hostname.len.cint,
|
||||
X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, peername)
|
||||
if match != 1:
|
||||
raiseSSLError("SSL Certificate check failed.")
|
||||
|
||||
proc wrapConnectedSocket*(ctx: SSLContext, socket: Socket,
|
||||
handshake: SslHandshakeType,
|
||||
hostname: string = "") =
|
||||
## Wraps a connected socket in an SSL context. This function effectively
|
||||
|
|
@ -671,6 +728,7 @@ when defineSsl:
|
|||
## This should be called on a connected socket, and will perform
|
||||
## an SSL handshake immediately.
|
||||
##
|
||||
## FIXME:
|
||||
## **Disclaimer**: This code is not well tested, may be very unsafe and
|
||||
## prone to security vulnerabilities.
|
||||
wrapSocket(ctx, socket)
|
||||
|
|
@ -682,6 +740,9 @@ when defineSsl:
|
|||
discard SSL_set_tlsext_host_name(socket.sslHandle, hostname)
|
||||
let ret = SSL_connect(socket.sslHandle)
|
||||
socketError(socket, ret)
|
||||
when not defined(nimDisableCertificateValidation) and not defined(windows):
|
||||
if hostname.len > 0 and not isIpAddress(hostname):
|
||||
socket.checkCertName(hostname)
|
||||
of handshakeAsServer:
|
||||
let ret = SSL_accept(socket.sslHandle)
|
||||
socketError(socket, ret)
|
||||
|
|
@ -1638,6 +1699,9 @@ proc connect*(socket: Socket, address: string,
|
|||
|
||||
let ret = SSL_connect(socket.sslHandle)
|
||||
socketError(socket, ret)
|
||||
when not defined(nimDisableCertificateValidation) and not defined(windows):
|
||||
if not isIpAddress(address):
|
||||
socket.checkCertName(address)
|
||||
|
||||
proc connectAsync(socket: Socket, name: string, port = Port(0),
|
||||
af: Domain = AF_INET) {.tags: [ReadIOEffect].} =
|
||||
|
|
|
|||
97
lib/pure/ssl_certs.nim
Normal file
97
lib/pure/ssl_certs.nim
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2017 Nim contributors
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
## Scan for SSL/TLS CA certificates on disk
|
||||
## The default locations can be overridden using the SSL_CERT_FILE and
|
||||
## SSL_CERT_DIR environment variables.
|
||||
|
||||
import os, strutils
|
||||
from ospaths import existsEnv, getEnv
|
||||
import strutils
|
||||
|
||||
# SECURITY: this unnecessarily scans through dirs/files regardless of the
|
||||
# actual host OS/distribution. Hopefully all the paths are writeble only by
|
||||
# root.
|
||||
|
||||
# FWIW look for files before scanning entire dirs.
|
||||
|
||||
const certificate_paths = [
|
||||
# Debian, Ubuntu, Arch: maintained by update-ca-certificates, SUSE, Gentoo
|
||||
# NetBSD (security/mozilla-rootcerts)
|
||||
# SLES10/SLES11, https://golang.org/issue/12139
|
||||
"/etc/ssl/certs/ca-certificates.crt",
|
||||
# OpenSUSE
|
||||
"/etc/ssl/ca-bundle.pem",
|
||||
# Red Hat 5+, Fedora, Centos
|
||||
"/etc/pki/tls/certs/ca-bundle.crt",
|
||||
# Red Hat 4
|
||||
"/usr/share/ssl/certs/ca-bundle.crt",
|
||||
# FreeBSD (security/ca-root-nss package)
|
||||
"/usr/local/share/certs/ca-root-nss.crt",
|
||||
# CentOS/RHEL 7
|
||||
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
|
||||
# OpenBSD, FreeBSD (optional symlink)
|
||||
"/etc/ssl/cert.pem",
|
||||
# Mac OS X
|
||||
"/System/Library/OpenSSL/certs/cert.pem",
|
||||
# Fedora/RHEL
|
||||
"/etc/pki/tls/certs",
|
||||
# Android
|
||||
"/system/etc/security/cacerts",
|
||||
# FreeBSD
|
||||
"/usr/local/share/certs",
|
||||
# NetBSD
|
||||
"/etc/openssl/certs",
|
||||
]
|
||||
|
||||
iterator scanSSLCertificates*(useEnvVars = false): string =
|
||||
## Scan for SSL/TLS CA certificates on disk.
|
||||
##
|
||||
## if `useEnvVars` is true, the SSL_CERT_FILE and SSL_CERT_DIR
|
||||
## environment variables can be used to override the certificate
|
||||
## directories to scan or specify a CA certificate file.
|
||||
if existsEnv("SSL_CERT_FILE"):
|
||||
yield getEnv("SSL_CERT_FILE")
|
||||
|
||||
elif existsEnv("SSL_CERT_DIR"):
|
||||
let p = getEnv("SSL_CERT_DIR")
|
||||
for fn in joinPath(p, "*").walkFiles():
|
||||
yield fn
|
||||
|
||||
else:
|
||||
for p in certificate_paths:
|
||||
if p.endsWith(".pem") or p.endsWith(".crt"):
|
||||
if existsFile(p):
|
||||
yield p
|
||||
elif existsDir(p):
|
||||
for fn in joinPath(p, "*").walkFiles():
|
||||
yield fn
|
||||
|
||||
# Certificates management on windows
|
||||
# when defined(windows) or defined(nimdoc):
|
||||
#
|
||||
# import openssl
|
||||
#
|
||||
# type
|
||||
# PCCertContext {.final, pure.} = pointer
|
||||
# X509 {.final, pure.} = pointer
|
||||
# CertStore {.final, pure.} = pointer
|
||||
#
|
||||
# # OpenSSL cert store
|
||||
#
|
||||
# {.push stdcall, dynlib: "kernel32", importc.}
|
||||
#
|
||||
# proc CertOpenSystemStore*(hprov: pointer=nil, szSubsystemProtocol: cstring): CertStore
|
||||
#
|
||||
# proc CertEnumCertificatesInStore*(hCertStore: CertStore, pPrevCertContext: PCCertContext): pointer
|
||||
#
|
||||
# proc CertFreeCertificateContext*(pContext: PCCertContext): bool
|
||||
#
|
||||
# proc CertCloseStore*(hCertStore:CertStore, flags:cint): bool
|
||||
#
|
||||
# {.pop.}
|
||||
|
|
@ -647,3 +647,74 @@ proc md5_Str*(str: string): string =
|
|||
|
||||
when defined(nimHasStyleChecks):
|
||||
{.pop.}
|
||||
|
||||
|
||||
# Certificate validation
|
||||
# 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.}
|
||||
|
||||
proc X509_get_subject_name*(a: PX509): PX509_NAME{.cdecl, dynlib: DLLSSLName, importc.}
|
||||
|
||||
proc X509_get_issuer_name*(a: PX509): PX509_NAME{.cdecl, dynlib: DLLUtilName, importc.}
|
||||
|
||||
proc X509_NAME_oneline*(a: PX509_NAME, buf: cstring, size: cint): cstring {.
|
||||
cdecl, dynlib:DLLSSLName, importc.}
|
||||
|
||||
proc X509_NAME_get_text_by_NID*(subject:cstring, NID: cint, buf: cstring, size: cint): cint{.
|
||||
cdecl, dynlib:DLLSSLName, importc.}
|
||||
|
||||
proc X509_check_host*(cert: PX509, name: cstring, namelen: cint, flags:cuint, peername: cstring): cint {.cdecl, dynlib: DLLSSLName, importc.}
|
||||
|
||||
# Certificates store
|
||||
|
||||
type PX509_STORE* = SslPtr
|
||||
type PX509_OBJECT* = SslPtr
|
||||
|
||||
{.push callconv:cdecl, dynlib:DLLUtilName, importc.}
|
||||
|
||||
proc X509_OBJECT_new*(): PX509_OBJECT
|
||||
proc X509_OBJECT_free*(a: PX509_OBJECT)
|
||||
|
||||
proc X509_STORE_new*(): PX509_STORE
|
||||
proc X509_STORE_free*(v: PX509_STORE)
|
||||
proc X509_STORE_lock*(ctx: PX509_STORE): cint
|
||||
proc X509_STORE_unlock*(ctx: PX509_STORE): cint
|
||||
proc X509_STORE_up_ref*(v: PX509_STORE): cint
|
||||
proc X509_STORE_set_flags*(ctx: PX509_STORE; flags: culong): cint
|
||||
proc X509_STORE_set_purpose*(ctx: PX509_STORE; purpose: cint): cint
|
||||
proc X509_STORE_set_trust*(ctx: PX509_STORE; trust: cint): cint
|
||||
proc X509_STORE_add_cert*(ctx: PX509_STORE; x: PX509): cint
|
||||
|
||||
proc d2i_X509*(px: ptr PX509, i: ptr ptr cuchar, len: cint): PX509
|
||||
|
||||
proc i2d_X509*(cert: PX509; o: ptr ptr cuchar): cint
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc d2i_X509*(b: string): PX509 =
|
||||
## decode DER/BER bytestring into X.509 certificate struct
|
||||
var bb = b.cstring
|
||||
let i = cast[ptr ptr cuchar](addr bb)
|
||||
let ret = d2i_X509(addr result, i, b.len.cint)
|
||||
if ret.isNil:
|
||||
raise newException(Exception, "X.509 certificate decoding failed")
|
||||
|
||||
proc i2d_X509*(cert: PX509): string =
|
||||
## encode `cert` to DER string
|
||||
let encoded_length = i2d_X509(cert, nil)
|
||||
result = newString(encoded_length)
|
||||
var q = result.cstring
|
||||
let o = cast[ptr ptr cuchar](addr q)
|
||||
let length = i2d_X509(cert, o)
|
||||
if length.int <= 0:
|
||||
raise newException(Exception, "X.509 certificate encoding failed")
|
||||
|
||||
when isMainModule:
|
||||
# A simple certificate test
|
||||
let certbytes = readFile("certificate.der")
|
||||
let cert = d2i_X509(certbytes)
|
||||
let encoded = cert.i2d_X509()
|
||||
assert encoded == certbytes
|
||||
|
|
|
|||
126
tests/stdlib/thttpclient_ssl.nim
Normal file
126
tests/stdlib/thttpclient_ssl.nim
Normal file
|
|
@ -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"))
|
||||
29
tests/stdlib/thttpclient_ssl_cert.pem
Normal file
29
tests/stdlib/thttpclient_ssl_cert.pem
Normal file
|
|
@ -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-----
|
||||
52
tests/stdlib/thttpclient_ssl_key.pem
Normal file
52
tests/stdlib/thttpclient_ssl_key.pem
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
6THbQsJpR5AalTVvXs3X1RnCLnHiNYg=
|
||||
-----END PRIVATE KEY-----
|
||||
203
tests/untestable/thttpclient_ssl.nim
Normal file
203
tests/untestable/thttpclient_ssl.nim
Normal file
|
|
@ -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)
|
||||
40
tests/untestable/thttpclient_ssl_disabled.nim
Normal file
40
tests/untestable/thttpclient_ssl_disabled.nim
Normal file
|
|
@ -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
|
||||
74
tests/untestable/thttpclient_ssl_env_var.nim
Normal file
74
tests/untestable/thttpclient_ssl_env_var.nim
Normal file
|
|
@ -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."
|
||||
Loading…
Add table
Add a link
Reference in a new issue