Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -99,11 +99,11 @@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ CC = @CC@ CCLD = @CCLD@ CFLAGS_DEFAULT = @CFLAGS_DEFAULT@ -CFLAGS_WARNING = @CFLAGS_WARNING@ -Wno-deprecated-declarations +CFLAGS_WARNING = @CFLAGS_WARNING@ EXEEXT = @EXEEXT@ LDFLAGS_DEFAULT = @LDFLAGS_DEFAULT@ MAKE_LIB = @MAKE_LIB@ MAKE_STUB_LIB = @MAKE_STUB_LIB@ OBJEXT = @OBJEXT@ @@ -155,11 +155,11 @@ # must make sure that configure.ac checks for the necessary components # that your library may use. TCL_DEFS can actually be a problem if # you do not compile with a similar machine setup as the Tcl core was # compiled with. #DEFS = $(TCL_DEFS) @DEFS@ $(PKG_CFLAGS) -DEFS = @DEFS@ $(PKG_CFLAGS) -DNO_SSL2 -DNO_SSL3 +DEFS = @DEFS@ $(PKG_CFLAGS) # Move pkgIndex.tcl to 'BINARIES' var if it is generated in the Makefile CONFIG_CLEAN_FILES = Makefile pkgIndex.tcl generic/tls.tcl.h CLEANFILES = @CLEANFILES@ @@ -320,17 +320,10 @@ od -A n -v -t xC < '@srcdir@/library/tls.tcl' > tls.tcl.h.new.1 sed 's@[^0-9A-Fa-f]@@g;s@..@0x&, @g' < tls.tcl.h.new.1 > tls.tcl.h.new.2 rm -f tls.tcl.h.new.1 mv tls.tcl.h.new.2 @srcdir@/generic/tls.tcl.h -# Create default DH parameters -dh_params.h: @srcdir@/gen_dh_params Makefile - sh @srcdir@/gen_dh_params @GEN_DH_PARAMS_ARGS@ > dh_params.h.new - mv dh_params.h.new dh_params.h - -tls.o: dh_params.h - $(srcdir)/manifest.uuid: printf "git-" >$(srcdir)/manifest.uuid (cd $(srcdir); git rev-parse HEAD >>$(srcdir)/manifest.uuid || \ (printf "svn-r" >$(srcdir)/manifest.uuid ; \ svn info --show-item last-changed-revision >>$(srcdir)/manifest.uuid) || \ Index: README.txt ================================================================== --- README.txt +++ README.txt @@ -1,38 +1,126 @@ -TclTLS 1.7.22 -========== +Tool Command Language (TCL) Transport Layer Security (TLS) Extension + +Intro +===== + +This package provides an extension which implements Secure Socket Layer (SSL) +and Transport Layer Security (TLS) over Transmission Control Protocol (TCP) +network communication channels. It utilizes either the OpenSSL or LibreSSL +software library. + +Version 2.0 also provides a cryptography library providing TCL scripts access +to the crypto capabilities of the OpenSSL library. + + +Description +=========== + +This extension works by creating a layered TCL Channel on top of an existing +bi-directional channel created by the TLS socket command. All existing socket +functionality is supported, in addition to several new options. Both client +and server modes are supported. + + +Documentation +============= + +See the doc directory for the full usage documentation. + + +Compatibility +============= + +This package requires TCL 8.5 or later. + +This package is compatible with: +- OpenSSL v1.1.1 or later. See (http://www.openssl.org/ +- LibreSSL (TBD version) + + +Installation +============ + +This package uses the Tcl Extension Architecture (TEA) to build and install on +any supported Unix, Mac, or MS Windows system. Either the OpenSSL or LibreSSL +software libraries must be built and available prior to building TCL TLS. + +UNIX and Linux +-------------- + +The standard TEA config, make and install process is supported. + + $ cd tcltls + $ ./configure --enable-64bit --enable-deterministic --with-builtin-dh-params-size=2048 + $ make + $ make test + $ make install + +The supported configure options include all of the standard TEA configure script +options, plus: + + --disable-tls1 disable TLS1 protocol + --disable-tls1_1 disable TLS1.1 protocol + --disable-tls1_2 disable TLS1.2 protocol + --disable-tls1_3 disable TLS1.3 protocol + --enable-deterministic enable deterministic DH parameters + --enable-ssl-fastpath enable using the underlying file descriptor for talking directly to the SSL library + --enable-hardening enable hardening attempts + --enable-static-ssl enable static linking to the SSL library + --with-builtin-dh-params-size=<bits> specify the size of the built-in, precomputed, DH params + +If either TCL or OpenSSL are installed in non-standard locations, the following +configure options are available. For all options, see ./configure --help. + + --with-tcl=<dir> path to where tclCondig.sh file resides + --with-tclinclude=<dir> directory containing the public Tcl header files + --with-openssl-dir=<dir> path to root directory of OpenSSL or LibreSSL installation + --with-openssl-includedir=<dir> path to include directory of OpenSSL or LibreSSL installation + --with-openssl-libdir=<dir> path to lib directory of OpenSSL or LibreSSL installation + --with-openssl-pkgconfig=<dir> path to root directory of OpenSSL or LibreSSL pkgconfigdir + + +MacOS +----- + +The standard TEA installation process is supported. Use the --with-tcl option +to set the TCL path if the ActiveState or other non-Apple version of TCL is to +be used. + + $ cd tcltls + $ ./configure --with-tcl=/Library/Frameworks/Tcl.framework/ + $ make + $ make test + $ make install + + +Windows +------- -Release Date: Mon Oct 12 15:40:16 CDT 2020 +If installing with MinGW, use the TEA build process. If using MS Visual C +(MSVC), see the win/README.txt file for the installation instructions. + -https://tcltls.rkeene.org/ +Copyrights +========== Original TLS Copyright (C) 1997-2000 Matt Newman <matt@novadigm.com> TLS 1.4.1 Copyright (C) 2000 Ajuba Solutions TLS 1.6 Copyright (C) 2008 ActiveState Software Inc. TLS 1.7 Copyright (C) 2016 Matt Newman, Ajuba Solutions, ActiveState Software Inc, Roy Keene <tcltls@rkeene.org> - -TLS (aka SSL) Channel - can be layered on any bi-directional Tcl_Channel. - -Both client and server-side sockets are possible, and this code should work -on any platform as it uses a generic mechanism for layering on SSL and Tcl. - -Full filevent sematics should also be intact - see tests directory for -blocking and non-blocking examples. - -The current release is TLS 1.6, with binaries built against OpenSSL 0.9.8g. -For best security and function, always compile from source with the latest -official release of OpenSSL (http://www.openssl.org/). - -TLS 1.7 and newer require Tcl 8.4.0+, older versions may be used if older -versions of Tcl need to be used. - -TclTLS requires OpenSSL or LibreSSL in order to be compiled and function. +TLS 1.9-2.0 Copyright (C) 2023 Brian O'Hagan + +Acknowledgments +=============== Non-exclusive credits for TLS are: Original work: Matt Newman @ Novadigm Updates: Jeff Hobbs @ ActiveState Tcl Channel mechanism: Andreas Kupries Impetus/Related work: tclSSL (Colin McCormack, Shared Technology) SSLtcl (Peter Antman) + +License +======= This code is licensed under the same terms as the Tcl Core. ADDED acinclude.m4 Index: acinclude.m4 ================================================================== --- /dev/null +++ acinclude.m4 @@ -0,0 +1,222 @@ +# +# Include the TEA standard macro set +# + +builtin(include,tclconfig/tcl.m4) + +# +# Add here whatever m4 macros you want to define for your package +# + +AC_DEFUN([TCLTLS_SSL_OPENSSL], [ + AC_CHECK_TOOL([PKG_CONFIG], [pkg-config]) + + dnl Disable support for TLS 1.0 protocol + AC_ARG_ENABLE([tls1], AS_HELP_STRING([--disable-tls1], [disable TLS1 protocol]), [ + if test "${enableval}" = "no"; then + AC_DEFINE([NO_TLS1], [1], [Disable TLS1 protocol]) + AC_MSG_CHECKING([for disable TLS1 protocol]) + AC_MSG_RESULT('yes') + fi + ]) + + dnl Disable support for TLS 1.1 protocol + AC_ARG_ENABLE([tls1_1], AS_HELP_STRING([--disable-tls1_1], [disable TLS1.1 protocol]), [ + if test "${enableval}" = "no"; then + AC_DEFINE([NO_TLS1_1], [1], [Disable TLS1.1 protocol]) + AC_MSG_CHECKING([for disable TLS1.1 protocol]) + AC_MSG_RESULT('yes') + fi + ]) + + dnl Disable support for TLS 1.2 protocol + AC_ARG_ENABLE([tls1_2], AS_HELP_STRING([--disable-tls1_2], [disable TLS1.2 protocol]), [ + if test "${enableval}" = "no"; then + AC_DEFINE([NO_TLS1_2], [1], [Disable TLS1.2 protocol]) + AC_MSG_CHECKING([for disable TLS1.2 protocol]) + AC_MSG_RESULT('yes') + fi + ]) + + dnl Disable support for TLS 1.3 protocol + AC_ARG_ENABLE([tls1_3], AS_HELP_STRING([--disable-tls1_3], [disable TLS1.3 protocol]), [ + if test "${enableval}" = "no"; then + AC_DEFINE([NO_TLS1_3], [1], [Disable TLS1.3 protocol]) + AC_MSG_CHECKING([for disable TLS1.3 protocol]) + AC_MSG_RESULT('yes') + fi + ]) + + + dnl Determine if we have been asked to use a fast path if possible + AC_ARG_ENABLE([ssl-fastpath], AS_HELP_STRING([--enable-ssl-fastpath], + [enable using the underlying file descriptor for talking directly to the SSL library]), [ + tcltls_ssl_fastpath="$enableval" + ], [ + tcltls_ssl_fastpath='no' + ]) + if test "$tcltls_ssl_fastpath" = 'yes'; then + AC_DEFINE(TCLTLS_SSL_USE_FASTPATH, [1], [Enable SSL library direct use of the underlying file descriptor]) + fi + AC_MSG_CHECKING([for fast path]) + AC_MSG_RESULT([$tcltls_ssl_fastpath]) + + + dnl Enable hardening + AC_ARG_ENABLE([hardening], AS_HELP_STRING([--enable-hardening], [enable hardening attempts]), [ + tcltls_enable_hardening="$enableval" + ], [ + tcltls_enable_hardening='yes' + ]) + if test "$tcltls_enable_hardening" = 'yes'; then + if test "$GCC" = 'yes' -o "$CC" = 'clang'; then + TEA_ADD_CFLAGS([-fstack-protector-all]) + TEA_ADD_CFLAGS([-fno-strict-overflow]) + AC_DEFINE([_FORTIFY_SOURCE], [2], [Enable fortification]) + fi + fi + AC_MSG_CHECKING([for enable hardening]) + AC_MSG_RESULT([$tcltls_enable_hardening]) + + + dnl Determine if we have been asked to statically link to the SSL library + AC_ARG_ENABLE([static-ssl], AS_HELP_STRING([--enable-static-ssl], [enable static linking to the SSL library]), [ + TCLEXT_TLS_STATIC_SSL="$enableval" + ], [ + TCLEXT_TLS_STATIC_SSL='no' + ]) + AC_MSG_CHECKING([for static linking of openSSL libraries]) + AC_MSG_RESULT([$TCLEXT_TLS_STATIC_SSL]) + + + dnl Set SSL files root path + AC_ARG_WITH([openssl-dir], + AS_HELP_STRING([--with-openssl-dir=<dir>], + [path to root directory of OpenSSL or LibreSSL installation] + ), [ + openssldir="$withval" + ], [ + openssldir='' + ] + ) + + dnl Set SSL include files path + AC_ARG_WITH([openssl-includedir], + AS_HELP_STRING([--with-openssl-includedir=<dir>], + [path to include directory of OpenSSL or LibreSSL installation] + ), [ + opensslincludedir="$withval" + ], [ + if test -n "$openssldir"; then + opensslincludedir="$openssldir/include/openssl" + else + opensslincludedir='' + fi + ] + ) + AC_MSG_CHECKING([for OpenSSL include directory]) + AC_MSG_RESULT($opensslincludedir) + + dnl Set SSL include vars + if test -n "$opensslincludedir"; then + if test -f "$opensslincludedir/ssl.h"; then + TCLTLS_SSL_CFLAGS="-I$opensslincludedir" + TCLTLS_SSL_INCLUDES="-I$opensslincludedir" + else + AC_MSG_ERROR([Unable to locate ssl.h]) + fi + else + TCLTLS_SSL_CFLAGS="-I$(includedir)/openssl" + TCLTLS_SSL_INCLUDES="-I$(includedir)/openssl" + fi + + dnl Set SSL lib files path + AC_ARG_WITH([openssl-libdir], + AS_HELP_STRING([--with-openssl-libdir=<dir>], + [path to lib directory of OpenSSL or LibreSSL installation] + ), [ + openssllibdir="$withval" + ], [ + if test -n "$openssldir"; then + if test "$do64bit" == 'yes'; then + openssllibdir="$openssldir/lib64" + else + openssllibdir="$openssldir/lib" + fi + else + openssllibdir='' + fi + ] + ) + AC_MSG_CHECKING([for OpenSSL lib directory]) + AC_MSG_RESULT($openssllibdir) + + dnl Set SSL lib vars + if test -n "$openssllibdir"; then + if test -f "$openssllibdir/libssl${SHLIB_SUFFIX}"; then + if test "${TCLEXT_TLS_STATIC_SSL}" == 'no'; then + TCLTLS_SSL_LIBS="-L$openssllibdir -lcrypto -lssl" + else + # Linux and Solaris + TCLTLS_SSL_LIBS="-Wl,-Bstatic `$PKG_CONFIG --static --libs crypto ssl` -Wl,-Bdynamic" + # HPUX + # -Wl,-a,archive ... -Wl,-a,shared_archive + fi + else + AC_MSG_ERROR([Unable to locate libssl${SHLIB_SUFFIX}]) + fi + else + TCLTLS_SSL_LIBS="-lcrypto -lssl" + fi + + + dnl Include config variables in --help list and make available to be substituted via AC_SUBST. + AC_ARG_VAR([TCLTLS_SSL_CFLAGS], [C compiler flags for OpenSSL or LibreSSL]) + AC_ARG_VAR([TCLTLS_SSL_INCLUDES], [C compiler include paths for OpenSSL or LibreSSL]) + AC_ARG_VAR([TCLTLS_SSL_LIBS], [libraries to pass to the linker for OpenSSL or LibreSSL]) + + + dnl Set location of pkgconfig files + AC_ARG_WITH([openssl-pkgconfig], + AS_HELP_STRING([--with-openssl-pkgconfig=<dir>], + [path to root directory of OpenSSL or LibreSSL pkgconfigdir] + ), [ + opensslpkgconfigdir="$withval" + ], [ + opensslpkgconfigdir='' + ] + ) + AC_MSG_CHECKING([for OpenSSL pkgconfig]) + AC_MSG_RESULT($opensslpkgconfigdir) + + + # Use Package Config tool to get config + pkgConfigExtraArgs='' + if test "${SHARED_BUILD}" == 0 -o "$TCLEXT_TLS_STATIC_SSL" = 'yes'; then + pkgConfigExtraArgs='--static' + fi + + dnl Use pkg-config to find the libraries + if test -n "${PKG_CONFIG}"; then + dnl Temporarily update PKG_CONFIG_PATH + PKG_CONFIG_PATH_SAVE="${PKG_CONFIG_PATH}" + if test -n "${opensslpkgconfigdir}"; then + if ! test -f "${opensslpkgconfigdir}/openssl.pc"; then + AC_MSG_ERROR([Unable to locate ${opensslpkgconfigdir}/openssl.pc]) + fi + + PKG_CONFIG_PATH="${opensslpkgconfigdir}${PATH_SEPARATOR}${PKG_CONFIG_PATH}" + export PKG_CONFIG_PATH + fi + if test -z "$TCLTLS_SSL_LIBS"; then + TCLTLS_SSL_LIBS="`"${PKG_CONFIG}" openssl --libs $pkgConfigExtraArgs`" || AC_MSG_ERROR([Unable to get OpenSSL Configuration]) + fi + if test -z "$TCLTLS_SSL_CFLAGS"; then + TCLTLS_SSL_CFLAGS="`"${PKG_CONFIG}" openssl --cflags-only-other $pkgConfigExtraArgs`" || AC_MSG_ERROR([Unable to get OpenSSL Configuration]) + fi + if test -z "$TCLTLS_SSL_INCLUDES"; then + TCLTLS_SSL_INCLUDES="`"${PKG_CONFIG}" openssl --cflags-only-I $pkgConfigExtraArgs`" || AC_MSG_ERROR([Unable to get OpenSSL Configuration]) + fi + PKG_CONFIG_PATH="${PKG_CONFIG_PATH_SAVE}" + fi +]) ADDED doc/tls.html Index: doc/tls.html ================================================================== --- /dev/null +++ doc/tls.html @@ -0,0 +1,826 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta http-equiv="Content-Type" +content="text/html; charset=iso-8859-1"> +<meta name="Copyright" content="1999 Matt Newman / 2004 Starfish Systems"> +<title>TLS (SSL) TCL Commands</title> +<link rel="stylesheet" href="docs.css" type="text/css" media="all"> +</head> + +<body class="vsc-initialized"> + +<h2>Tcl Tls Extension Documentation</h2> + +<dl> + <dd><a href="#NAME">NAME</a> + <dl> + <dd><b>tls</b> - binding to <b>OpenSSL</b> library + for socket and I/O channel communications.</dd> + </dl> + </dd> + <dd><a href="#SYNOPSIS">SYNOPSIS</a> </dd> + <dd><dl> + <dd><b>package require Tcl</b> <em>?<b>8.5</b>?</em></dd> + <dd><b>package require tls</b></dd> + <dt> </dt> + <dd><b>tls::init</b> <em>?options?</em> </dd> + <dd><b>tls::socket</b> <em>?options? host port</em></dd> + <dd><b>tls::socket</b> <em>?-server command? ?options? port</em></dd> + <dd><b>tls::handshake</b> <em> channel</em></dd> + <dd><b>tls::status</b> <em>?-local? channel</em></dd> + <dd><b>tls::connection</b> <em>channel</em></dd> + <dd><b>tls::import</b> <em>channel ?options?</em></dd> + <dd><b>tls::unimport</b> <em>channel</em></dd> + <dt> </dt> + <dd><b>tls::protocols</b></dd> + <dd><b>tls::version</b></dd> + </dl> + </dd> + <dd><a href="#COMMANDS">COMMANDS</a></dd> + <dd><a href="#CALLBACK OPTIONS">CALLBACK OPTIONS</a></dd> + <dd><a href="#HTTPS EXAMPLE">HTTPS EXAMPLE</a></dd> + <dd><a href="#SEE ALSO">SPECIAL CONSIDERATIONS</a></dd> + <dd><a href="#SEE ALSO">SEE ALSO</a></dd> +</dl> + +<hr> + +<h3><a name="NAME">NAME</a></h3> + +<p><strong>tls</strong> - binding to <strong>OpenSSL</strong> library +for socket and I/O channel communications.</p> + +<h3><a name="SYNOPSIS">SYNOPSIS</a></h3> + +<p><b>package require Tcl</b> <em>?<b>8.5</b>?</em><br> +<b>package require tls</b><br> +<br> +<a href="#tls::init"><b>tls::init</b> <i>?options?</i></a><br> +<a href="#tls::socket"><b>tls::socket</b> <i>?options? host port</i><br> +<a href="#tls::socket"><b>tls::socket</b> <i>?-server command? ?options? port</i></a><br> +<a href="#tls::status"><b>tls::status</b> <i>?-local? channel</i></a><br> +<a href="#tls::connection"><b>tls::connection</b> <i>channel</i></a><br> +<a href="#tls::handshake"><b>tls::handshake</b> <i>channel</i></a><br> +<a href="#tls::import"><b>tls::import</b> <i>channel ?options?</i></a><br> +<a href="#tls::unimport"><b>tls::unimport</b> <i>channel</i></a><br> +<br> +<a href="#tls::protocols"><b>tls::protocols</b></a><br> +<a href="#tls::version"><b>tls::version</b></a><br> +</p> + +<h3><a name="DESCRIPTION">DESCRIPTION</a></h3> + +<p>This extension provides TCL script access to secure socket communications +using the Transport Layer Security (TLS) protocol. It provides a generic +binding to <a href="http://www.openssl.org/">OpenSSL</a>, utilizing the +<strong>Tcl_StackChannel</strong> API in Tcl 8.4 and higher. +These sockets behave exactly the same as channels created using the built-in +<strong>socket</strong> command, along with additional options for controlling +the SSL session. +</p> + +<h3><a name="COMMANDS">COMMANDS</a></h3> + +<p>Typically one would use the <strong>tls::socket </strong>command +which provides compatibility with the native Tcl <strong>socket</strong> +command. In such cases <strong>tls::import</strong> should not be +used directly.</p> + +<dl> + <dt><a name="tls::init"><b>tls::init </b><i>?options?</i></a></dt> + <dd>Optional function to set the default options used by + <strong>tls::socket</strong>. If you call <strong>tls::import</strong> + directly this routine has no effect. Any of the options + that <strong>tls::socket</strong> accepts can be set + using this command, though you should limit your options + to only TLS related ones.</dd> + <dt> </dt> + <dt><a name="tls::socket"><b>tls::socket </b><em>?options? + host port</em></a></dt> + <dt><b>tls::socket</b><em> ?-server command? ?options? port</em></dt> + <dd>This is a helper function that utilizes the underlying + commands (<strong>tls::import</strong>). It behaves + exactly the same as the native Tcl <strong>socket</strong> + command except the options can also include any of the + applicable <a href="#tls::import"><strong>tls:import</strong></a> + options with one additional option:</dd> +<blockquote> + <dl> + <dt><strong>-autoservername</strong> <em>bool</em></dt> + <dd>Automatically set the -servername argument to the <em>host</em> + argument (default is <em>false</em>).</dd> + </dl> +</blockquote> + + <dt><a name="tls::import"><b>tls::import </b><i>channel + ?options?</i></a></dt> + <dd>Add SSL/TLS encryption to a regular Tcl channel. It need + not be a socket, but must provide bi-directional flow. Also + set session parameters for SSL handshake.</dd> + +<blockquote> + <dl> + <dt><strong>-alpn</strong> <em>list</em></dt> + <dd>List of protocols to offer during Application-Layer + Protocol Negotiation (ALPN). For example: <em>h2</em> and + <em>http/1.1</em>, but not <em>h3</em> or <em>quic</em>.</dd> + <dt><strong>-cadir</strong> <em>dir</em></dt> + <dd>Set the CA certificates path. The default directory is platform + specific and can be set at compile time. This can be overridden + via the <b>SSL_CERT_DIR</b> environment variable.</dd> + <dt><strong>-cafile </strong><em>filename</em></dt> + <dd>Set the certificate authority (CA) certificates file. The default + is the cert.pem file in the OpsnSSL directory. This can also be + overridden via the <b>SSL_CERT_FILE</b> environment variable.</dd> + <dt><strong>-certfile</strong> <em>filename</em></dt> + <dd>Specify the filename with the certificate to use.</dd> + <dt><strong>-cert</strong> <em>filename</em></dt> + <dd>Specify the contents of a certificate to use, as a DER + encoded binary value (X.509 DER).</dd> + <dt><strong>-cipher</strong> <em>string</em></dt> + <dd>List of ciphers to use. String is a colon (":") separated list + of ciphers. Ciphers can be combined + using the <b>+</b> character. Prefixes can be used to permanently + remove ("!"), delete ("-"), or move a cypher to the end of + the list ("+"). Keywords <b>@STRENGTH</b> (sort by algorithm + key length), <b>@SECLEVEL=</b><i>n</i> (set security level to + n), and <b>DEFAULT</b> (use default cipher list, at start only) + can also be specified. See OpenSSL documentation for the full + list of valid values. (TLS 1.2 and earlier only)</dd> + <dt><strong>-ciphersuites</strong> <em>string</em></dt> + <dd>List of cipher suites to use. String is a colon (":") + separated list of cipher suite names. (TLS 1.3 only)</dd> + <dt><strong>-command</strong> <em>callback</em></dt> + <dd>Callback command to invoke at several points during the handshake. + This is used to pass errors and tracing information, and + it can allow Tcl scripts to perform their own certificate + validation in place of the default validation provided by + OpenSSL. See <a href="#CALLBACK OPTIONS">CALLBACK OPTIONS</a> + for further discussion.</dd> + <dt><strong>-dhparams </strong><em>filename</em></dt> + <dd>Specify the Diffie-Hellman parameters file.</dd> + <dt><strong>-keyfile</strong> <em>filename</em></dt> + <dd>Specify the private key file. (default is + value of -certfile)</dd> + <dt><strong>-key</strong> <em>filename</em></dt> + <dd>Specify the private key to use as a DER encoded value (PKCS#1 DER)</dd> + <dt><strong>-model</strong> <em>channel</em></dt> + <dd>Force this channel to share the same <em><strong>SSL_CTX</strong></em> + structure as the specified <em>channel</em>, and + therefore share callbacks etc.</dd> + <dt><strong>-password</strong> <em>callback</em></dt> + <dd>Callback command to invoke when OpenSSL needs to obtain a password. + Typically used to unlock the private key of a certificate. The + callback should return a string which represents the password + to be used. See <a href="#CALLBACK OPTIONS">CALLBACK OPTIONS</a> + for further discussion.</dd> + <dt><strong>-post_handshake</strong> <em>bool</em></dt> + <dd>Allow post-handshake ticket updates.</dd> + <dt><strong>-request </strong><em>bool</em></dt> + <dd>Request a certificate from peer during SSL handshake. + (default is <em>true</em>)</dd> + <dt><strong>-require</strong> <em>bool</em></dt> + <dd>Require a valid certificate from peer during SSL handshake. + If this is set to true, then <strong>-request</strong> must + also be set to true and a either a -cadir, -cafile, or platform + default must be provided in order to validate against. + (default is <em>false</em>)</dd> + <dt><strong>-security_level</strong> <em>integer</em></dt> + <dd>Set security level. Must be 0 to 5. The security level affects + the cipher suite encryption algorithms, supported ECC curves, + supported signature algorithms, DH parameter sizes, certificate + key sizes and signature algorithms. The default is 1. + Level 3 and higher disable support for session tickets and only + accept cipher suites that provide forward secrecy.</dd> + <dt><strong>-server</strong> <em>bool</em></dt> + <dd>Set to act as a server and respond with a server handshake when + a client connects and provides a client handshake. + (default is <em>false</em>)</dd> + <dt><strong>-servername</strong> <em>host</em></dt> + <dd>Specify server's hostname. Used to set the TLS 'Server Name + Indication' (SNI) extension. Set to the expected servername + in the server's certificate or one of the subjectAltName + alternates.</dd> + <dt><strong>-session_id</strong> <em>string</em></dt> + <dd>Session id to resume session.</dd> + <dt><strong>-ssl2</strong> <em>bool</em></dt> + <dd>Enable use of SSL v2. (default is <em>false</em>)</dd> + <dt><strong>-ssl3 </strong><em>bool</em></dt> + <dd>Enable use of SSL v3. (default is <em>false</em>)</dd> + <dt>-<strong>tls1</strong> <em>bool</em></dt> + <dd>Enable use of TLS v1. (default is <em>true</em>)</dd> + <dt>-<strong>tls1.1</strong> <em>bool</em></dt> + <dd>Enable use of TLS v1.1 (default is <em>true</em>)</dd> + <dt>-<strong>tls1.2</strong> <em>bool</em></dt> + <dd>Enable use of TLS v1.2 (default is <em>true</em>)</dd> + <dt>-<strong>tls1.3</strong> <em>bool</em></dt> + <dd>Enable use of TLS v1.3 (default is <em>true</em>)</dd> + <dt><strong>-validatecommand</strong> <em>callback</em></dt> + <dd>Callback command to invoke to verify or validate protocol config + parameters during the protocol negotiation phase. See + <a href="#CALLBACK OPTIONS">CALLBACK OPTIONS</a> + for further discussion.</dd> + </dl> +</blockquote> + + <dt><a name="tls::unimport"><b>tls::unimport </b><i>channel</i></a></dt> + <dd>Provided for symmetry to <strong>tls::import</strong>, this + unstacks the encryption of a regular Tcl channel. An error + is thrown if TLS is not the top stacked channel type.</dd> + <dt> </dt> + <dt><a name="tls::handshake"><strong>tls::handshake</strong> + <em>channel</em></a></dt> + <dd>Forces handshake to take place, and returns 0 if + handshake is still in progress (non-blocking), or 1 if + the handshake was successful. If the handshake failed + this routine will throw an error.</dd> + <dt> </dt> + <dt><a name="tls::status"><strong>tls::status</strong> + <em>?</em><b>-local</b><em>? channel</em></a></dt> + <dd>Returns the current status of an SSL channel. The result is a list + of key-value pairs describing the SSL, certificate, and certificate + verification status. If the SSL handshake has not yet completed, + an empty list is returned. If <b>-local</b> is specified, then the + local certificate is used.</dd> +<blockquote> + <b>SSL Status</b> + <dl> + <dt><strong>alpn</strong> <em>protocol</em></dt> + <dd>The protocol selected after Application-Layer Protocol + Negotiation (ALPN).</dd> + <dt><strong>cipher</strong> <em>cipher</em></dt> + <dd>The current cipher in use between for the channel.</dd> + <dt><strong>peername</strong> <em>name</em></dt> + <dd>The peername from the certificate.</dd> + <dt><strong>protocol</strong> <em>version</em></dt> + <dd>The protocol version used for the connection: + SSL2, SSL3, TLS1, TLS1.1, TLS1.2, TLS1.3, or unknown.</dd> + <dt><strong>sbits</strong> <em>n</em></dt> + <dd>The number of bits used for the session key.</dd> + <dt><strong>signatureHashAlgorithm</strong> <em>algorithm</em></dt> + <dd>The signature hash algorithm.</dd> + <dt><strong>signatureType</strong> <em>type</em></dt> + <dd>The signature type value.</dd> + <dt><strong>verifyDepth</strong> <em>n</em></dt> + <dd>Maximum depth for the certificate chain verification. + Default is -1, to check all.</dd> + <dt><strong>verifyMode</strong> <em>list</em></dt> + <dd>List of certificate verification modes.</dd> + <dt><strong>verifyResult</strong> <em>result</em></dt> + <dd>Certificate verification result.</dd> + <dt><strong>ca_names</strong> <em>list</em></dt> + <dd>List of the Certificate Authorities used to create the certificate.</dd> + </dl> +</blockquote> +<blockquote> + <b>Certificate Status</b> + <dl> + <dt><strong>all</strong> <em>string</em></dt> + <dd>Dump of all certificate info.</dd> + + <dt><strong>version</strong> <em>value</em></dt> + <dd>The certificate version.</dd> + <dt><strong>serialNumber</strong> <em>n</em></dt> + <dd>The serial number of the certificate as a hex string.</dd> + <dt><strong>signature</strong> <em>algorithm</em></dt> + <dd>Cipher algorithm used for certificate signature.</dd> + <dt><strong>issuer</strong> <em>dn</em></dt> + <dd>The distinguished name (DN) of the certificate issuer.</dd> + <dt><strong>notBefore</strong> <em>date</em></dt> + <dd>The begin date for the validity of the certificate.</dd> + <dt><strong>notAfter</strong> <em>date</em></dt> + <dd>The expiration date for the certificate.</dd> + <dt><strong>subject</strong> <em>dn</em></dt> + <dd>The distinguished name (DN) of the certificate subject. + Fields include: Common Name (CN), Organization (O), Locality + or City (L), State or Province (S), and Country Name (C).</dd> + <dt><strong>issuerUniqueID</strong> <em>string</em></dt> + <dd>The issuer unique id.</dd> + <dt><strong>subjectUniqueID</strong> <em>string</em></dt> + <dd>The subject unique id.</dd> + + <dt><strong>num_extensions</strong> <em>n</em></dt> + <dd>Number of certificate extensions.</dd> + <dt><strong>extensions</strong> <em>list</em></dt> + <dd>List of certificate extension names.</dd> + <dt><strong>authorityKeyIdentifier</strong> <em>string</em></dt> + <dd>(AKI) Key identifier of the Issuing CA certificate that signed + the SSL certificate as a hex string. This value matches the SKI + value of the Intermediate CA certificate.</dd> + <dt><strong>subjectKeyIdentifier</strong> <em>string</em></dt> + <dd>(SKI) Hash of the public key inside the certificate as a hex + string. Used to identify certificates that contain a particular + public key.</dd> + <dt><strong>subjectAltName</strong> <em>list</em></dt> + <dd>List of all of the alternative domain names, sub domains, + and IP addresses that are secured by the certificate.</dd> + <dt><strong>ocsp</strong> <em>list</em></dt> + <dd>List of all Online Certificate Status Protocol (OCSP) URLs.</dd> + + <dt><strong>certificate</strong> <em>cert</em></dt> + <dd>The PEM encoded certificate.</dd> + + <dt><strong>signatureAlgorithm</strong> <em>algorithm</em></dt> + <dd>Cipher algorithm used for the certificate signature.</dd> + <dt><strong>signatureValue</strong> <em>string</em></dt> + <dd>Certificate signature as a hex string.</dd> + <dt><strong>signatureDigest</strong> <em>version</em></dt> + <dd>Certificate signing digest as a hex string.</dd> + <dt><strong>publicKeyAlgorithm</strong> <em>algorithm</em></dt> + <dd>Certificate signature public key algorithm.</dd> + <dt><strong>publicKey</strong> <em>string</em></dt> + <dd>Certificate signature public key as a hex string.</dd> + <dt><strong>bits</strong> <em>n</em></dt> + <dd>Number of bits used for certificate signature key.</dd> + <dt><strong>self_signed</strong> <em>boolean</em></dt> + <dd>Whether the certificate signature is self signed.</dd> + + <dt><strong>sha1_hash</strong> <em>hash</em></dt> + <dd>The SHA1 hash of the certificate as a hex string.</dd> + <dt><strong>sha256_hash</strong> <em>hash</em></dt> + <dd>The SHA256 hash of the certificate as a hex string.</dd> + </dl> +</blockquote> + + <dt><a name="tls::connection"><strong>tls::connection</strong> + <em>channel</em></a></dt> + <dd>Returns the current connection status of an SSL channel. The + result is a list of key-value pairs describing the connection.</dd> +<blockquote> + <b>SSL Status</b> + <dl> + <dt><strong>state</strong> <em>state</em></dt> + <dd>State of the connection.</dd> + <dt><strong>servername</strong> <em>name</em></dt> + <dd>The name of the connected to server.</dd> + <dt><strong>protocol</strong> <em>version</em></dt> + <dd>The protocol version used for the connection: + SSL2, SSL3, TLS1, TLS1.1, TLS1.2, TLS1.3, or unknown.</dd> + <dt><strong>renegotiation_allowed</strong> <em>boolean</em></dt> + <dd>Whether protocol renegotiation is supported or not.</dd> + <dt><strong>security_level</strong> <em>level</em></dt> + <dd>The security level used for selection of ciphers, key size, etc.</dd> + <dt><strong>session_reused</strong> <em>boolean</em></dt> + <dd>Whether the session has been reused or not.</dd> + <dt><strong>is_server</strong> <em>boolean</em></dt> + <dd>Whether the connection is configured as a server (1) or client (0).</dd> + <dt><strong>compression</strong> <em>mode</em></dt> + <dd>Compression method.</dd> + <dt><strong>expansion</strong> <em>mode</em></dt> + <dd>Expansion method.</dd> + <dt><strong>caList</strong> <em>list</em></dt> + <dd>List of Certificate Authorities (CA) for X.509 certificate.</dd> + </dl> +</blockquote> +<blockquote> + <b>Cipher Info</b> + <dl> + <dt><strong>cipher</strong> <em>cipher</em></dt> + <dd>The current cipher in use for the connection.</dd> + <dt><strong>standard_name</strong> <em>name</em></dt> + <dd>The standard RFC name of cipher.</dd> + <dt><strong>algorithm_bits</strong> <em>n</em></dt> + <dd>The number of processed bits used for cipher.</dd> + <dt><strong>secret_bits</strong> <em>n</em></dt> + <dd>The number of secret bits used for cipher.</dd> + <dt><strong>min_version</strong> <em>version</em></dt> + <dd>The minimum protocol version for cipher.</dd> + <dt><strong>cipher_is_aead</strong> <em>boolean</em></dt> + <dd>Whether the cipher is Authenticated Encryption with + Associated Data (AEAD).</dd> + <dt><strong>cipher_id</strong> <em>id</em></dt> + <dd>The OpenSSL cipher id.</dd> + <dt><strong>description</strong> <em>string</em></dt> + <dd>A text description of the cipher.</dd> + <dt><strong>handshake_digest</strong> <em>boolean</em></dt> + <dd>Digest used during handshake.</dd> + </dl> +</blockquote> +<blockquote> + <b>Session Info</b> + <dl> + <dt><strong>alpn</strong> <em>protocol</em></dt> + <dd>The protocol selected after Application-Layer Protocol + Negotiation (ALPN).</dd> + <dt><strong>resumable</strong> <em>boolean</em></dt> + <dd>Whether the session can be resumed or not.</dd> + <dt><strong>start_time</strong> <em>seconds</em></dt> + <dd>Time since session started in seconds since epoch.</dd> + <dt><strong>timeout</strong> <em>seconds</em></dt> + <dd>Max duration of session in seconds before time-out.</dd> + <dt><strong>lifetime</strong> <em>seconds</em></dt> + <dd>Session ticket lifetime hint in seconds.</dd> + <dt><strong>session_id</strong> <em>binary_string</em></dt> + <dd>Unique session id for use in resuming the session.</dd> + <dt><strong>session_ticket</strong> <em>binary_string</em></dt> + <dd>Unique session ticket for use in resuming the session.</dd> + <dt><strong>ticket_app_data</strong> <em>binary_string</em></dt> + <dd>Unique session ticket application data.</dd> + <dt><strong>master_key</strong> <em>binary_string</em></dt> + <dd>Unique session master key.</dd> + <dt><strong>session_cache_mode</strong> <em>mode</em></dt> + <dd>Server cache mode (client, server, or both).</dd> + </dl> +</blockquote> + + <dt><a name="tls::protocols"><strong>tls::protocols</strong></a></dt> + <dd>Returns a list of the supported protocols. Valid values are: + <b>ssl2</b>, <b>ssl3</b>, <b>tls1</b>, <b>tls1.1</b>, <b>tls1.2</b>, + and <b>tls1.3</b>. Exact list depends on OpenSSL version and + compile time flags.</dd> + + <dt><a name="tls::version"><strong>tls::version</strong></a></dt> + <dd>Returns the OpenSSL version string.</dd> +</dl> + +<h3><a name="CALLBACK OPTIONS">CALLBACK OPTIONS</a></h3> + +<p> +As indicated above, individual channels can be given their own callbacks +to handle intermediate processing by the OpenSSL library, using the +<strong>-command</strong>, <strong>-password</strong>, and +<strong>-validate_command</strong> options passed to either of +<strong>tls::socket</strong> or <strong>tls::import</strong>. +If the callback generates an error, the <b>bgerror</b> command will be +invoked with the error information. +</p> + +<blockquote> +<dl> + + <dt><strong>-command</strong> <em>callback</em></dt> + <dd> + Invokes the specified <em>callback</em> script at several points + during the OpenSSL handshake and use. See below for the possible + arguments passed to the callback script. Values returned from the + callback are ignored. + + <br> + <br> + + <dl> + + <dt> + <strong>error</strong> <em>channelId message</em> + </dt> + <dd> + This form of callback is invoked whenever an error occurs during the + initial connection, handshake, or I/O operations. The <em>message</em> + argument can be from the Tcl_ErrnoMsg, OpenSSL function + <code>ERR_reason_error_string()</code>, or a custom message. + </dd> + + <br> + + <dt> + <strong>info</strong> <em>channelId major minor message type</em> + </dt> + <dd> + This form of callback is invoked by the OpenSSL function + <code>SSL_set_info_callback()</code> during the initial connection + and handshake operations. The <em>type</em> argument is new for + TLS 1.8. The arguments are: + <br> + <ul> + <li>Possible values for <em>major</em> are: + <code>handshake, alert, connect, accept</code>.</li> + <li>Possible values for <em>minor</em> are: + <code>start, done, read, write, loop, exit</code>.</li> + <li>The <em>message</em> argument is a descriptive string which may + be generated either by <code>SSL_state_string_long()</code> or by + <code>SSL_alert_desc_string_long()</code>, depending on the context.</li> + <li>For alerts, the possible values for <em>type</em> are: + <code>warning, fatal, and unknown</code>. For others, + <code>info</code> is used.</li> + </ul> + </dd> + + <dt> + <strong>message</strong> <em>channelId direction version content_type message</em> + </dt> + <dd> + This form of callback is invoked by the OpenSSL function + <code>SSL_set_msg_callback()</code> whenever a message is sent or + received during the initial connection, handshake, or I/O operations. + It is only available when OpenSSL is complied with the + <em>enable-ssl-trace</em> option. Arguments are: <em>direction</em> + is <b>Sent</b> or <b>Received</b>, <em>version</em> is the protocol + version, <em>content_type</em> is the message content type, and + <em>message</em> is more info from the <code>SSL_trace</code> API. + This callback is new for TLS 1.8. + </dd> + <br> + + <dt> + <strong>session</strong> <em>channelId session_id ticket lifetime</em> + </dt> + <dd> + This form of callback is invoked by the OpenSSL function + <code>SSL_CTX_sess_set_new_cb()</code> whenever a new session id is + sent by the server during the initial connection and handshake, but + can also be received later if the <b>-post_handshake</b> option is + used. Arguments are: <em>session_id</em> is the current + session identifier, <em>ticket</em> is the session ticket info, and + <em>lifetime</em> is the the ticket lifetime in seconds. + This callback is new for TLS 1.8. + </dd> + <br> + </dl> + </dd> + + <br> + + <dt><strong>-password</strong> <em>callback</em></dt> + <dd> + Invokes the specified <em>callback</em> script when OpenSSL needs to + obtain a password. See below for the possible arguments passed to + the callback script. See below for valid return values. + + <br> + <br> + + <dl> + + <dt> + <strong>password</strong> <em>rwflag size</em> + </dt> + <dd> + Invoked when loading or storing a PEM certificate with encryption. + Where <em>rwflag</em> is 0 for reading/decryption or 1 for + writing/encryption (can prompt user to confirm) and + <em>size</em> is the max password length in bytes. + The callback should return the password as a string. + Both arguments are new for TLS 1.8. + </dd> + </dd> + + <br> + + + <dt><strong>-validatecommand</strong> <em>callback</em></dt> + <dd> + Invokes the specified <em>callback</em> script during handshake in + order to validate the provided value(s). See below for the possible + arguments passed to the callback script. If not specified, OpenSSL + will accept valid certificates and extensions. + To reject the value and abort the connection, the callback should return 0. + To accept the value and continue the connection, it should return 1. + To reject the value, but continue the connection, it should return 2. + + <br> + <br> + + <dl> + + <dt> + <strong>alpn</strong> <em>channelId protocol match</em> + </dt> + <dd> + For servers, this form of callback is invoked when the client ALPN + extension is received. If <em>match</em> is true, <em>protocol</em> + is the first <b>-alpn</b> option specified protocol common to both + the client and server. If not, the first client specified protocol is + used. It is called after the hello and ALPN callbacks. + This callback is new for TLS 1.8. + </dd> + + <br> + + <dt> + <strong>hello</strong> <em>channelId servername</em> + </dt> + <dd> + For servers, this form of callback is invoked during client hello + message processing. The purpose is so the server can select the + appropriate certificate to present to the client, and to make other + configuration adjustments relevant to that server name and its + configuration. It is called before the SNI and ALPN callbacks. + This callback is new for TLS 1.8. + </dd> + + <br> + + <dt> + <strong>sni</strong> <em>channelId servername</em> + </dt> + <dd> + For servers, this form of callback is invoked when the Server Name + Indication (SNI) extension is received. The <em>servername</em> + argument is the client provided server name in the <b>-servername</b> + option. The purpose is so when a server supports multiple names, the + right certificate can be used. It is called after the hello callback + but before the ALPN callback. + This callback is new for TLS 1.8. + </dd> + + <br> + + <dt> + <strong>verify</strong> <em>channelId depth cert status error</em> + </dt> + <dd> + This form of callback is invoked by OpenSSL when a new certificate + is received from the peer. It allows the client to check the + certificate verification results and choose whether to continue + or not. It is called for each certificate in the certificate chain. + <ul> + <li>The <em>depth</em> argument is the integer depth of the + certificate in the certificate chain, where 0 is the peer certificate + and higher values going up to the Certificate Authority (CA).</li> + <li>The <em>cert</em> argument is a list of key-value pairs similar + to those returned by + <a href="#tls::status"><strong>tls::status</strong></a>.</li> + <li>The <em>status</em> argument is the boolean validity of the + current certificate where 0 is invalid and 1 is valid.</li> + <li>The <em>error</em> argument is the error message, if any, generated + by <code>X509_STORE_CTX_get_error()</code>.</li> + </ul> + </dd> + <br> + </dl> + </dd> +</dl> +</blockquote> + +<p> +Reference implementations of these callbacks are provided in the +distribution as <strong>tls::callback</strong>, <strong>tls::password</strong>, +and <strong>tls::validate_command</strong> respectively. Note that these are +<em>sample</em> implementations only. In a more realistic deployment +you would specify your own callback scripts on each TLS channel using the +<strong>-command</strong>, <strong>-password</strong>, and <strong>-validate_command</strong> options. +</p> + +<p> +The default behavior when the <strong>-command</strong> and <strong>-validate_command</strong> +options are not specified is for TLS to process the associated library callbacks +internally. The default behavior when the <strong>-password</strong> option is not +specified is for TLS to process the associated library callbacks by attempting +to call <strong>tls::password</strong>. +The difference between these two behaviors is a consequence of maintaining +compatibility with earlier implementations. +</p> + +<p> +<em> +The use of the reference callbacks <strong>tls::callback</strong>, +<strong>tls::password</strong>, and <strong>tls::validate_command</strong> +is not recommended. They may be removed from future releases. +</em> +</p> + +<h3><a name="DEBUG">DEBUG</a></h3> + +TLS key logging can be enabled by setting the environment variable +<b>SSLKEYLOGFILE</b> to the name of the file to log to. Then whenever TLS +key material is generated or received it will be logged to the file. This +is useful for logging key data for network logging tools to use to +decrypt the data. + +<p> +The <strong>tls::debug</strong> variable provides some additional +control over these reference callbacks. Its value is zero by default. +Higher values produce more diagnostic output, and will also force the +verify method in <strong>tls::callback</strong> to accept the +certificate, even when it is invalid if the <b>tls::validate_command</b> +callback is used for the <b>-validatecommand</b> option. +</p> + +<p> +<em> +The use of the variable <strong>tls::debug</strong> is not recommended. +It may be removed from future releases. +</em> +</p> + +<h4><a name="DEBUG_EXAMPLES">Debug Examples</a></h4> + +<p>These examples use the default Unix platform SSL certificates. For standard +installations, -cadir and -cafile should not be needed. If your certificates +are in non-standard locations, update -cadir or use -cafile as needed.</p> +<br> +Example #1: Use HTTP package + + +<pre><code> +package require http +package require tls +set url "https://www.tcl.tk/" + +http::register https 443 [list ::tls::socket -autoservername true -require true -cadir /etc/ssl/certs \ + -command ::tls::callback -password ::tls::password -validatecommand ::tls::validate_command] + +# Check for error +set token [http::geturl $url] +if {[http::status $token] ne "ok"} { + puts [format "Error %s" [http::status $token]] +} + +# Get web page +set data [http::data $token] +puts [string length $data] + +# Cleanup +::http::cleanup $token +</code></pre> + +Example #2: Use raw socket +<pre><code> +package require tls + +set url "www.tcl-lang.org" +set port 443 + +set ch [tls::socket -autoservername 1 -servername $url -request 1 -require 1 \ + -alpn {http/1.1} -cadir /etc/ssl/certs -command ::tls::callback \ + -password ::tls::password -validatecommand ::tls::validate_command $url $port] +chan configure $ch -buffersize 65536 +tls::handshake $ch + +puts $ch "GET / HTTP/1.1" +flush $ch +after 500 +set data [read $ch] + +array set status [tls::status $ch] +array set conn [tls::connection $ch] +array set chan [chan configure $ch] +close $ch +parray status +parray conn +parray chan +</code></pre> + + +<h3><a name="HTTPS EXAMPLE">HTTPS EXAMPLE</a></h3> + +<p>These examples use the default Unix platform SSL certificates. For standard +installations, -cadir and -cafile should not be needed. If your certificates +are in non-standard locations, update -cadir or use -cafile as needed.</p> + +Example #1: Get web page + +<pre><code> +package require http +package require tls +set url "https://www.tcl.tk/" + +http::register https 443 [list ::tls::socket -autoservername true -require true -cadir /etc/ssl/certs] + +# Check for error +set token [http::geturl $url] +if {[http::status $token] ne "ok"} { + puts [format "Error %s" [http::status $token]] +} + +# Get web page +set data [http::data $token] +puts $data + +# Cleanup +::http::cleanup $token +</code></pre> + +Example #2: Download file + +<pre><code> +package require http +package require tls + +set url "https://wiki.tcl-lang.org/sitemap.xml" +set filename [file tail $url] + +http::register https 443 [list ::tls::socket -autoservername true -require true -cadir /etc/ssl/certs] + +# Get file +set ch [open $filename wb] +set token [::http::geturl $url -blocksize 65536 -channel $ch] + +# Cleanup +close $ch +::http::cleanup $token +</code></pre> + +<h3><a name="SPECIAL CONSIDERATIONS">SPECIAL CONSIDERATIONS</a></h3> + +<p>The capabilities of this package can vary enormously based upon how the +linked to OpenSSL library was configured and built. New versions may obsolete +older protocol versions, add or remove ciphers, change default values, etc. +Use the <strong>tls::protocols</strong> commands to obtain the supported +protocol versions.</p> + +<h3><a name="SEE ALSO">SEE ALSO</a></h3> + +<p><strong>socket</strong>, <strong>fileevent</strong>, <strong>http</strong>, +<a href="http://www.openssl.org/"><strong>OpenSSL</strong></a></p> + +<hr> + +<pre> +Copyright © 1999 Matt Newman. +Copyright © 2004 Starfish Systems. +Copyright © 2023 Brian O'Hagan. +</pre> +</body> +</html> Index: generic/tls.c ================================================================== --- generic/tls.c +++ generic/tls.c @@ -2,10 +2,11 @@ * Copyright (C) 1997-1999 Matt Newman <matt@novadigm.com> * some modifications: * Copyright (C) 2000 Ajuba Solutions * Copyright (C) 2002 ActiveState Corporation * Copyright (C) 2004 Starfish Systems + * Copyright (C) 2023 Brian O'Hagan * * TLS (aka SSL) Channel - can be layered on any bi-directional * Tcl_Channel (Note: Requires Trf Core Patch) * * This was built (almost) from scratch based upon observation of @@ -21,39 +22,36 @@ * */ #include "tlsInt.h" #include "tclOpts.h" +#include <stdio.h> #include <stdlib.h> +#include <openssl/rsa.h> +#include <openssl/safestack.h> + +/* Min OpenSSL version */ +#if OPENSSL_VERSION_NUMBER < 0x10101000L +#error "Only OpenSSL v1.1.1 or later is supported" +#endif /* * External functions */ /* * Forward declarations */ -#define F2N( key, dsp) \ +#define F2N(key, dsp) \ (((key) == NULL) ? (char *) NULL : \ Tcl_TranslateFileName(interp, (key), (dsp))) -#define REASON() ERR_reason_error_string(ERR_get_error()) - -static void InfoCallback(const SSL *ssl, int where, int ret); - -static Tcl_ObjCmdProc CiphersObjCmd; -static Tcl_ObjCmdProc HandshakeObjCmd; -static Tcl_ObjCmdProc ImportObjCmd; -static Tcl_ObjCmdProc StatusObjCmd; -static Tcl_ObjCmdProc VersionObjCmd; -static Tcl_ObjCmdProc MiscObjCmd; -static Tcl_ObjCmdProc UnimportObjCmd; static SSL_CTX *CTX_Init(State *statePtr, int isServer, int proto, char *key, - char *certfile, unsigned char *key_asn1, unsigned char *cert_asn1, - int key_asn1_len, int cert_asn1_len, char *CAdir, char *CAfile, - char *ciphers, char *DHparams); + char *certfile, unsigned char *key_asn1, unsigned char *cert_asn1, + int key_asn1_len, int cert_asn1_len, char *CApath, char *CAfile, + char *ciphers, char *ciphersuites, int level, char *DHparams); static int TlsLibInit(int uninitialize); #define TLS_PROTO_SSL2 0x01 #define TLS_PROTO_SSL3 0x02 @@ -61,36 +59,11 @@ #define TLS_PROTO_TLS1_1 0x08 #define TLS_PROTO_TLS1_2 0x10 #define TLS_PROTO_TLS1_3 0x20 #define ENABLED(flag, mask) (((flag) & (mask)) == (mask)) -/* - * Static data structures - */ - -#ifndef OPENSSL_NO_DH -#include "dh_params.h" -#endif - -/* - * We lose the tcl password callback when we use the RSA BSAFE SSL-C 1.1.2 - * libraries instead of the current OpenSSL libraries. - */ - -#ifdef BSAFE -#define PRE_OPENSSL_0_9_4 1 -#endif - -/* - * Pre OpenSSL 0.9.4 Compat - */ - -#ifndef STACK_OF -#define STACK_OF(x) STACK -#define sk_SSL_CIPHER_num(sk) sk_num((sk)) -#define sk_SSL_CIPHER_value( sk, index) (SSL_CIPHER*)sk_value((sk), (index)) -#endif +#define SSLKEYLOGFILE "SSLKEYLOGFILE" /* * Thread-Safe TLS Code */ @@ -98,90 +71,106 @@ #define OPENSSL_THREAD_DEFINES #include <openssl/opensslconf.h> #ifdef OPENSSL_THREADS #include <openssl/crypto.h> +#include <openssl/ssl.h> /* * Threaded operation requires locking callbacks * Based from /crypto/cryptlib.c of OpenSSL and NSOpenSSL. */ static Tcl_Mutex *locks = NULL; static int locksCount = 0; static Tcl_Mutex init_mx; - -void CryptoThreadLockCallback(int mode, int n, const char *file, int line) { - - if (mode & CRYPTO_LOCK) { - /* This debugging is turned off by default -- it's too noisy. */ - /* dprintf("Called to lock (n=%i of %i)", n, locksCount); */ - Tcl_MutexLock(&locks[n]); - } else { - /* dprintf("Called to unlock (n=%i of %i)", n, locksCount); */ - Tcl_MutexUnlock(&locks[n]); - } - - /* dprintf("Returning"); */ - - return; - file = file; - line = line; -} - -unsigned long CryptoThreadIdCallback(void) { - unsigned long ret; - - dprintf("Called"); - - ret = (unsigned long) Tcl_GetCurrentThread(); - - dprintf("Returning %lu", ret); - - return(ret); -} #endif /* OPENSSL_THREADS */ #endif /* TCL_THREADS */ + +/********************/ +/* Callbacks */ +/********************/ + +/* + *------------------------------------------------------------------- + * + * Eval Callback Command -- + * + * Eval callback command and catch any errors + * + * Results: + * 0 = Command returned fail or eval returned TCL_ERROR + * 1 = Command returned success or eval returned TCL_OK + * + * Side effects: + * Evaluates callback command + * + *------------------------------------------------------------------- + */ +static int +EvalCallback(Tcl_Interp *interp, State *statePtr, Tcl_Obj *cmdPtr) { + int code, ok = 0; + + dprintf("Called"); + + Tcl_Preserve((ClientData) interp); + Tcl_Preserve((ClientData) statePtr); + + /* Eval callback with success for ok or return value 1, fail for error or return value 0 */ + Tcl_ResetResult(interp); + code = Tcl_EvalObjEx(interp, cmdPtr, TCL_EVAL_GLOBAL); + dprintf("EvalCallback: %d", code); + if (code == TCL_OK) { + /* Check result for return value */ + Tcl_Obj *result = Tcl_GetObjResult(interp); + if (result == NULL || Tcl_GetIntFromObj(interp, result, &ok) != TCL_OK) { + ok = 1; + } + dprintf("Result: %d", ok); + } else { + /* Error - reject the certificate */ + dprintf("Tcl_BackgroundError"); +#if (TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION < 6) + Tcl_BackgroundError(interp); +#else + Tcl_BackgroundException(interp, code); +#endif + } + + Tcl_Release((ClientData) statePtr); + Tcl_Release((ClientData) interp); + return ok; +} /* *------------------------------------------------------------------- * * InfoCallback -- * - * monitors SSL connection process + * Monitors SSL connection process * * Results: * None * * Side effects: * Calls callback (if defined) + * *------------------------------------------------------------------- */ static void -InfoCallback(const SSL *ssl, int where, int ret) -{ +InfoCallback(const SSL *ssl, int where, int ret) { State *statePtr = (State*)SSL_get_app_data((SSL *)ssl); + Tcl_Interp *interp = statePtr->interp; Tcl_Obj *cmdPtr; - const char *major, *minor; + char *major; char *minor; dprintf("Called"); if (statePtr->callback == (Tcl_Obj*)NULL) return; - cmdPtr = Tcl_DuplicateObj(statePtr->callback); - -#if 0 - if (where & SSL_CB_ALERT) { - sev = SSL_alert_type_string_long(ret); - if (strcmp( sev, "fatal")==0) { /* Map to error */ - Tls_Error(statePtr, SSL_ERROR(ssl, 0)); - return; - } - } -#endif if (where & SSL_CB_HANDSHAKE_START) { major = "handshake"; minor = "start"; } else if (where & SSL_CB_HANDSHAKE_DONE) { major = "handshake"; @@ -197,274 +186,751 @@ else if (where & SSL_CB_LOOP) minor = "loop"; else if (where & SSL_CB_EXIT) minor = "exit"; else minor = "unknown"; } - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( "info", -1)); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( Tcl_GetChannelName(statePtr->self), -1) ); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( major, -1) ); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( minor, -1) ); - - if (where & (SSL_CB_LOOP|SSL_CB_EXIT)) { - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( SSL_state_string_long(ssl), -1) ); - } else if (where & SSL_CB_ALERT) { - const char *cp = (char *) SSL_alert_desc_string_long(ret); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( cp, -1) ); + /* Create command to eval with fn, chan, major, minor, message, and type args */ + cmdPtr = Tcl_DuplicateObj(statePtr->callback); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("info", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(major, -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(minor, -1)); + + if (where & SSL_CB_ALERT) { + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(SSL_alert_desc_string_long(ret), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(SSL_alert_type_string_long(ret), -1)); } else { - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( SSL_state_string_long(ssl), -1) ); - } - Tcl_Preserve( (ClientData) statePtr->interp); - Tcl_Preserve( (ClientData) statePtr); - - Tcl_IncrRefCount( cmdPtr); - (void) Tcl_EvalObjEx(statePtr->interp, cmdPtr, TCL_EVAL_GLOBAL); - Tcl_DecrRefCount( cmdPtr); - - Tcl_Release( (ClientData) statePtr); - Tcl_Release( (ClientData) statePtr->interp); - -} + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(SSL_state_string_long(ssl), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("info", -1)); + } + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + EvalCallback(interp, statePtr, cmdPtr); + Tcl_DecrRefCount(cmdPtr); +} + +/* + *------------------------------------------------------------------- + * + * MessageCallback -- + * + * Monitors SSL protocol messages + * + * Results: + * None + * + * Side effects: + * Calls callback (if defined) + * + *------------------------------------------------------------------- + */ +#ifndef OPENSSL_NO_SSL_TRACE +static void +MessageCallback(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) { + State *statePtr = (State*)arg; + Tcl_Interp *interp = statePtr->interp; + Tcl_Obj *cmdPtr; + char *ver, *type; + BIO *bio; + char buffer[15000]; + buffer[0] = 0; + + dprintf("Called"); + + if (statePtr->callback == (Tcl_Obj*)NULL) + return; + + switch(version) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(NO_SSL2) && !defined(OPENSSL_NO_SSL2) + case SSL2_VERSION: + ver = "SSLv2"; + break; +#endif +#if !defined(NO_SSL3) && !defined(OPENSSL_NO_SSL3) + case SSL3_VERSION: + ver = "SSLv3"; + break; +#endif + case TLS1_VERSION: + ver = "TLSv1"; + break; + case TLS1_1_VERSION: + ver = "TLSv1.1"; + break; + case TLS1_2_VERSION: + ver = "TLSv1.2"; + break; + case TLS1_3_VERSION: + ver = "TLSv1.3"; + break; + case 0: + ver = "none"; + break; + default: + ver = "unknown"; + break; + } + + switch (content_type) { + case SSL3_RT_HEADER: + type = "Header"; + break; + case SSL3_RT_INNER_CONTENT_TYPE: + type = "Inner Content Type"; + break; + case SSL3_RT_CHANGE_CIPHER_SPEC: + type = "Change Cipher"; + break; + case SSL3_RT_ALERT: + type = "Alert"; + break; + case SSL3_RT_HANDSHAKE: + type = "Handshake"; + break; + case SSL3_RT_APPLICATION_DATA: + type = "App Data"; + break; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + case DTLS1_RT_HEARTBEAT: + type = "Heartbeat"; + break; +#endif + default: + type = "unknown"; + } + + /* Needs compile time option "enable-ssl-trace". */ + if ((bio = BIO_new(BIO_s_mem())) != NULL) { + int n; + SSL_trace(write_p, version, content_type, buf, len, ssl, (void *)bio); + n = BIO_read(bio, buffer, BIO_pending(bio) < 15000 ? BIO_pending(bio) : 14999); + n = (n<0) ? 0 : n; + buffer[n] = 0; + (void)BIO_flush(bio); + BIO_free(bio); + } + + /* Create command to eval with fn, chan, direction, version, type, and message args */ + cmdPtr = Tcl_DuplicateObj(statePtr->callback); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("message", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(write_p ? "Sent" : "Received", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(ver, -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(type, -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(buffer, -1)); + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + EvalCallback(interp, statePtr, cmdPtr); + Tcl_DecrRefCount(cmdPtr); +} +#endif /* *------------------------------------------------------------------- * * VerifyCallback -- * - * Monitors SSL certificate validation process. - * This is called whenever a certificate is inspected - * or decided invalid. + * Monitors SSL certificate validation process. Used to control the + * behavior when the SSL_VERIFY_PEER flag is set. This is called + * whenever a certificate is inspected or decided invalid. Called for + * each certificate in the cert chain. + * + * Checks: + * certificate chain is checked starting with the deepest nesting level + * (the root CA certificate) and worked upward to the peer's certificate. + * All signatures are valid, current time is within first and last validity time. + * Check that the certificate is issued by the issuer certificate issuer. + * Check the revocation status for each certificate. + * Check the validity of the given CRL and the cert revocation status. + * Check the policies of all the certificates + * + * Args + * preverify_ok indicates whether the certificate verification passed (1) or not (0) * * Results: * A callback bound to the socket may return one of: - * 0 - the certificate is deemed invalid - * 1 - the certificate is deemed valid + * 0 - the certificate is deemed invalid, send verification + * failure alert to peer, and terminate handshake. + * 1 - the certificate is deemed valid, continue with handshake. * empty string - no change to certificate validation * * Side effects: * The err field of the currently operative State is set * to a string describing the SSL negotiation failure reason + * *------------------------------------------------------------------- */ static int -VerifyCallback(int ok, X509_STORE_CTX *ctx) -{ - Tcl_Obj *cmdPtr, *result; - char *errStr, *string; - Tcl_Size length; +VerifyCallback(int ok, X509_STORE_CTX *ctx) { + Tcl_Obj *cmdPtr; SSL *ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); X509 *cert = X509_STORE_CTX_get_current_cert(ctx); State *statePtr = (State*)SSL_get_app_data(ssl); + Tcl_Interp *interp = statePtr->interp; int depth = X509_STORE_CTX_get_error_depth(ctx); int err = X509_STORE_CTX_get_error(ctx); - dprintf("Verify: %d", ok); - - if (!ok) { - errStr = (char*)X509_verify_cert_error_string(err); - } else { - errStr = (char *)0; - } - - if (statePtr->callback == (Tcl_Obj*)NULL) { + dprintf("Called"); + dprintf("VerifyCallback: %d", ok); + + if (statePtr->vcmd == (Tcl_Obj*)NULL) { + /* Use ok value if verification is required */ if (statePtr->vflags & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) { return ok; } else { return 1; } - } - cmdPtr = Tcl_DuplicateObj(statePtr->callback); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( "verify", -1)); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( Tcl_GetChannelName(statePtr->self), -1) ); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewIntObj( depth) ); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tls_NewX509Obj( statePtr->interp, cert) ); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewIntObj( ok) ); - - Tcl_ListObjAppendElement( statePtr->interp, cmdPtr, - Tcl_NewStringObj( errStr ? errStr : "", -1) ); - - Tcl_Preserve( (ClientData) statePtr->interp); - Tcl_Preserve( (ClientData) statePtr); - - statePtr->flags |= TLS_TCL_CALLBACK; - - Tcl_IncrRefCount( cmdPtr); - if (Tcl_EvalObjEx(statePtr->interp, cmdPtr, TCL_EVAL_GLOBAL) != TCL_OK) { - /* It got an error - reject the certificate. */ - Tcl_BackgroundError( statePtr->interp); - ok = 0; - } else { - result = Tcl_GetObjResult(statePtr->interp); - string = Tcl_GetStringFromObj(result, &length); - /* An empty result leaves verification unchanged. */ - if (string != NULL && length > 0) { - if (Tcl_GetIntFromObj(statePtr->interp, result, &ok) != TCL_OK) { - Tcl_BackgroundError(statePtr->interp); - ok = 0; - } - } - } - Tcl_DecrRefCount( cmdPtr); - - statePtr->flags &= ~(TLS_TCL_CALLBACK); - - Tcl_Release( (ClientData) statePtr); - Tcl_Release( (ClientData) statePtr->interp); - - return(ok); /* By default, leave verification unchanged. */ + } else if (cert == NULL || ssl == NULL) { + return 0; + } + + dprintf("VerifyCallback: eval callback"); + + /* Create command to eval with fn, chan, depth, cert info list, status, and error args */ + cmdPtr = Tcl_DuplicateObj(statePtr->vcmd); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("verify", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewIntObj(depth)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tls_NewX509Obj(interp, cert)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewIntObj(ok)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj((char*)X509_verify_cert_error_string(err), -1)); + + /* Prevent I/O while callback is in progress */ + /* statePtr->flags |= TLS_TCL_CALLBACK; */ + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + ok = EvalCallback(interp, statePtr, cmdPtr); + Tcl_DecrRefCount(cmdPtr); + + dprintf("VerifyCallback: command result = %d", ok); + + /* statePtr->flags &= ~(TLS_TCL_CALLBACK); */ + return(ok); /* By default, leave verification unchanged. */ } /* *------------------------------------------------------------------- * * Tls_Error -- * - * Calls callback with $fd and $msg - so the callback can decide - * what to do with errors. + * Calls callback with list of errors. * * Side effects: * The err field of the currently operative State is set * to a string describing the SSL negotiation failure reason + * *------------------------------------------------------------------- */ void -Tls_Error(State *statePtr, char *msg) -{ - Tcl_Obj *cmdPtr; - - dprintf("Called"); - - if (msg && *msg) { - Tcl_SetErrorCode(statePtr->interp, "SSL", msg, (char *)NULL); - } else { - msg = Tcl_GetString(Tcl_GetObjResult(statePtr->interp)); - } +Tls_Error(State *statePtr, char *msg) { + Tcl_Interp *interp = statePtr->interp; + Tcl_Obj *cmdPtr, *listPtr; + unsigned long err; statePtr->err = msg; - if (statePtr->callback == (Tcl_Obj*)NULL) { - char buf[BUFSIZ]; - sprintf(buf, "SSL channel \"%s\": error: %s", - Tcl_GetChannelName(statePtr->self), msg); - Tcl_SetResult( statePtr->interp, buf, TCL_VOLATILE); - Tcl_BackgroundError( statePtr->interp); + dprintf("Called"); + + if (statePtr->callback == (Tcl_Obj*)NULL) return; - } + + /* Create command to eval with fn, chan, and message args */ cmdPtr = Tcl_DuplicateObj(statePtr->callback); - - Tcl_ListObjAppendElement(statePtr->interp, cmdPtr, - Tcl_NewStringObj("error", -1)); - - Tcl_ListObjAppendElement(statePtr->interp, cmdPtr, + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("error", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + if (msg != NULL) { + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(msg, -1)); - Tcl_ListObjAppendElement(statePtr->interp, cmdPtr, - Tcl_NewStringObj(msg, -1)); + } else if ((msg = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), (Tcl_Size *) NULL)) != NULL) { + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(msg, -1)); - Tcl_Preserve((ClientData) statePtr->interp); - Tcl_Preserve((ClientData) statePtr); + } else { + listPtr = Tcl_NewListObj(0, NULL); + while ((err = ERR_get_error()) != 0) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(ERR_reason_error_string(err), -1)); + } + Tcl_ListObjAppendElement(interp, cmdPtr, listPtr); + } + /* Eval callback command */ Tcl_IncrRefCount(cmdPtr); - if (Tcl_EvalObjEx(statePtr->interp, cmdPtr, TCL_EVAL_GLOBAL) != TCL_OK) { - Tcl_BackgroundError(statePtr->interp); - } + EvalCallback(interp, statePtr, cmdPtr); Tcl_DecrRefCount(cmdPtr); +} + +/* + *------------------------------------------------------------------- + * + * KeyLogCallback -- + * + * Write received key data to log file. + * + * Side effects: + * none + * + *------------------------------------------------------------------- + */ +void KeyLogCallback(const SSL *ssl, const char *line) { + char *str = getenv(SSLKEYLOGFILE); + FILE *fd; - Tcl_Release((ClientData) statePtr); - Tcl_Release((ClientData) statePtr->interp); + dprintf("Called"); + + if (str) { + fd = fopen(str, "a"); + fprintf(fd, "%s\n",line); + fclose(fd); + } } /* *------------------------------------------------------------------- * - * PasswordCallback -- - * - * Called when a password is needed to unpack RSA and PEM keys. - * Evals any bound password script and returns the result as - * the password string. - *------------------------------------------------------------------- - */ -#ifdef PRE_OPENSSL_0_9_4 -/* - * No way to handle user-data therefore no way without a global - * variable to access the Tcl interpreter. -*/ -static int -PasswordCallback(char *buf, int size, int verify) -{ - return -1; - buf = buf; - size = size; - verify = verify; -} -#else -static int -PasswordCallback(char *buf, int size, int verify, void *udata) -{ + * Password Callback -- + * + * Called when a password for a private key loading/storing a PEM + * certificate with encryption. Evals callback script and returns + * the result as the password string in buf. + * + * Results: + * None + * + * Side effects: + * Calls callback (if defined) + * + * Returns: + * Password size in bytes or -1 for an error. + * + *------------------------------------------------------------------- + */ +static int +PasswordCallback(char *buf, int size, int rwflag, void *udata) { State *statePtr = (State *) udata; Tcl_Interp *interp = statePtr->interp; Tcl_Obj *cmdPtr; - int result; + int code; dprintf("Called"); + /* If no callback, use default callback */ if (statePtr->password == NULL) { - if (Tcl_EvalEx(interp, "tls::password", -1, TCL_EVAL_GLOBAL) - == TCL_OK) { + if (Tcl_EvalEx(interp, "tls::password", -1, TCL_EVAL_GLOBAL) == TCL_OK) { char *ret = (char *) Tcl_GetStringResult(interp); strncpy(buf, ret, (size_t) size); return (int)strlen(ret); } else { return -1; } } + /* Create command to eval with fn, rwflag, and size args */ cmdPtr = Tcl_DuplicateObj(statePtr->password); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("password", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewIntObj(rwflag)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewIntObj(size)); - Tcl_Preserve((ClientData) statePtr->interp); + Tcl_Preserve((ClientData) interp); Tcl_Preserve((ClientData) statePtr); + /* Eval callback command */ Tcl_IncrRefCount(cmdPtr); - result = Tcl_EvalObjEx(interp, cmdPtr, TCL_EVAL_GLOBAL); - if (result != TCL_OK) { - Tcl_BackgroundError(statePtr->interp); + code = Tcl_EvalObjEx(interp, cmdPtr, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { +#if (TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION < 6) + Tcl_BackgroundError(interp); +#else + Tcl_BackgroundException(interp, code); +#endif } Tcl_DecrRefCount(cmdPtr); Tcl_Release((ClientData) statePtr); - Tcl_Release((ClientData) statePtr->interp); - if (result == TCL_OK) { - char *ret = (char *) Tcl_GetStringResult(interp); - strncpy(buf, ret, (size_t) size); - return (int)strlen(ret); + /* If successful, pass back password string and truncate if too long */ + if (code == TCL_OK) { + Tcl_Size len; + char *ret = (char *) Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &len); + if (len > (Tcl_Size) size-1) { + len = (Tcl_Size) size-1; + } + strncpy(buf, ret, (size_t) len); + buf[len] = '\0'; + Tcl_Release((ClientData) interp); + return((int) len); + } + Tcl_Release((ClientData) interp); + return -1; +} + +/* + *------------------------------------------------------------------- + * + * Session Callback for Clients -- + * + * Called when a new session is added to the cache. In TLS 1.3 + * this may be received multiple times after the handshake. For + * earlier versions, this will be received during the handshake. + * This is the preferred way to obtain a resumable session. + * + * Results: + * None + * + * Side effects: + * Calls callback (if defined) + * + * Return codes: + * 0 = error where session will be immediately removed from the internal cache. + * 1 = success where app retains session in session cache, and must call SSL_SESSION_free() when done. + * + *------------------------------------------------------------------- + */ +static int +SessionCallback(SSL *ssl, SSL_SESSION *session) { + State *statePtr = (State*)SSL_get_app_data((SSL *)ssl); + Tcl_Interp *interp = statePtr->interp; + Tcl_Obj *cmdPtr; + const unsigned char *ticket; + const unsigned char *session_id; + size_t len2; + unsigned int ulen; + + dprintf("Called"); + + if (statePtr->callback == (Tcl_Obj*)NULL) { + return SSL_TLSEXT_ERR_OK; + } else if (ssl == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + + /* Create command to eval with fn, chan, session id, session ticket, and lifetime args */ + cmdPtr = Tcl_DuplicateObj(statePtr->callback); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("session", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + + /* Session id */ + session_id = SSL_SESSION_get_id(session, &ulen); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewByteArrayObj(session_id, (Tcl_Size) ulen)); + + /* Session ticket */ + SSL_SESSION_get0_ticket(session, &ticket, &len2); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewByteArrayObj(ticket, (Tcl_Size) len2)); + + /* Lifetime - number of seconds */ + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewLongObj((long) SSL_SESSION_get_ticket_lifetime_hint(session))); + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + EvalCallback(interp, statePtr, cmdPtr); + Tcl_DecrRefCount(cmdPtr); + return 0; +} + +/* + *------------------------------------------------------------------- + * + * ALPN Callback for Servers and NPN Callback for Clients -- + * + * Perform protocol (http/1.1, h2, h3, etc.) selection for the + * incoming connection. Called after Hello and server callbacks. + * Where 'out' is selected protocol and 'in' is the peer advertised list. + * + * Results: + * None + * + * Side effects: + * Calls callback (if defined) + * + * Return codes: + * SSL_TLSEXT_ERR_OK: ALPN protocol selected. The connection continues. + * SSL_TLSEXT_ERR_ALERT_FATAL: There was no overlap between the client's + * supplied list and the server configuration. The connection will be aborted. + * SSL_TLSEXT_ERR_NOACK: ALPN protocol not selected, e.g., because no ALPN + * protocols are configured for this connection. The connection continues. + * + *------------------------------------------------------------------- + */ +static int +ALPNCallback(SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) { + State *statePtr = (State*)arg; + Tcl_Interp *interp = statePtr->interp; + Tcl_Obj *cmdPtr; + int code, res; + + dprintf("Called"); + + if (ssl == NULL || arg == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + + /* Select protocol */ + if (SSL_select_next_proto((unsigned char **) out, outlen, statePtr->protos, statePtr->protos_len, + in, inlen) == OPENSSL_NPN_NEGOTIATED) { + /* Match found */ + res = SSL_TLSEXT_ERR_OK; + } else { + /* OPENSSL_NPN_NO_OVERLAP = No overlap, so use first item from client protocol list */ + res = SSL_TLSEXT_ERR_NOACK; + } + + if (statePtr->vcmd == (Tcl_Obj*)NULL) { + return res; + } + + /* Create command to eval with fn, chan, depth, cert info list, status, and error args */ + cmdPtr = Tcl_DuplicateObj(statePtr->vcmd); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("alpn", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj((const char *) *out, -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewBooleanObj(res == SSL_TLSEXT_ERR_OK)); + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + if ((code = EvalCallback(interp, statePtr, cmdPtr)) > 1) { + res = SSL_TLSEXT_ERR_NOACK; + } else if (code == 1) { + res = SSL_TLSEXT_ERR_OK; + } else { + res = SSL_TLSEXT_ERR_ALERT_FATAL; + } + Tcl_DecrRefCount(cmdPtr); + return res; +} + +/* + *------------------------------------------------------------------- + * + * Advertise Protocols Callback for Next Protocol Negotiation (NPN) in ServerHello -- + * + * called when a TLS server needs a list of supported protocols for Next + * Protocol Negotiation. + * + * Results: + * None + * + * Side effects: + * + * Return codes: + * SSL_TLSEXT_ERR_OK: NPN protocol selected. The connection continues. + * SSL_TLSEXT_ERR_NOACK: NPN protocol not selected. The connection continues. + * + *------------------------------------------------------------------- + */ +#ifdef USE_NPN +static int +NPNCallback(const SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) { + State *statePtr = (State*)arg; + + dprintf("Called"); + + if (ssl == NULL || arg == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + + /* Set protocols list */ + if (statePtr->protos != NULL) { + *out = statePtr->protos; + *outlen = statePtr->protos_len; + } else { + *out = NULL; + *outlen = 0; + return SSL_TLSEXT_ERR_NOACK; + } + return SSL_TLSEXT_ERR_OK; +} +#endif + +/* + *------------------------------------------------------------------- + * + * SNI Callback for Servers -- + * + * Perform server-side SNI hostname selection after receiving SNI extension + * in Client Hello. Called after hello callback but before ALPN callback. + * + * Results: + * None + * + * Side effects: + * Calls callback (if defined) + * + * Return codes: + * SSL_TLSEXT_ERR_OK: SNI hostname is accepted. The connection continues. + * SSL_TLSEXT_ERR_ALERT_FATAL: SNI hostname is not accepted. The connection + * is aborted. Default for alert is SSL_AD_UNRECOGNIZED_NAME. + * SSL_TLSEXT_ERR_ALERT_WARNING: SNI hostname is not accepted, warning alert + * sent (not supported in TLSv1.3). The connection continues. + * SSL_TLSEXT_ERR_NOACK: SNI hostname is not accepted and not acknowledged, + * e.g. if SNI has not been configured. The connection continues. + * + *------------------------------------------------------------------- + */ +static int +SNICallback(const SSL *ssl, int *alert, void *arg) { + State *statePtr = (State*)arg; + Tcl_Interp *interp = statePtr->interp; + Tcl_Obj *cmdPtr; + int code, res; + const char *servername = NULL; + + dprintf("Called"); + + if (ssl == NULL || arg == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + + /* Only works for TLS 1.2 and earlier */ + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername || servername[0] == '\0') { + return SSL_TLSEXT_ERR_NOACK; + } + + if (statePtr->vcmd == (Tcl_Obj*)NULL) { + return SSL_TLSEXT_ERR_OK; + } + + /* Create command to eval with fn, chan, and server name args */ + cmdPtr = Tcl_DuplicateObj(statePtr->vcmd); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("sni", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(servername , -1)); + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + if ((code = EvalCallback(interp, statePtr, cmdPtr)) > 1) { + res = SSL_TLSEXT_ERR_ALERT_WARNING; + *alert = SSL_AD_UNRECOGNIZED_NAME; /* Not supported by TLS 1.3 */ + } else if (code == 1) { + res = SSL_TLSEXT_ERR_OK; + } else { + res = SSL_TLSEXT_ERR_ALERT_FATAL; + *alert = SSL_AD_UNRECOGNIZED_NAME; /* Not supported by TLS 1.3 */ + } + Tcl_DecrRefCount(cmdPtr); + return res; +} + +/* + *------------------------------------------------------------------- + * + * ClientHello Handshake Callback for Servers -- + * + * Used by server to examine the server name indication (SNI) extension + * provided by the client in order to select an appropriate certificate to + * present, and make other configuration adjustments relevant to that server + * name and its configuration. This includes swapping out the associated + * SSL_CTX pointer, modifying the server's list of permitted TLS versions, + * changing the server's cipher list in response to the client's cipher list, etc. + * Called before SNI and ALPN callbacks. + * + * Results: + * None + * + * Side effects: + * Calls callback (if defined) + * + * Return codes: + * SSL_CLIENT_HELLO_RETRY: suspend the handshake, and the handshake function will return immediately + * SSL_CLIENT_HELLO_ERROR: failure, terminate connection. Set alert to error code. + * SSL_CLIENT_HELLO_SUCCESS: success + * + *------------------------------------------------------------------- + */ +static int +HelloCallback(SSL *ssl, int *alert, void *arg) { + State *statePtr = (State*)arg; + Tcl_Interp *interp = statePtr->interp; + Tcl_Obj *cmdPtr; + int code, res; + const char *servername; + const unsigned char *p; + size_t len, remaining; + + dprintf("Called"); + + if (statePtr->vcmd == (Tcl_Obj*)NULL) { + return SSL_CLIENT_HELLO_SUCCESS; + } else if (ssl == (const SSL *)NULL || arg == (void *)NULL) { + return SSL_CLIENT_HELLO_ERROR; + } + + /* Get names */ + if (!SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &p, &remaining) || remaining <= 2) { + *alert = SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER; + return SSL_CLIENT_HELLO_ERROR; + } + + /* Extract the length of the supplied list of names. */ + len = (*(p++) << 8); + len += *(p++); + if (len + 2 != remaining) { + *alert = SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER; + return SSL_CLIENT_HELLO_ERROR; + } + remaining = len; + + /* The list in practice only has a single element, so we only consider the first one. */ + if (remaining == 0 || *p++ != TLSEXT_NAMETYPE_host_name) { + *alert = SSL_R_TLSV1_ALERT_INTERNAL_ERROR; + return SSL_CLIENT_HELLO_ERROR; + } + remaining--; + + /* Now we can finally pull out the byte array with the actual hostname. */ + if (remaining <= 2) { + *alert = SSL_R_TLSV1_ALERT_INTERNAL_ERROR; + return SSL_CLIENT_HELLO_ERROR; + } + len = (*(p++) << 8); + len += *(p++); + if (len + 2 > remaining) { + *alert = SSL_R_TLSV1_ALERT_INTERNAL_ERROR; + return SSL_CLIENT_HELLO_ERROR; + } + remaining = len; + servername = (const char *)p; + + /* Create command to eval with fn, chan, and server name args */ + cmdPtr = Tcl_DuplicateObj(statePtr->vcmd); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("hello", -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, + Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); + Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(servername, (Tcl_Size) len)); + + /* Eval callback command */ + Tcl_IncrRefCount(cmdPtr); + if ((code = EvalCallback(interp, statePtr, cmdPtr)) > 1) { + res = SSL_CLIENT_HELLO_RETRY; + *alert = SSL_R_TLSV1_ALERT_USER_CANCELLED; + } else if (code == 1) { + res = SSL_CLIENT_HELLO_SUCCESS; } else { - return -1; + res = SSL_CLIENT_HELLO_ERROR; + *alert = SSL_R_TLSV1_ALERT_INTERNAL_ERROR; } - verify = verify; + Tcl_DecrRefCount(cmdPtr); + return res; } -#endif +/********************/ +/* Commands */ +/********************/ + /* *------------------------------------------------------------------- * * CiphersObjCmd -- list available ciphers * @@ -477,134 +943,207 @@ * Side effects: * constructs and destroys SSL context (CTX) * *------------------------------------------------------------------- */ +static const char *protocols[] = { + "ssl2", "ssl3", "tls1", "tls1.1", "tls1.2", "tls1.3", NULL +}; +enum protocol { + TLS_SSL2, TLS_SSL3, TLS_TLS1, TLS_TLS1_1, TLS_TLS1_2, TLS_TLS1_3, TLS_NONE +}; + static int -CiphersObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ - static const char *protocols[] = { - "ssl2", "ssl3", "tls1", "tls1.1", "tls1.2", "tls1.3", NULL - }; - enum protocol { - TLS_SSL2, TLS_SSL3, TLS_TLS1, TLS_TLS1_1, TLS_TLS1_2, TLS_TLS1_3, TLS_NONE - }; - Tcl_Obj *objPtr; +CiphersObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + Tcl_Obj *objPtr = NULL; SSL_CTX *ctx = NULL; SSL *ssl = NULL; STACK_OF(SSL_CIPHER) *sk; char *cp, buf[BUFSIZ]; - int index, verbose = 0; + int index, verbose = 0, use_supported = 0; + const SSL_METHOD *method; + (void) clientData; dprintf("Called"); - if (objc < 2 || objc > 3) { - Tcl_WrongNumArgs(interp, 1, objv, "protocol ?verbose?"); + if ((objc < 2) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 1, objv, "protocol ?verbose? ?supported?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[1], protocols, "protocol", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + if ((objc > 2) && Tcl_GetBooleanFromObj(interp, objv[2], &verbose) != TCL_OK) { return TCL_ERROR; } - if (Tcl_GetIndexFromObj( interp, objv[1], protocols, "protocol", 0, - &index) != TCL_OK) { + if ((objc > 3) && Tcl_GetBooleanFromObj(interp, objv[3], &use_supported) != TCL_OK) { return TCL_ERROR; } - if (objc > 2 && Tcl_GetBooleanFromObj( interp, objv[2], - &verbose) != TCL_OK) { - return TCL_ERROR; - } + + ERR_clear_error(); + switch ((enum protocol)index) { - case TLS_SSL2: -#if defined(NO_SSL2) - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return TCL_ERROR; -#else - ctx = SSL_CTX_new(SSLv2_method()); break; -#endif - case TLS_SSL3: -#if defined(NO_SSL3) - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return TCL_ERROR; -#else - ctx = SSL_CTX_new(SSLv3_method()); break; -#endif - case TLS_TLS1: -#if defined(NO_TLS1) - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return TCL_ERROR; -#else - ctx = SSL_CTX_new(TLSv1_method()); break; -#endif - case TLS_TLS1_1: -#if defined(NO_TLS1_1) - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return TCL_ERROR; -#else - ctx = SSL_CTX_new(TLSv1_1_method()); break; -#endif - case TLS_TLS1_2: -#if defined(NO_TLS1_2) - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return TCL_ERROR; -#else - ctx = SSL_CTX_new(TLSv1_2_method()); break; -#endif - case TLS_TLS1_3: -#if defined(NO_TLS1_3) - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return TCL_ERROR; -#else - ctx = SSL_CTX_new(TLS_method()); break; - SSL_CTX_set_min_proto_version (ctx, TLS1_3_VERSION); - SSL_CTX_set_max_proto_version (ctx, TLS1_3_VERSION); -#endif - default: - break; - } + case TLS_SSL2: +#if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined(NO_SSL2) || defined(OPENSSL_NO_SSL2) + Tcl_AppendResult(interp, protocols[index], ": protocol not supported", NULL); + return TCL_ERROR; +#else + method = SSLv2_method(); break; +#endif + case TLS_SSL3: +#if defined(NO_SSL3) || defined(OPENSSL_NO_SSL3) || defined(OPENSSL_NO_SSL3_METHOD) + Tcl_AppendResult(interp, protocols[index], ": protocol not supported", NULL); + return TCL_ERROR; +#else + method = SSLv3_method(); break; +#endif + case TLS_TLS1: +#if defined(NO_TLS1) || defined(OPENSSL_NO_TLS1) || defined(OPENSSL_NO_TLS1_METHOD) + Tcl_AppendResult(interp, protocols[index], ": protocol not supported", NULL); + return TCL_ERROR; +#else + method = TLSv1_method(); break; +#endif + case TLS_TLS1_1: +#if defined(NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1_METHOD) + Tcl_AppendResult(interp, protocols[index], ": protocol not supported", NULL); + return TCL_ERROR; +#else + method = TLSv1_1_method(); break; +#endif + case TLS_TLS1_2: +#if defined(NO_TLS1_2) || defined(OPENSSL_NO_TLS1_2) || defined(OPENSSL_NO_TLS1_2_METHOD) + Tcl_AppendResult(interp, protocols[index], ": protocol not supported", NULL); + return TCL_ERROR; +#else + method = TLSv1_2_method(); break; +#endif + case TLS_TLS1_3: +#if defined(NO_TLS1_3) || defined(OPENSSL_NO_TLS1_3) + Tcl_AppendResult(interp, protocols[index], ": protocol not supported", NULL); + return TCL_ERROR; +#else + method = TLS_method(); + SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); + break; +#endif + default: + method = TLS_method(); + break; + } + + ctx = SSL_CTX_new(method); if (ctx == NULL) { - Tcl_AppendResult(interp, REASON(), (char *) NULL); + Tcl_AppendResult(interp, GET_ERR_REASON(), NULL); return TCL_ERROR; } + ssl = SSL_new(ctx); if (ssl == NULL) { - Tcl_AppendResult(interp, REASON(), (char *) NULL); + Tcl_AppendResult(interp, GET_ERR_REASON(), NULL); SSL_CTX_free(ctx); return TCL_ERROR; } - objPtr = Tcl_NewListObj( 0, NULL); - - if (!verbose) { - for (index = 0; ; index++) { - cp = (char*)SSL_get_cipher_list( ssl, index); - if (cp == NULL) break; - Tcl_ListObjAppendElement( interp, objPtr, - Tcl_NewStringObj( cp, -1) ); - } + + /* Use list and order as would be sent in a ClientHello or all available ciphers */ + if (use_supported) { + sk = SSL_get1_supported_ciphers(ssl); } else { sk = SSL_get_ciphers(ssl); - - for (index = 0; index < sk_SSL_CIPHER_num(sk); index++) { - size_t i; - SSL_CIPHER_description( sk_SSL_CIPHER_value( sk, index), - buf, sizeof(buf)); - for (i = strlen(buf) - 1; i ; i--) { - if (buf[i] == ' ' || buf[i] == '\n' || - buf[i] == '\r' || buf[i] == '\t') { - buf[i] = '\0'; - } else { - break; - } - } - Tcl_ListObjAppendElement( interp, objPtr, - Tcl_NewStringObj( buf, -1) ); + } + + if (sk != NULL) { + if (!verbose) { + objPtr = Tcl_NewListObj(0, NULL); + for (int i = 0; i < sk_SSL_CIPHER_num(sk); i++) { + const SSL_CIPHER *c = sk_SSL_CIPHER_value(sk, i); + if (c == NULL) continue; + + /* cipher name or (NONE) */ + cp = SSL_CIPHER_get_name(c); + if (cp == NULL) break; + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(cp, -1)); + } + + } else { + objPtr = Tcl_NewStringObj("",0); + for (int i = 0; i < sk_SSL_CIPHER_num(sk); i++) { + const SSL_CIPHER *c = sk_SSL_CIPHER_value(sk, i); + if (c == NULL) continue; + + /* textual description of the cipher */ + if (SSL_CIPHER_description(c, buf, sizeof(buf)) != NULL) { + Tcl_AppendToObj(objPtr, buf, (Tcl_Size) strlen(buf)); + } else { + Tcl_AppendToObj(objPtr, "UNKNOWN\n", 8); + } + } + } + if (use_supported) { + sk_SSL_CIPHER_free(sk); } } SSL_free(ssl); SSL_CTX_free(ctx); - Tcl_SetObjResult( interp, objPtr); + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; +} + +/* + *------------------------------------------------------------------- + * + * ProtocolsObjCmd -- list available protocols + * + * This procedure is invoked to process the "tls::protocols" command + * to list available protocols. + * + * Results: + * A standard Tcl result list. + * + * Side effects: + * none + * + *------------------------------------------------------------------- + */ +static int +ProtocolsObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + Tcl_Obj *objPtr; + (void) clientData; + + dprintf("Called"); + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + + ERR_clear_error(); + + objPtr = Tcl_NewListObj(0, NULL); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(NO_SSL2) && !defined(OPENSSL_NO_SSL2) + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(protocols[TLS_SSL2], -1)); +#endif +#if !defined(NO_SSL3) && !defined(OPENSSL_NO_SSL3) && !defined(OPENSSL_NO_SSL3_METHOD) + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(protocols[TLS_SSL3], -1)); +#endif +#if !defined(NO_TLS1) && !defined(OPENSSL_NO_TLS1) && !defined(OPENSSL_NO_TLS1_METHOD) + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(protocols[TLS_TLS1], -1)); +#endif +#if !defined(NO_TLS1_1) && !defined(OPENSSL_NO_TLS1_1) && !defined(OPENSSL_NO_TLS1_1_METHOD) + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(protocols[TLS_TLS1_1], -1)); +#endif +#if !defined(NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_2_METHOD) + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(protocols[TLS_TLS1_2], -1)); +#endif +#if !defined(NO_TLS1_3) && !defined(OPENSSL_NO_TLS1_3) + Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(protocols[TLS_TLS1_3], -1)); +#endif + + Tcl_SetObjResult(interp, objPtr); return TCL_OK; } /* *------------------------------------------------------------------- @@ -620,78 +1159,76 @@ * Side effects: * May force SSL negotiation to take place. * *------------------------------------------------------------------- */ - -static int HandshakeObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ - Tcl_Channel chan; /* The channel to set a mode on. */ - State *statePtr; /* client state for ssl socket */ - const char *errStr = NULL; - int ret = 1; - int err = 0; - - dprintf("Called"); - - if (objc != 2) { - Tcl_WrongNumArgs(interp, 1, objv, "channel"); - return(TCL_ERROR); - } - - chan = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL); - if (chan == (Tcl_Channel) NULL) { - return(TCL_ERROR); - } - - /* - * Make sure to operate on the topmost channel - */ - chan = Tcl_GetTopChannel(chan); - if (Tcl_GetChannelType(chan) != Tls_ChannelType()) { - Tcl_AppendResult(interp, "bad channel \"", Tcl_GetChannelName(chan), "\": not a TLS channel", (char *)NULL); - return(TCL_ERROR); - } - statePtr = (State *)Tcl_GetChannelInstanceData(chan); - - dprintf("Calling Tls_WaitForConnect"); - ret = Tls_WaitForConnect(statePtr, &err, 1); - dprintf("Tls_WaitForConnect returned: %i", ret); - - if ( - ret < 0 && \ - ((statePtr->flags & TLS_TCL_ASYNC) && err == EAGAIN) - ) { - dprintf("Async set and err = EAGAIN"); - ret = 0; - } else if (ret < 0) { - errStr = statePtr->err; - Tcl_ResetResult(interp); - Tcl_SetErrno(err); - - if (!errStr || *errStr == 0) { - errStr = Tcl_PosixError(interp); - } - - Tcl_AppendResult(interp, "handshake failed: ", errStr, (char *) NULL); - dprintf("Returning TCL_ERROR with handshake failed: %s", errStr); - return(TCL_ERROR); - } else { - if (err != 0) { - dprintf("Got an error with a completed handshake: err = %i", err); - } - - ret = 1; - } - - dprintf("Returning TCL_OK with data \"%i\"", ret); - Tcl_SetObjResult(interp, Tcl_NewIntObj(ret)); - return(TCL_OK); +static int HandshakeObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + Tcl_Channel chan; /* The channel to set a mode on. */ + State *statePtr; /* client state for ssl socket */ + const char *errStr = NULL; + int ret = 1; + int err = 0; + (void) clientData; + + dprintf("Called"); + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "channel"); + return(TCL_ERROR); + } + + ERR_clear_error(); + + chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], (Tcl_Size *) NULL), NULL); + if (chan == (Tcl_Channel) NULL) { + return(TCL_ERROR); + } + + /* Make sure to operate on the topmost channel */ + chan = Tcl_GetTopChannel(chan); + if (Tcl_GetChannelType(chan) != Tls_ChannelType()) { + Tcl_AppendResult(interp, "bad channel \"", Tcl_GetChannelName(chan), + "\": not a TLS channel", NULL); + Tcl_SetErrorCode(interp, "TLS", "HANDSHAKE", "CHANNEL", "INVALID", (char *) NULL); + return(TCL_ERROR); + } + statePtr = (State *)Tcl_GetChannelInstanceData(chan); + + dprintf("Calling Tls_WaitForConnect"); + ret = Tls_WaitForConnect(statePtr, &err, 1); + dprintf("Tls_WaitForConnect returned: %i", ret); + + if (ret < 0 && ((statePtr->flags & TLS_TCL_ASYNC) && (err == EAGAIN))) { + dprintf("Async set and err = EAGAIN"); + ret = 0; + } else if (ret < 0) { + long result; + errStr = statePtr->err; + Tcl_ResetResult(interp); + Tcl_SetErrno(err); + + if (!errStr || (*errStr == 0)) { + errStr = Tcl_PosixError(interp); + } + + Tcl_AppendResult(interp, "handshake failed: ", errStr, (char *) NULL); + if ((result = SSL_get_verify_result(statePtr->ssl)) != X509_V_OK) { + Tcl_AppendResult(interp, " due to \"", X509_verify_cert_error_string(result), "\"", (char *) NULL); + } + Tcl_SetErrorCode(interp, "TLS", "HANDSHAKE", "FAILED", (char *) NULL); + dprintf("Returning TCL_ERROR with handshake failed: %s", errStr); + return(TCL_ERROR); + } else { + if (err != 0) { + dprintf("Got an error with a completed handshake: err = %i", err); + } + ret = 1; + } + + dprintf("Returning TCL_OK with data \"%i\"", ret); + Tcl_SetObjResult(interp, Tcl_NewIntObj(ret)); + return(TCL_OK); } /* *------------------------------------------------------------------- * @@ -707,121 +1244,118 @@ * Side effects: * May modify the behavior of an IO channel. * *------------------------------------------------------------------- */ - static int -ImportObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ +ImportObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Channel chan; /* The channel to set a mode on. */ State *statePtr; /* client state for ssl socket */ - SSL_CTX *ctx = NULL; - Tcl_Obj *script = NULL; - Tcl_Obj *password = NULL; + SSL_CTX *ctx = NULL; + Tcl_Obj *script = NULL; + Tcl_Obj *password = NULL; + Tcl_Obj *vcmd = NULL; Tcl_DString upperChannelTranslation, upperChannelBlocking, upperChannelEncoding, upperChannelEOFChar; int idx; Tcl_Size len; - int flags = TLS_TCL_INIT; - int server = 0; /* is connection incoming or outgoing? */ - char *keyfile = NULL; - char *certfile = NULL; - unsigned char *key = NULL; - Tcl_Size key_len = 0; - unsigned char *cert = NULL; - Tcl_Size cert_len = 0; - char *ciphers = NULL; - char *CAfile = NULL; - char *CAdir = NULL; - char *DHparams = NULL; - char *model = NULL; -#ifndef OPENSSL_NO_TLSEXT - char *servername = NULL; /* hostname for Server Name Indication */ -#endif + int flags = TLS_TCL_INIT; + int server = 0; /* is connection incoming or outgoing? */ + char *keyfile = NULL; + char *certfile = NULL; + unsigned char *key = NULL; + Tcl_Size key_len = 0; + unsigned char *cert = NULL; + Tcl_Size cert_len = 0; + char *ciphers = NULL; + char *ciphersuites = NULL; + char *CAfile = NULL; + char *CApath = NULL; + char *DHparams = NULL; + char *model = NULL; + char *servername = NULL; /* hostname for Server Name Indication */ + const unsigned char *session_id = NULL; + Tcl_Obj *alpn = NULL; int ssl2 = 0, ssl3 = 0; int tls1 = 1, tls1_1 = 1, tls1_2 = 1, tls1_3 = 1; - int proto = 0; - int verify = 0, require = 0, request = 1; + int proto = 0, level = -1; + int verify = 0, require = 0, request = 1, post_handshake = 0; + (void) clientData; dprintf("Called"); -#if defined(NO_TLS1) && defined(NO_TLS1_1) && defined(NO_TLS1_2) && defined(NO_SSL3) && !defined(NO_SSL2) - ssl2 = 1; -#endif -#if defined(NO_TLS1) && defined(NO_TLS1_1) && defined(NO_TLS1_2) && defined(NO_SSL2) && !defined(NO_SSL3) - ssl3 = 1; -#endif -#if defined(NO_TLS1) +#if defined(NO_TLS1) || defined(OPENSSL_NO_TLS1) tls1 = 0; #endif -#if defined(NO_TLS1_1) +#if defined(NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1) tls1_1 = 0; #endif -#if defined(NO_TLS1_2) +#if defined(NO_TLS1_2) || defined(OPENSSL_NO_TLS1_2) tls1_2 = 0; #endif -#if defined(NO_TLS1_3) +#if defined(NO_TLS1_3) || defined(OPENSSL_NO_TLS1_3) tls1_3 = 0; #endif if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?options?"); return TCL_ERROR; } - chan = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL); + ERR_clear_error(); + + chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], (Tcl_Size *) NULL), NULL); if (chan == (Tcl_Channel) NULL) { return TCL_ERROR; } - /* - * Make sure to operate on the topmost channel - */ + /* Make sure to operate on the topmost channel */ chan = Tcl_GetTopChannel(chan); for (idx = 2; idx < objc; idx++) { - char *opt = Tcl_GetString(objv[idx]); + char *opt = Tcl_GetStringFromObj(objv[idx], (Tcl_Size *)NULL); if (opt[0] != '-') break; - OPTSTR( "-cadir", CAdir); - OPTSTR( "-cafile", CAfile); - OPTSTR( "-certfile", certfile); - OPTSTR( "-cipher", ciphers); - OPTOBJ( "-command", script); - OPTSTR( "-dhparams", DHparams); - OPTSTR( "-keyfile", keyfile); - OPTSTR( "-model", model); - OPTOBJ( "-password", password); - OPTBOOL( "-require", require); - OPTBOOL( "-request", request); - OPTBOOL( "-server", server); -#ifndef OPENSSL_NO_TLSEXT - OPTSTR( "-servername", servername); -#endif - - OPTBOOL( "-ssl2", ssl2); - OPTBOOL( "-ssl3", ssl3); - OPTBOOL( "-tls1", tls1); - OPTBOOL( "-tls1.1", tls1_1); - OPTBOOL( "-tls1.2", tls1_2); - OPTBOOL( "-tls1.3", tls1_3) + OPTOBJ("-alpn", alpn); + OPTSTR("-cadir", CApath); + OPTSTR("-cafile", CAfile); OPTBYTE("-cert", cert, cert_len); + OPTSTR("-certfile", certfile); + OPTSTR("-cipher", ciphers); + OPTSTR("-ciphers", ciphers); + OPTSTR("-ciphersuites", ciphersuites); + OPTOBJ("-command", script); + OPTSTR("-dhparams", DHparams); OPTBYTE("-key", key, key_len); + OPTSTR("-keyfile", keyfile); + OPTSTR("-model", model); + OPTOBJ("-password", password); + OPTBOOL("-post_handshake", post_handshake); + OPTBOOL("-request", request); + OPTBOOL("-require", require); + OPTINT("-security_level", level); + OPTBOOL("-server", server); + OPTSTR("-servername", servername); + OPTSTR("-session_id", session_id); + OPTBOOL("-ssl2", ssl2); + OPTBOOL("-ssl3", ssl3); + OPTBOOL("-tls1", tls1); + OPTBOOL("-tls1.1", tls1_1); + OPTBOOL("-tls1.2", tls1_2); + OPTBOOL("-tls1.3", tls1_3); + OPTOBJ("-validatecommand", vcmd); + OPTOBJ("-vcmd", vcmd); - OPTBAD( "option", "-cadir, -cafile, -cert, -certfile, -cipher, -command, -dhparams, -key, -keyfile, -model, -password, -require, -request, -server, -servername, -ssl2, -ssl3, -tls1, -tls1.1, -tls1.2, or tls1.3"); + OPTBAD("option", "-alpn, -cadir, -cafile, -cert, -certfile, -cipher, -ciphersuites, -command, -dhparams, -key, -keyfile, -model, -password, -post_handshake, -request, -require, -security_level, -server, -servername, -session_id, -ssl2, -ssl3, -tls1, -tls1.1, -tls1.2, -tls1.3, or -validatecommand"); return TCL_ERROR; } - if (request) verify |= SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_PEER; - if (request && require) verify |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - if (verify == 0) verify = SSL_VERIFY_NONE; + if (request) verify |= SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_PEER; + if (request && require) verify |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + if (request && post_handshake) verify |= SSL_VERIFY_POST_HANDSHAKE; + if (verify == 0) verify = SSL_VERIFY_NONE; proto |= (ssl2 ? TLS_PROTO_SSL2 : 0); proto |= (ssl3 ? TLS_PROTO_SSL3 : 0); proto |= (tls1 ? TLS_PROTO_TLS1 : 0); proto |= (tls1_1 ? TLS_PROTO_TLS1_1 : 0); @@ -832,12 +1366,13 @@ if (cert && !*cert) cert = NULL; if (key && !*key) key = NULL; if (certfile && !*certfile) certfile = NULL; if (keyfile && !*keyfile) keyfile = NULL; if (ciphers && !*ciphers) ciphers = NULL; + if (ciphersuites && !*ciphersuites) ciphersuites = NULL; if (CAfile && !*CAfile) CAfile = NULL; - if (CAdir && !*CAdir) CAdir = NULL; + if (CApath && !*CApath) CApath = NULL; if (DHparams && !*DHparams) DHparams = NULL; /* new SSL state */ statePtr = (State *) ckalloc((unsigned) sizeof(State)); memset(statePtr, 0, sizeof(State)); @@ -862,36 +1397,45 @@ if (len) { statePtr->password = password; Tcl_IncrRefCount(statePtr->password); } } + + /* allocate validate command */ + if (vcmd) { + (void) Tcl_GetStringFromObj(vcmd, &len); + if (len) { + statePtr->vcmd = vcmd; + Tcl_IncrRefCount(statePtr->vcmd); + } + } if (model != NULL) { int mode; /* Get the "model" context */ chan = Tcl_GetChannel(interp, model, &mode); if (chan == (Tcl_Channel) NULL) { - Tls_Free((void *)statePtr); + Tls_Free((char *) statePtr); return TCL_ERROR; } - /* - * Make sure to operate on the topmost channel - */ - chan = Tcl_GetTopChannel(chan); + /* + * Make sure to operate on the topmost channel + */ + chan = Tcl_GetTopChannel(chan); if (Tcl_GetChannelType(chan) != Tls_ChannelType()) { - Tcl_AppendResult(interp, "bad channel \"", - Tcl_GetChannelName(chan), "\": not a TLS channel", (char *)NULL); - Tls_Free((void *)statePtr); + Tcl_AppendResult(interp, "bad channel \"", Tcl_GetChannelName(chan), + "\": not a TLS channel", NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "CHANNEL", "INVALID", (char *) NULL); + Tls_Free((char *) statePtr); return TCL_ERROR; } ctx = ((State *)Tcl_GetChannelInstanceData(chan))->ctx; } else { - if ((ctx = CTX_Init(statePtr, server, proto, keyfile, certfile, key, - cert, key_len, cert_len, CAdir, CAfile, ciphers, - DHparams)) == (SSL_CTX*)0) { - Tls_Free((void *)statePtr); + if ((ctx = CTX_Init(statePtr, server, proto, keyfile, certfile, key, cert, (int) key_len, + (int) cert_len, CApath, CAfile, ciphers, ciphersuites, level, DHparams)) == NULL) { + Tls_Free((char *) statePtr); return TCL_ERROR; } } statePtr->ctx = ctx; @@ -911,78 +1455,204 @@ Tcl_GetChannelOption(interp, chan, "-translation", &upperChannelTranslation); Tcl_GetChannelOption(interp, chan, "-blocking", &upperChannelBlocking); Tcl_SetChannelOption(interp, chan, "-translation", "binary"); Tcl_SetChannelOption(interp, chan, "-blocking", "true"); dprintf("Consuming Tcl channel %s", Tcl_GetChannelName(chan)); - statePtr->self = Tcl_StackChannel(interp, Tls_ChannelType(), statePtr, (TCL_READABLE | TCL_WRITABLE), chan); + statePtr->self = Tcl_StackChannel(interp, Tls_ChannelType(), (ClientData) statePtr, + (TCL_READABLE | TCL_WRITABLE), chan); dprintf("Created channel named %s", Tcl_GetChannelName(statePtr->self)); if (statePtr->self == (Tcl_Channel) NULL) { /* * No use of Tcl_EventuallyFree because no possible Tcl_Preserve. */ - Tls_Free((void *)statePtr); + Tls_Free((char *) statePtr); return TCL_ERROR; } Tcl_SetChannelOption(interp, statePtr->self, "-translation", Tcl_DStringValue(&upperChannelTranslation)); Tcl_SetChannelOption(interp, statePtr->self, "-encoding", Tcl_DStringValue(&upperChannelEncoding)); Tcl_SetChannelOption(interp, statePtr->self, "-eofchar", Tcl_DStringValue(&upperChannelEOFChar)); Tcl_SetChannelOption(interp, statePtr->self, "-blocking", Tcl_DStringValue(&upperChannelBlocking)); + Tcl_DStringFree(&upperChannelTranslation); + Tcl_DStringFree(&upperChannelEncoding); + Tcl_DStringFree(&upperChannelEOFChar); + Tcl_DStringFree(&upperChannelBlocking); /* * SSL Initialization */ - statePtr->ssl = SSL_new(statePtr->ctx); if (!statePtr->ssl) { /* SSL library error */ - Tcl_AppendResult(interp, "couldn't construct ssl session: ", REASON(), - (char *) NULL); - Tls_Free((void *)statePtr); + Tcl_AppendResult(interp, "couldn't construct ssl session: ", GET_ERR_REASON(), (char *) NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "INIT", "FAILED", (char *) NULL); + Tls_Free((char *) statePtr); return TCL_ERROR; } -#ifndef OPENSSL_NO_TLSEXT + /* Set host server name */ if (servername) { - if (!SSL_set_tlsext_host_name(statePtr->ssl, servername) && require) { - Tcl_AppendResult(interp, "setting TLS host name extension failed", - (char *) NULL); - Tls_Free((void *)statePtr); - return TCL_ERROR; - } - } -#endif + /* Sets the server name indication (SNI) in ClientHello extension */ + /* Per RFC 6066, hostname is a ASCII encoded string, though RFC 4366 says UTF-8. */ + if (!SSL_set_tlsext_host_name(statePtr->ssl, servername) && require) { + Tcl_AppendResult(interp, "Set SNI extension failed: ", GET_ERR_REASON(), (char *) NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "SNI", "FAILED", (char *) NULL); + Tls_Free((char *) statePtr); + return TCL_ERROR; + } + + /* Set hostname for peer certificate hostname verification in clients. + Don't use SSL_set1_host since it has limitations. */ + if (!SSL_add1_host(statePtr->ssl, servername)) { + Tcl_AppendResult(interp, "Set DNS hostname failed: ", GET_ERR_REASON(), (char *) NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "HOSTNAME", "FAILED", (char *) NULL); + Tls_Free((char *) statePtr); + return TCL_ERROR; + } + } + + /* Resume session id */ + if (session_id && strlen(session_id) <= SSL_MAX_SID_CTX_LENGTH) { + /* SSL_set_session() */ + if (!SSL_SESSION_set1_id_context(SSL_get_session(statePtr->ssl), session_id, (unsigned int) strlen(session_id))) { + Tcl_AppendResult(interp, "Resume session failed: ", GET_ERR_REASON(), (char *) NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "SESSION", "FAILED", (char *) NULL); + Tls_Free((char *) statePtr); + return TCL_ERROR; + } + } + + /* Enable Application-Layer Protocol Negotiation. Examples are: http/1.0, + http/1.1, h2, h3, ftp, imap, pop3, xmpp-client, xmpp-server, mqtt, irc, etc. */ + if (alpn) { + /* Convert a TCL list into a protocol-list in wire-format */ + unsigned char *protos, *p; + unsigned int protos_len = 0; + Tcl_Size cnt, i; + int j; + Tcl_Obj **list; + + if (Tcl_ListObjGetElements(interp, alpn, &cnt, &list) != TCL_OK) { + Tls_Free((char *) statePtr); + return TCL_ERROR; + } + + /* Determine the memory required for the protocol-list */ + for (i = 0; i < cnt; i++) { + Tcl_GetStringFromObj(list[i], &len); + if (len > 255) { + Tcl_AppendResult(interp, "ALPN protocol names too long", (char *) NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "ALPN", "FAILED", (char *) NULL); + Tls_Free((char *) statePtr); + return TCL_ERROR; + } + protos_len += 1 + (int) len; + } + + /* Build the complete protocol-list */ + protos = ckalloc(protos_len); + /* protocol-lists consist of 8-bit length-prefixed, byte strings */ + for (j = 0, p = protos; j < cnt; j++) { + char *str = Tcl_GetStringFromObj(list[j], &len); + *p++ = (unsigned char) len; + memcpy(p, str, (size_t) len); + p += len; + } + + /* SSL_set_alpn_protos makes a copy of the protocol-list */ + /* Note: This functions reverses the return value convention */ + if (SSL_set_alpn_protos(statePtr->ssl, protos, protos_len)) { + Tcl_AppendResult(interp, "Set ALPN protocols failed: ", GET_ERR_REASON(), (char *) NULL); + Tcl_SetErrorCode(interp, "TLS", "IMPORT", "ALPN", "FAILED", (char *) NULL); + Tls_Free((char *) statePtr); + ckfree(protos); + return TCL_ERROR; + } + + /* Store protocols list */ + statePtr->protos = protos; + statePtr->protos_len = protos_len; + } else { + statePtr->protos = NULL; + statePtr->protos_len = 0; + } /* * SSL Callbacks */ - SSL_set_app_data(statePtr->ssl, (void *)statePtr); /* point back to us */ - SSL_set_verify(statePtr->ssl, verify, VerifyCallback); + SSL_set_info_callback(statePtr->ssl, InfoCallback); - SSL_CTX_set_info_callback(statePtr->ctx, InfoCallback); + /* Callback for observing protocol messages */ +#ifndef OPENSSL_NO_SSL_TRACE + /* void SSL_CTX_set_msg_callback_arg(statePtr->ctx, (void *)statePtr); + void SSL_CTX_set_msg_callback(statePtr->ctx, MessageCallback); */ + SSL_set_msg_callback_arg(statePtr->ssl, (void *)statePtr); + SSL_set_msg_callback(statePtr->ssl, MessageCallback); +#endif /* Create Tcl_Channel BIO Handler */ statePtr->p_bio = BIO_new_tcl(statePtr, BIO_NOCLOSE); statePtr->bio = BIO_new(BIO_f_ssl()); if (server) { + /* Server callbacks */ + SSL_CTX_set_tlsext_servername_arg(statePtr->ctx, (void *)statePtr); + SSL_CTX_set_tlsext_servername_callback(statePtr->ctx, SNICallback); + SSL_CTX_set_client_hello_cb(statePtr->ctx, HelloCallback, (void *)statePtr); + if (statePtr->protos != NULL) { + SSL_CTX_set_alpn_select_cb(statePtr->ctx, ALPNCallback, (void *)statePtr); +#ifdef USE_NPN + if (tls1_2 == 0 && tls1_3 == 0) { + SSL_CTX_set_next_protos_advertised_cb(statePtr->ctx, NPNCallback, (void *)statePtr); + } +#endif + } + + /* Enable server to send cert request after handshake (TLS 1.3 only) */ + /* A write operation must take place for the Certificate Request to be + sent to the client, this can be done with SSL_do_handshake(). */ + if (request && post_handshake && tls1_3) { + SSL_verify_client_post_handshake(statePtr->ssl); + } + + /* set automatic curve selection */ + SSL_set_ecdh_auto(statePtr->ssl, 1); + + /* Set server mode */ statePtr->flags |= TLS_TCL_SERVER; SSL_set_accept_state(statePtr->ssl); } else { + /* Client callbacks */ +#ifdef USE_NPN + if (statePtr->protos != NULL && tls1_2 == 0 && tls1_3 == 0) { + SSL_CTX_set_next_proto_select_cb(statePtr->ctx, ALPNCallback, (void *)statePtr); + } +#endif + + /* Session caching */ + SSL_CTX_set_session_cache_mode(statePtr->ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE); + SSL_CTX_sess_set_new_cb(statePtr->ctx, SessionCallback); + + /* Enable post handshake Authentication extension. TLS 1.3 only, not http/2. */ + if (request && post_handshake) { + SSL_set_post_handshake_auth(statePtr->ssl, 1); + } + + /* Set client mode */ SSL_set_connect_state(statePtr->ssl); } SSL_set_bio(statePtr->ssl, statePtr->p_bio, statePtr->p_bio); BIO_set_ssl(statePtr->bio, statePtr->ssl, BIO_NOCLOSE); /* * End of SSL Init */ dprintf("Returning %s", Tcl_GetChannelName(statePtr->self)); - Tcl_SetResult(interp, (char *) Tcl_GetChannelName(statePtr->self), - TCL_VOLATILE); + Tcl_SetResult(interp, (char *) Tcl_GetChannelName(statePtr->self), TCL_VOLATILE); + return TCL_OK; } /* *------------------------------------------------------------------- @@ -997,19 +1667,14 @@ * Side effects: * May modify the behavior of an IO channel. * *------------------------------------------------------------------- */ - static int -UnimportObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ +UnimportObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Channel chan; /* The channel to set a mode on. */ + (void) clientData; dprintf("Called"); if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); @@ -1019,18 +1684,17 @@ chan = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL); if (chan == (Tcl_Channel) NULL) { return TCL_ERROR; } - /* - * Make sure to operate on the topmost channel - */ + /* Make sure to operate on the topmost channel */ chan = Tcl_GetTopChannel(chan); if (Tcl_GetChannelType(chan) != Tls_ChannelType()) { Tcl_AppendResult(interp, "bad channel \"", Tcl_GetChannelName(chan), - "\": not a TLS channel", (char *)NULL); + "\": not a TLS channel", NULL); + Tcl_SetErrorCode(interp, "TLS", "UNIMPORT", "CHANNEL", "INVALID", (char *) NULL); return TCL_ERROR; } if (Tcl_UnstackChannel(interp, chan) == TCL_ERROR) { return TCL_ERROR; @@ -1050,249 +1714,259 @@ * Side effects: * constructs SSL context (CTX) * *------------------------------------------------------------------- */ - static SSL_CTX * -CTX_Init( - State *statePtr, - TCL_UNUSED(int) /* isServer */, - int proto, - char *keyfile, - char *certfile, - unsigned char *key, - unsigned char *cert, - int key_len, - int cert_len, - char *CAdir, - char *CAfile, - char *ciphers, - char *DHparams) -{ +CTX_Init(State *statePtr, int isServer, int proto, char *keyfile, char *certfile, + unsigned char *key, unsigned char *cert, int key_len, int cert_len, char *CApath, + char *CAfile, char *ciphers, char *ciphersuites, int level, char *DHparams) { Tcl_Interp *interp = statePtr->interp; SSL_CTX *ctx = NULL; Tcl_DString ds; Tcl_DString ds1; - int off = 0; + int off = 0, abort = 0; int load_private_key; const SSL_METHOD *method; dprintf("Called"); if (!proto) { - Tcl_AppendResult(interp, "no valid protocol selected", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "no valid protocol selected", (char *) NULL); + return NULL; } /* create SSL context */ -#if defined(NO_SSL2) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined(NO_SSL2) || defined(OPENSSL_NO_SSL2) if (ENABLED(proto, TLS_PROTO_SSL2)) { - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "SSL2 protocol not supported", (char *) NULL); + return NULL; } #endif -#if defined(NO_SSL3) +#if defined(NO_SSL3) || defined(OPENSSL_NO_SSL3) if (ENABLED(proto, TLS_PROTO_SSL3)) { - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "SSL3 protocol not supported", (char *) NULL); + return NULL; } #endif -#if defined(NO_TLS1) +#if defined(NO_TLS1) || defined(OPENSSL_NO_TLS1) if (ENABLED(proto, TLS_PROTO_TLS1)) { - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "TLS 1.0 protocol not supported", (char *) NULL); + return NULL; } #endif -#if defined(NO_TLS1_1) +#if defined(NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1) if (ENABLED(proto, TLS_PROTO_TLS1_1)) { - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "TLS 1.1 protocol not supported", (char *) NULL); + return NULL; } #endif -#if defined(NO_TLS1_2) +#if defined(NO_TLS1_2) || defined(OPENSSL_NO_TLS1_2) if (ENABLED(proto, TLS_PROTO_TLS1_2)) { - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "TLS 1.2 protocol not supported", (char *) NULL); + return NULL; } #endif -#if defined(NO_TLS1_3) +#if defined(NO_TLS1_3) || defined(OPENSSL_NO_TLS1_3) if (ENABLED(proto, TLS_PROTO_TLS1_3)) { - Tcl_AppendResult(interp, "protocol not supported", (char *)NULL); - return (SSL_CTX *)0; + Tcl_AppendResult(interp, "TLS 1.3 protocol not supported", (char *) NULL); + return NULL; } #endif + if (proto == 0) { + /* Use full range */ + SSL_CTX_set_min_proto_version(ctx, 0); + SSL_CTX_set_max_proto_version(ctx, 0); + } switch (proto) { -#if !defined(NO_SSL2) - case TLS_PROTO_SSL2: - method = SSLv2_method (); - break; -#endif -#if !defined(NO_SSL3) - case TLS_PROTO_SSL3: - method = SSLv3_method (); - break; -#endif -#if !defined(NO_TLS1) - case TLS_PROTO_TLS1: - method = TLSv1_method (); - break; -#endif -#if !defined(NO_TLS1_1) - case TLS_PROTO_TLS1_1: - method = TLSv1_1_method (); - break; -#endif -#if !defined(NO_TLS1_2) - case TLS_PROTO_TLS1_2: - method = TLSv1_2_method (); - break; -#endif -#if !defined(NO_TLS1_3) - case TLS_PROTO_TLS1_3: - /* - * The version range is constrained below, - * after the context is created. Use the - * generic method here. - */ - method = TLS_method (); +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(NO_SSL2) && !defined(OPENSSL_NO_SSL2) + case TLS_PROTO_SSL2: + method = isServer ? SSLv2_server_method() : SSLv2_client_method(); + break; +#endif +#if !defined(NO_SSL3) && !defined(OPENSSL_NO_SSL3) && !defined(OPENSSL_NO_SSL3_METHOD) + case TLS_PROTO_SSL3: + method = isServer ? SSLv3_server_method() : SSLv3_client_method(); + break; +#endif +#if !defined(NO_TLS1) && !defined(OPENSSL_NO_TLS1) && !defined(OPENSSL_NO_TLS1_METHOD) + case TLS_PROTO_TLS1: + method = isServer ? TLSv1_server_method() : TLSv1_client_method(); + break; +#endif +#if !defined(NO_TLS1_1) && !defined(OPENSSL_NO_TLS1_1) && !defined(OPENSSL_NO_TLS1_1_METHOD) + case TLS_PROTO_TLS1_1: + method = isServer ? TLSv1_1_server_method() : TLSv1_1_client_method(); + break; +#endif +#if !defined(NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_2_METHOD) + case TLS_PROTO_TLS1_2: + method = isServer ? TLSv1_2_server_method() : TLSv1_2_client_method(); + break; +#endif +#if !defined(NO_TLS1_3) && !defined(OPENSSL_NO_TLS1_3) + case TLS_PROTO_TLS1_3: + /* Use the generic method and constraint range after context is created */ + method = isServer ? TLS_server_method() : TLS_client_method(); break; #endif default: -#ifdef HAVE_TLS_METHOD - method = TLS_method (); -#else - method = SSLv23_method (); -#endif -#if !defined(NO_SSL2) + /* Negotiate highest available SSL/TLS version */ + method = isServer ? TLS_server_method() : TLS_client_method(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(NO_SSL2) && !defined(OPENSSL_NO_SSL2) off |= (ENABLED(proto, TLS_PROTO_SSL2) ? 0 : SSL_OP_NO_SSLv2); #endif -#if !defined(NO_SSL3) +#if !defined(NO_SSL3) && !defined(OPENSSL_NO_SSL3) off |= (ENABLED(proto, TLS_PROTO_SSL3) ? 0 : SSL_OP_NO_SSLv3); #endif -#if !defined(NO_TLS1) +#if !defined(NO_TLS1) && !defined(OPENSSL_NO_TLS1) off |= (ENABLED(proto, TLS_PROTO_TLS1) ? 0 : SSL_OP_NO_TLSv1); #endif -#if !defined(NO_TLS1_1) +#if !defined(NO_TLS1_1) && !defined(OPENSSL_NO_TLS1_1) off |= (ENABLED(proto, TLS_PROTO_TLS1_1) ? 0 : SSL_OP_NO_TLSv1_1); #endif -#if !defined(NO_TLS1_2) +#if !defined(NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_2) off |= (ENABLED(proto, TLS_PROTO_TLS1_2) ? 0 : SSL_OP_NO_TLSv1_2); #endif -#if !defined(NO_TLS1_3) +#if !defined(NO_TLS1_3) && !defined(OPENSSL_NO_TLS1_3) off |= (ENABLED(proto, TLS_PROTO_TLS1_3) ? 0 : SSL_OP_NO_TLSv1_3); #endif break; } - ctx = SSL_CTX_new (method); + ERR_clear_error(); + ctx = SSL_CTX_new(method); if (!ctx) { - return(NULL); + return(NULL); + } + + if (getenv(SSLKEYLOGFILE)) { + SSL_CTX_set_keylog_callback(ctx, KeyLogCallback); } -#if !defined(NO_TLS1_3) +#if !defined(NO_TLS1_3) && !defined(OPENSSL_NO_TLS1_3) if (proto == TLS_PROTO_TLS1_3) { - SSL_CTX_set_min_proto_version (ctx, TLS1_3_VERSION); - SSL_CTX_set_max_proto_version (ctx, TLS1_3_VERSION); + SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); + } +#endif + + /* Force cipher selection order by server */ + if (!isServer) { + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OpenSSL_add_all_algorithms(); /* Load ciphers and digests */ +#endif + + SSL_CTX_set_app_data(ctx, (void*)interp); /* remember the interpreter */ + SSL_CTX_set_options(ctx, SSL_OP_ALL); /* all SSL bug workarounds */ + SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); /* disable compression even if supported */ + SSL_CTX_set_options(ctx, off); /* disable protocol versions */ +#if OPENSSL_VERSION_NUMBER < 0x10101000L + SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); /* handle new handshakes in background. On by default in OpenSSL 1.1.1. */ #endif - - SSL_CTX_set_app_data( ctx, (void*)interp); /* remember the interpreter */ - SSL_CTX_set_options( ctx, SSL_OP_ALL); /* all SSL bug workarounds */ - SSL_CTX_set_options( ctx, off); /* all SSL bug workarounds */ - SSL_CTX_sess_set_cache_size( ctx, 128); - - if (ciphers != NULL) - SSL_CTX_set_cipher_list(ctx, ciphers); + SSL_CTX_sess_set_cache_size(ctx, 128); + + /* Set user defined ciphers, cipher suites, and security level */ + if ((ciphers != NULL) && !SSL_CTX_set_cipher_list(ctx, ciphers)) { + Tcl_AppendResult(interp, "Set ciphers failed: No valid ciphers", (char *) NULL); + SSL_CTX_free(ctx); + return NULL; + } + if ((ciphersuites != NULL) && !SSL_CTX_set_ciphersuites(ctx, ciphersuites)) { + Tcl_AppendResult(interp, "Set cipher suites failed: No valid ciphers", (char *) NULL); + SSL_CTX_free(ctx); + return NULL; + } + + /* Set security level */ + if (level > -1 && level < 6) { + /* SSL_set_security_level */ + SSL_CTX_set_security_level(ctx, level); + } /* set some callbacks */ SSL_CTX_set_default_passwd_cb(ctx, PasswordCallback); - -#ifndef BSAFE SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)statePtr); -#endif /* read a Diffie-Hellman parameters file, or use the built-in one */ #ifdef OPENSSL_NO_DH if (DHparams != NULL) { - Tcl_AppendResult(interp, - "DH parameter support not available", (char *) NULL); + Tcl_AppendResult(interp, "DH parameter support not available", (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; } #else { DH* dh; if (DHparams != NULL) { BIO *bio; - Tcl_DStringInit(&ds); bio = BIO_new_file(F2N(DHparams, &ds), "r"); if (!bio) { Tcl_DStringFree(&ds); - Tcl_AppendResult(interp, - "Could not find DH parameters file", (char *) NULL); + Tcl_AppendResult(interp, "Could not find DH parameters file", (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; } dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); Tcl_DStringFree(&ds); if (!dh) { - Tcl_AppendResult(interp, - "Could not read DH parameters from file", (char *) NULL); + Tcl_AppendResult(interp, "Could not read DH parameters from file", (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; } + SSL_CTX_set_tmp_dh(ctx, dh); + DH_free(dh); + } else { - dh = get_dhParams(); + /* Use well known DH parameters that have built-in support in OpenSSL */ + if (!SSL_CTX_set_dh_auto(ctx, 1)) { + Tcl_AppendResult(interp, "Could not enable set DH auto: ", GET_ERR_REASON(), (char *) NULL); + SSL_CTX_free(ctx); + return NULL; + } } - SSL_CTX_set_tmp_dh(ctx, dh); - DH_free(dh); } #endif /* set our certificate */ load_private_key = 0; if (certfile != NULL) { load_private_key = 1; - Tcl_DStringInit(&ds); - - if (SSL_CTX_use_certificate_file(ctx, F2N( certfile, &ds), - SSL_FILETYPE_PEM) <= 0) { + if (SSL_CTX_use_certificate_file(ctx, F2N(certfile, &ds), SSL_FILETYPE_PEM) <= 0) { Tcl_DStringFree(&ds); - Tcl_AppendResult(interp, - "unable to set certificate file ", certfile, ": ", - REASON(), (char *) NULL); + Tcl_AppendResult(interp, "unable to set certificate file ", certfile, ": ", + GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; } + Tcl_DStringFree(&ds); + } else if (cert != NULL) { load_private_key = 1; if (SSL_CTX_use_certificate_ASN1(ctx, cert_len, cert) <= 0) { - Tcl_DStringFree(&ds); - Tcl_AppendResult(interp, - "unable to set certificate: ", - REASON(), (char *) NULL); + Tcl_AppendResult(interp, "unable to set certificate: ", + GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; } } else { certfile = (char*)X509_get_default_cert_file(); - if (SSL_CTX_use_certificate_file(ctx, certfile, - SSL_FILETYPE_PEM) <= 0) { + if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) <= 0) { #if 0 - Tcl_DStringFree(&ds); - Tcl_AppendResult(interp, - "unable to use default certificate file ", certfile, ": ", - REASON(), (char *) NULL); + Tcl_AppendResult(interp, "unable to use default certificate file ", certfile, ": ", + GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; #endif } } /* set our private key */ @@ -1305,72 +1979,91 @@ /* get the private key associated with this certificate */ if (keyfile == NULL) { keyfile = certfile; } - if (SSL_CTX_use_PrivateKey_file(ctx, F2N( keyfile, &ds), SSL_FILETYPE_PEM) <= 0) { - Tcl_DStringFree(&ds); - /* flush the passphrase which might be left in the result */ - Tcl_SetResult(interp, NULL, TCL_STATIC); - Tcl_AppendResult(interp, - "unable to set public key file ", keyfile, " ", - REASON(), (char *) NULL); - SSL_CTX_free(ctx); - return (SSL_CTX *)0; - } - - Tcl_DStringFree(&ds); - } else if (key != NULL) { - if (SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, key,key_len) <= 0) { - Tcl_DStringFree(&ds); - /* flush the passphrase which might be left in the result */ - Tcl_SetResult(interp, NULL, TCL_STATIC); - Tcl_AppendResult(interp, - "unable to set public key: ", - REASON(), (char *) NULL); - SSL_CTX_free(ctx); - return (SSL_CTX *)0; + if (SSL_CTX_use_PrivateKey_file(ctx, F2N(keyfile, &ds), SSL_FILETYPE_PEM) <= 0) { + Tcl_DStringFree(&ds); + /* flush the passphrase which might be left in the result */ + Tcl_SetResult(interp, NULL, TCL_STATIC); + Tcl_AppendResult(interp, "unable to set public key file ", keyfile, " ", + GET_ERR_REASON(), (char *) NULL); + SSL_CTX_free(ctx); + return NULL; + } + Tcl_DStringFree(&ds); + + } else if (key != NULL) { + if (SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, key,key_len) <= 0) { + /* flush the passphrase which might be left in the result */ + Tcl_SetResult(interp, NULL, TCL_STATIC); + Tcl_AppendResult(interp, "unable to set public key: ", GET_ERR_REASON(), (char *) NULL); + SSL_CTX_free(ctx); + return NULL; } } /* Now we know that a key and cert have been set against * the SSL context */ if (!SSL_CTX_check_private_key(ctx)) { - Tcl_AppendResult(interp, - "private key does not match the certificate public key", + Tcl_AppendResult(interp, "private key does not match the certificate public key", (char *) NULL); SSL_CTX_free(ctx); - return (SSL_CTX *)0; - } - } - - /* Set verification CAs */ - Tcl_DStringInit(&ds); - Tcl_DStringInit(&ds1); - if (!SSL_CTX_load_verify_locations(ctx, F2N(CAfile, &ds), F2N(CAdir, &ds1)) || - !SSL_CTX_set_default_verify_paths(ctx)) { -#if 0 - Tcl_DStringFree(&ds); - Tcl_DStringFree(&ds1); - /* Don't currently care if this fails */ - Tcl_AppendResult(interp, "SSL default verify paths: ", - REASON(), (char *) NULL); - SSL_CTX_free(ctx); - return (SSL_CTX *)0; + return NULL; + } + } + + /* Set to use default location and file for Certificate Authority (CA) certificates. The + * verify path and store can be overridden by the SSL_CERT_DIR env var. The verify file can + * be overridden by the SSL_CERT_FILE env var. */ + if (!SSL_CTX_set_default_verify_paths(ctx)) { + abort++; + } + + /* Overrides for the CA verify path and file */ + { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (CApath != NULL || CAfile != NULL) { + Tcl_DString ds1; + if (!SSL_CTX_load_verify_locations(ctx, F2N(CAfile, &ds), F2N(CApath, &ds1))) { + abort++; + } + Tcl_DStringFree(&ds); + Tcl_DStringFree(&ds1); + + /* Set list of CAs to send to client when requesting a client certificate */ + /* https://sourceforge.net/p/tls/bugs/57/ */ + /* XXX:TODO: Let the user supply values here instead of something that exists on the filesystem */ + STACK_OF(X509_NAME) *certNames = SSL_load_client_CA_file(F2N(CAfile, &ds)); + if (certNames != NULL) { + SSL_CTX_set_client_CA_list(ctx, certNames); + } + Tcl_DStringFree(&ds); + } + +#else + if (CApath != NULL) { + if (!SSL_CTX_load_verify_dir(ctx, F2N(CApath, &ds))) { + abort++; + } + Tcl_DStringFree(&ds); + } + if (CAfile != NULL) { + if (!SSL_CTX_load_verify_file(ctx, F2N(CAfile, &ds))) { + abort++; + } + Tcl_DStringFree(&ds); + + /* Set list of CAs to send to client when requesting a client certificate */ + STACK_OF(X509_NAME) *certNames = SSL_load_client_CA_file(F2N(CAfile, &ds)); + if (certNames != NULL) { + SSL_CTX_set_client_CA_list(ctx, certNames); + } + Tcl_DStringFree(&ds); + } #endif } - /* https://sourceforge.net/p/tls/bugs/57/ */ - /* XXX:TODO: Let the user supply values here instead of something that exists on the filesystem */ - if ( CAfile != NULL ) { - STACK_OF(X509_NAME) *certNames = SSL_load_client_CA_file( F2N(CAfile, &ds) ); - if ( certNames != NULL ) { - SSL_CTX_set_client_CA_list(ctx, certNames ); - } - } - - Tcl_DStringFree(&ds); - Tcl_DStringFree(&ds1); return ctx; } /* *------------------------------------------------------------------- @@ -1384,86 +2077,356 @@ * None. * *------------------------------------------------------------------- */ static int -StatusObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ +StatusObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { State *statePtr; X509 *peer; Tcl_Obj *objPtr; Tcl_Channel chan; char *channelName, *ciphers; int mode; + const unsigned char *proto; + unsigned int len; + int nid, res; + (void) clientData; dprintf("Called"); - switch (objc) { - case 2: - channelName = Tcl_GetString(objv[1]); - break; - - case 3: - if (!strcmp (Tcl_GetString (objv[1]), "-local")) { - channelName = Tcl_GetString(objv[2]); - break; - } - /* fallthrough */ - default: - Tcl_WrongNumArgs(interp, 1, objv, "?-local? channel"); - return TCL_ERROR; - } - + if (objc < 2 || objc > 3 || (objc == 3 && !strcmp(Tcl_GetString(objv[1]), "-local"))) { + Tcl_WrongNumArgs(interp, 1, objv, "?-local? channel"); + return TCL_ERROR; + } + + /* Get channel Id */ + channelName = Tcl_GetStringFromObj(objv[(objc == 2 ? 1 : 2)], (Tcl_Size *) NULL); chan = Tcl_GetChannel(interp, channelName, &mode); if (chan == (Tcl_Channel) NULL) { return TCL_ERROR; } - /* - * Make sure to operate on the topmost channel - */ + + /* Make sure to operate on the topmost channel */ chan = Tcl_GetTopChannel(chan); if (Tcl_GetChannelType(chan) != Tls_ChannelType()) { Tcl_AppendResult(interp, "bad channel \"", Tcl_GetChannelName(chan), - "\": not a TLS channel", (char *)NULL); + "\": not a TLS channel", NULL); + Tcl_SetErrorCode(interp, "TLS", "STATUS", "CHANNEL", "INVALID", (char *) NULL); return TCL_ERROR; } statePtr = (State *) Tcl_GetChannelInstanceData(chan); + + /* Get certificate for peer or self */ if (objc == 2) { peer = SSL_get_peer_certificate(statePtr->ssl); } else { peer = SSL_get_certificate(statePtr->ssl); } + /* Get X509 certificate info */ if (peer) { objPtr = Tls_NewX509Obj(interp, peer); - if (objc == 2) { X509_free(peer); } + if (objc == 2) { + X509_free(peer); + peer = NULL; + } } else { objPtr = Tcl_NewListObj(0, NULL); } - Tcl_ListObjAppendElement (interp, objPtr, - Tcl_NewStringObj ("sbits", -1)); - Tcl_ListObjAppendElement (interp, objPtr, - Tcl_NewIntObj (SSL_get_cipher_bits (statePtr->ssl, NULL))); + /* Peer name */ + LAPPEND_STR(interp, objPtr, "peername", SSL_get0_peername(statePtr->ssl), -1); + LAPPEND_INT(interp, objPtr, "sbits", SSL_get_cipher_bits(statePtr->ssl, NULL)); ciphers = (char*)SSL_get_cipher(statePtr->ssl); - if (ciphers != NULL && strcmp(ciphers, "(NONE)")!=0) { - Tcl_ListObjAppendElement(interp, objPtr, - Tcl_NewStringObj("cipher", -1)); - Tcl_ListObjAppendElement(interp, objPtr, - Tcl_NewStringObj(SSL_get_cipher(statePtr->ssl), -1)); - } - - Tcl_ListObjAppendElement(interp, objPtr, - Tcl_NewStringObj("version", -1)); - Tcl_ListObjAppendElement(interp, objPtr, - Tcl_NewStringObj(SSL_get_version(statePtr->ssl), -1)); - - Tcl_SetObjResult( interp, objPtr); + LAPPEND_STR(interp, objPtr, "cipher", ciphers, -1); + + /* Verify the X509 certificate presented by the peer */ + LAPPEND_STR(interp, objPtr, "verifyResult", + X509_verify_cert_error_string(SSL_get_verify_result(statePtr->ssl)), -1); + + /* Verify mode */ + mode = SSL_get_verify_mode(statePtr->ssl); + if (mode && SSL_VERIFY_NONE) { + LAPPEND_STR(interp, objPtr, "verifyMode", "none", -1); + } else { + Tcl_Obj *listObjPtr = Tcl_NewListObj(0, NULL); + if (mode && SSL_VERIFY_PEER) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("peer", -1)); + } + if (mode && SSL_VERIFY_FAIL_IF_NO_PEER_CERT) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("fail if no peer cert", -1)); + } + if (mode && SSL_VERIFY_CLIENT_ONCE) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("client once", -1)); + } + if (mode && SSL_VERIFY_POST_HANDSHAKE) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("post handshake", -1)); + } + LAPPEND_OBJ(interp, objPtr, "verifyMode", listObjPtr) + } + + /* Verify mode depth */ + LAPPEND_INT(interp, objPtr, "verifyDepth", SSL_get_verify_depth(statePtr->ssl)); + + /* Report the selected protocol as a result of the negotiation */ + SSL_get0_alpn_selected(statePtr->ssl, &proto, &len); + LAPPEND_STR(interp, objPtr, "alpn", (char *)proto, (Tcl_Size) len); + LAPPEND_STR(interp, objPtr, "protocol", SSL_get_version(statePtr->ssl), -1); + + /* Valid for non-RSA signature and TLS 1.3 */ + if (objc == 2) { + res = SSL_get_peer_signature_nid(statePtr->ssl, &nid); + } else { + res = SSL_get_signature_nid(statePtr->ssl, &nid); + } + if (!res) {nid = 0;} + LAPPEND_STR(interp, objPtr, "signatureHashAlgorithm", OBJ_nid2ln(nid), -1); + + if (objc == 2) { + res = SSL_get_peer_signature_type_nid(statePtr->ssl, &nid); + } else { + res = SSL_get_signature_type_nid(statePtr->ssl, &nid); + } + if (!res) {nid = 0;} + LAPPEND_STR(interp, objPtr, "signatureType", OBJ_nid2ln(nid), -1); + + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; +} + +/* + *------------------------------------------------------------------- + * + * ConnectionInfoObjCmd -- return connection info from OpenSSL. + * + * Results: + * A list of connection info + * + *------------------------------------------------------------------- + */ + +static int ConnectionInfoObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + Tcl_Channel chan; /* The channel to set a mode on */ + State *statePtr; /* client state for ssl socket */ + Tcl_Obj *objPtr, *listPtr; + const SSL *ssl; + const SSL_CIPHER *cipher; + const SSL_SESSION *session; + const EVP_MD *md; + (void) clientData; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "channel"); + return(TCL_ERROR); + } + + chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], (Tcl_Size *)NULL), NULL); + if (chan == (Tcl_Channel) NULL) { + return(TCL_ERROR); + } + + /* Make sure to operate on the topmost channel */ + chan = Tcl_GetTopChannel(chan); + if (Tcl_GetChannelType(chan) != Tls_ChannelType()) { + Tcl_AppendResult(interp, "bad channel \"", Tcl_GetChannelName(chan), + "\": not a TLS channel", NULL); + Tcl_SetErrorCode(interp, "TLS", "CONNECTION", "CHANNEL", "INVALID", (char *) NULL); + return(TCL_ERROR); + } + + objPtr = Tcl_NewListObj(0, NULL); + + /* Connection info */ + statePtr = (State *)Tcl_GetChannelInstanceData(chan); + ssl = statePtr->ssl; + if (ssl != NULL) { + /* connection state */ + LAPPEND_STR(interp, objPtr, "state", SSL_state_string_long(ssl), -1); + + /* Get SNI requested server name */ + LAPPEND_STR(interp, objPtr, "servername", SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name), -1); + + /* Get protocol */ + LAPPEND_STR(interp, objPtr, "protocol", SSL_get_version(ssl), -1); + + /* Renegotiation allowed */ + LAPPEND_BOOL(interp, objPtr, "renegotiation_allowed", SSL_get_secure_renegotiation_support((SSL *) ssl)); + + /* Get security level */ + LAPPEND_INT(interp, objPtr, "security_level", SSL_get_security_level(ssl)); + + /* Session info */ + LAPPEND_BOOL(interp, objPtr, "session_reused", SSL_session_reused(ssl)); + + /* Is server info */ + LAPPEND_BOOL(interp, objPtr, "is_server", SSL_is_server(ssl)); + + /* Is DTLS */ + LAPPEND_BOOL(interp, objPtr, "is_dtls", SSL_is_dtls(ssl)); + } + + /* Cipher info */ + cipher = SSL_get_current_cipher(ssl); + if (cipher != NULL) { + char buf[BUFSIZ] = {0}; + int bits, alg_bits; + + /* Cipher name */ + LAPPEND_STR(interp, objPtr, "cipher", SSL_CIPHER_get_name(cipher), -1); + + /* RFC name of cipher */ + LAPPEND_STR(interp, objPtr, "standard_name", SSL_CIPHER_standard_name(cipher), -1); + + /* OpenSSL name of cipher */ + LAPPEND_STR(interp, objPtr, "openssl_name", OPENSSL_cipher_name(SSL_CIPHER_standard_name(cipher)), -1); + + /* number of secret bits used for cipher */ + bits = SSL_CIPHER_get_bits(cipher, &alg_bits); + LAPPEND_INT(interp, objPtr, "secret_bits", bits); + LAPPEND_INT(interp, objPtr, "algorithm_bits", alg_bits); + /* alg_bits is actual key secret bits. If use bits and secret (algorithm) bits differ, + the rest of the bits are fixed, i.e. for limited export ciphers (bits < 56) */ + + /* Indicates which SSL/TLS protocol version first defined the cipher */ + LAPPEND_STR(interp, objPtr, "min_version", SSL_CIPHER_get_version(cipher), -1); + + /* Cipher NID */ + LAPPEND_STR(interp, objPtr, "cipherNID", (char *)OBJ_nid2ln(SSL_CIPHER_get_cipher_nid(cipher)), -1); + LAPPEND_STR(interp, objPtr, "digestNID", (char *)OBJ_nid2ln(SSL_CIPHER_get_digest_nid(cipher)), -1); + LAPPEND_STR(interp, objPtr, "keyExchangeNID", (char *)OBJ_nid2ln(SSL_CIPHER_get_kx_nid(cipher)), -1); + LAPPEND_STR(interp, objPtr, "authenticationNID", (char *)OBJ_nid2ln(SSL_CIPHER_get_auth_nid(cipher)), -1); + + /* message authentication code - Cipher is AEAD (e.g. GCM or ChaCha20/Poly1305) or not */ + /* Authenticated Encryption with associated data (AEAD) check */ + LAPPEND_BOOL(interp, objPtr, "cipher_is_aead", SSL_CIPHER_is_aead(cipher)); + + /* Digest used during the SSL/TLS handshake when using the cipher. */ + md = SSL_CIPHER_get_handshake_digest(cipher); + LAPPEND_STR(interp, objPtr, "handshake_digest", (char *)EVP_MD_name(md), -1); + + /* Get OpenSSL-specific ID, not IANA ID */ + LAPPEND_INT(interp, objPtr, "cipher_id", (int) SSL_CIPHER_get_id(cipher)); + + /* Two-byte ID used in the TLS protocol of the given cipher */ + LAPPEND_INT(interp, objPtr, "protocol_id", (int) SSL_CIPHER_get_protocol_id(cipher)); + + /* Textual description of the cipher */ + if (SSL_CIPHER_description(cipher, buf, sizeof(buf)) != NULL) { + LAPPEND_STR(interp, objPtr, "description", buf, -1); + } + } + + /* Session info */ + session = SSL_get_session(ssl); + if (session != NULL) { + const unsigned char *ticket; + size_t len2; + unsigned int ulen; + const unsigned char *session_id, *proto; + unsigned char buffer[SSL_MAX_MASTER_KEY_LENGTH]; + + /* Report the selected protocol as a result of the ALPN negotiation */ + SSL_SESSION_get0_alpn_selected(session, &proto, &len2); + LAPPEND_STR(interp, objPtr, "alpn", (char *) proto, (Tcl_Size) len2); + + /* Report the selected protocol as a result of the NPN negotiation */ +#ifdef USE_NPN + SSL_get0_next_proto_negotiated(ssl, &proto, &ulen); + LAPPEND_STR(interp, objPtr, "npn", (char *) proto, (Tcl_Size) ulen); +#endif + + /* Resumable session */ + LAPPEND_BOOL(interp, objPtr, "resumable", SSL_SESSION_is_resumable(session)); + + /* Session start time (seconds since epoch) */ + LAPPEND_LONG(interp, objPtr, "start_time", SSL_SESSION_get_time(session)); + + /* Timeout value - SSL_CTX_get_timeout (in seconds) */ + LAPPEND_LONG(interp, objPtr, "timeout", SSL_SESSION_get_timeout(session)); + + /* Session id - TLSv1.2 and below only */ + session_id = SSL_SESSION_get_id(session, &ulen); + LAPPEND_BARRAY(interp, objPtr, "session_id", session_id, (Tcl_Size) ulen); + + /* Session context */ + session_id = SSL_SESSION_get0_id_context(session, &ulen); + LAPPEND_BARRAY(interp, objPtr, "session_context", session_id, (Tcl_Size) ulen); + + /* Session ticket - client only */ + SSL_SESSION_get0_ticket(session, &ticket, &len2); + LAPPEND_BARRAY(interp, objPtr, "session_ticket", ticket, (Tcl_Size) len2); + + /* Session ticket lifetime hint (in seconds) */ + LAPPEND_LONG(interp, objPtr, "lifetime", SSL_SESSION_get_ticket_lifetime_hint(session)); + + /* Ticket app data */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + SSL_SESSION_get0_ticket_appdata((SSL_SESSION *) session, &ticket, &len2); + LAPPEND_BARRAY(interp, objPtr, "ticket_app_data", ticket, (Tcl_Size) len2); +#endif + + /* Get master key */ + len2 = SSL_SESSION_get_master_key(session, buffer, SSL_MAX_MASTER_KEY_LENGTH); + LAPPEND_BARRAY(interp, objPtr, "master_key", buffer, (Tcl_Size) len2); + + /* Compression id */ + unsigned int id = SSL_SESSION_get_compress_id(session); + LAPPEND_STR(interp, objPtr, "compression_id", id == 1 ? "zlib" : "none", -1); + } + + /* Compression info */ + if (ssl != NULL) { +#ifdef HAVE_SSL_COMPRESSION + const COMP_METHOD *comp, *expn; + comp = SSL_get_current_compression(ssl); + expn = SSL_get_current_expansion(ssl); + + LAPPEND_STR(interp, objPtr, "compression", comp ? SSL_COMP_get_name(comp) : "none", -1); + LAPPEND_STR(interp, objPtr, "expansion", expn ? SSL_COMP_get_name(expn) : "none", -1); +#else + LAPPEND_STR(interp, objPtr, "compression", "none", -1); + LAPPEND_STR(interp, objPtr, "expansion", "none", -1); +#endif + } + + /* Server info */ + { + long mode = SSL_CTX_get_session_cache_mode(statePtr->ctx); + char *msg; + + if (mode & SSL_SESS_CACHE_OFF) { + msg = "off"; + } else if (mode & SSL_SESS_CACHE_CLIENT) { + msg = "client"; + } else if (mode & SSL_SESS_CACHE_SERVER) { + msg = "server"; + } else if (mode & SSL_SESS_CACHE_BOTH) { + msg = "both"; + } else { + msg = "unknown"; + } + LAPPEND_STR(interp, objPtr, "session_cache_mode", msg, -1); + } + + /* CA List */ + /* IF not a server, same as SSL_get0_peer_CA_list. If server same as SSL_CTX_get_client_CA_list */ + listPtr = Tcl_NewListObj(0, NULL); + STACK_OF(X509_NAME) *ca_list; + if ((ca_list = SSL_get_client_CA_list(ssl)) != NULL) { + char buffer[BUFSIZ]; + for (int i = 0; i < sk_X509_NAME_num(ca_list); i++) { + X509_NAME *name = sk_X509_NAME_value(ca_list, i); + if (name) { + X509_NAME_oneline(name, buffer, BUFSIZ); + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(buffer, -1)); + } + } + } + LAPPEND_OBJ(interp, objPtr, "caList", listPtr); + LAPPEND_INT(interp, objPtr, "caListCount", sk_X509_NAME_num(ca_list)); + + Tcl_SetObjResult(interp, objPtr); return TCL_OK; } /* *------------------------------------------------------------------- @@ -1477,23 +2440,21 @@ * None. * *------------------------------------------------------------------- */ static int -VersionObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - TCL_UNUSED(int) /* objc */, - TCL_UNUSED(Tcl_Obj *const *) /* objv */) -{ +VersionObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *objPtr; + (void) clientData; + (void) objc; + (void) objv; dprintf("Called"); objPtr = Tcl_NewStringObj(OPENSSL_VERSION_TEXT, -1); - Tcl_SetObjResult(interp, objPtr); + return TCL_OK; } /* *------------------------------------------------------------------- @@ -1507,44 +2468,53 @@ * None. * *------------------------------------------------------------------- */ static int -MiscObjCmd( - TCL_UNUSED(void *), - Tcl_Interp *interp, - int objc, - Tcl_Obj *const objv[]) -{ - static const char *commands [] = { "req", NULL }; - enum command { C_REQ, C_DUMMY }; - int cmd; +MiscObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + static const char *commands [] = { "req", "strreq", NULL }; + enum command { C_REQ, C_STRREQ, C_DUMMY }; + Tcl_Size cmd; + int isStr; + char buffer[16384]; + (void) clientData; dprintf("Called"); if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?"); return TCL_ERROR; } - if (Tcl_GetIndexFromObj(interp, objv[1], commands, - "command", 0,&cmd) != TCL_OK) { + if (Tcl_GetIndexFromObj(interp, objv[1], commands, "command", 0, &cmd) != TCL_OK) { return TCL_ERROR; } + ERR_clear_error(); + + isStr = (cmd == C_STRREQ); switch ((enum command) cmd) { - case C_REQ: { + case C_REQ: + case C_STRREQ: { EVP_PKEY *pkey=NULL; X509 *cert=NULL; X509_NAME *name=NULL; Tcl_Obj **listv; - Tcl_Size listc,i; + Tcl_Size listc; + int i; BIO *out=NULL; - const char *k_C="",*k_ST="",*k_L="",*k_O="",*k_OU="",*k_CN="",*k_Email=""; + char *k_C="",*k_ST="",*k_L="",*k_O="",*k_OU="",*k_CN="",*k_Email=""; char *keyout,*pemout,*str; int keysize,serial=0,days=365; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIGNUM *bne = NULL; + RSA *rsa = NULL; +#else + EVP_PKEY_CTX *ctx = NULL; +#endif if ((objc<5) || (objc>6)) { Tcl_WrongNumArgs(interp, 2, objv, "keysize keyfile certfile ?info?"); return TCL_ERROR; } @@ -1552,14 +2522,17 @@ if (Tcl_GetIntFromObj(interp, objv[2], &keysize) != TCL_OK) { return TCL_ERROR; } keyout=Tcl_GetString(objv[3]); pemout=Tcl_GetString(objv[4]); + if (isStr) { + Tcl_SetVar(interp,keyout,"",0); + Tcl_SetVar(interp,pemout,"",0); + } if (objc>=6) { - if (Tcl_ListObjGetElements(interp, objv[5], - &listc, &listv) != TCL_OK) { + if (Tcl_ListObjGetElements(interp, objv[5], &listc, &listv) != TCL_OK) { return TCL_ERROR; } if ((listc%2) != 0) { Tcl_SetResult(interp,"Information list must have even number of arguments",NULL); @@ -1568,13 +2541,10 @@ for (i=0; i<listc; i+=2) { str=Tcl_GetString(listv[i]); if (strcmp(str,"days")==0) { if (Tcl_GetIntFromObj(interp,listv[i+1],&days)!=TCL_OK) return TCL_ERROR; - } else if (strcmp(str,"serial")==0) { - if (Tcl_GetIntFromObj(interp,listv[i+1],&serial)!=TCL_OK) - return TCL_ERROR; } else if (strcmp(str,"serial")==0) { if (Tcl_GetIntFromObj(interp,listv[i+1],&serial)!=TCL_OK) return TCL_ERROR; } else if (strcmp(str,"C")==0) { k_C=Tcl_GetString(listv[i+1]); @@ -1594,73 +2564,119 @@ Tcl_SetResult(interp,"Unknown parameter",NULL); return TCL_ERROR; } } } - if ((pkey = EVP_PKEY_new()) != NULL) { - if (!EVP_PKEY_assign_RSA(pkey, - RSA_generate_key(keysize, 0x10001, NULL, NULL))) { - Tcl_SetResult(interp,"Error generating private key",NULL); - EVP_PKEY_free(pkey); - return TCL_ERROR; - } - out=BIO_new(BIO_s_file()); - BIO_write_filename(out,keyout); - PEM_write_bio_PrivateKey(out,pkey,NULL,NULL,0,NULL,NULL); - BIO_free_all(out); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + bne = BN_new(); + rsa = RSA_new(); + pkey = EVP_PKEY_new(); + if (bne == NULL || rsa == NULL || pkey == NULL || !BN_set_word(bne,RSA_F4) || + !RSA_generate_key_ex(rsa, keysize, bne, NULL) || !EVP_PKEY_assign_RSA(pkey, rsa)) { + EVP_PKEY_free(pkey); + /* RSA_free(rsa); freed by EVP_PKEY_free */ + BN_free(bne); +#else + pkey = EVP_RSA_gen((unsigned int) keysize); + ctx = EVP_PKEY_CTX_new(pkey,NULL); + if (pkey == NULL || ctx == NULL || !EVP_PKEY_keygen_init(ctx) || + !EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, keysize) || !EVP_PKEY_keygen(ctx, &pkey)) { + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); +#endif + Tcl_SetResult(interp,"Error generating private key",NULL); + return TCL_ERROR; + } else { + if (isStr) { + out=BIO_new(BIO_s_mem()); + PEM_write_bio_PrivateKey(out,pkey,NULL,NULL,0,NULL,NULL); + i=BIO_read(out,buffer,sizeof(buffer)-1); + i=(i<0) ? 0 : i; + buffer[i]='\0'; + Tcl_SetVar(interp,keyout,buffer,0); + BIO_flush(out); + BIO_free(out); + } else { + out=BIO_new(BIO_s_file()); + BIO_write_filename(out,keyout); + PEM_write_bio_PrivateKey(out,pkey,NULL,NULL,0,NULL,NULL); + /* PEM_write_bio_RSAPrivateKey(out, rsa, NULL, NULL, 0, NULL, NULL); */ + BIO_free_all(out); + } if ((cert=X509_new())==NULL) { Tcl_SetResult(interp,"Error generating certificate request",NULL); EVP_PKEY_free(pkey); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BN_free(bne); +#endif return(TCL_ERROR); } X509_set_version(cert,2); ASN1_INTEGER_set(X509_get_serialNumber(cert),serial); - X509_gmtime_adj(X509_get_notBefore(cert),0); - X509_gmtime_adj(X509_get_notAfter(cert),(long)60*60*24*days); + X509_gmtime_adj(X509_getm_notBefore(cert),0); + X509_gmtime_adj(X509_getm_notAfter(cert),(long)60*60*24*days); X509_set_pubkey(cert,pkey); name=X509_get_subject_name(cert); - X509_NAME_add_entry_by_txt(name,"C", MBSTRING_ASC, (unsigned char *) k_C, -1, -1, 0); - X509_NAME_add_entry_by_txt(name,"ST", MBSTRING_ASC, (unsigned char *) k_ST, -1, -1, 0); - X509_NAME_add_entry_by_txt(name,"L", MBSTRING_ASC, (unsigned char *) k_L, -1, -1, 0); - X509_NAME_add_entry_by_txt(name,"O", MBSTRING_ASC, (unsigned char *) k_O, -1, -1, 0); - X509_NAME_add_entry_by_txt(name,"OU", MBSTRING_ASC, (unsigned char *) k_OU, -1, -1, 0); - X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (unsigned char *) k_CN, -1, -1, 0); - X509_NAME_add_entry_by_txt(name,"Email", MBSTRING_ASC, (unsigned char *) k_Email, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"C", MBSTRING_ASC, (const unsigned char *) k_C, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"ST", MBSTRING_ASC, (const unsigned char *) k_ST, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"L", MBSTRING_ASC, (const unsigned char *) k_L, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"O", MBSTRING_ASC, (const unsigned char *) k_O, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"OU", MBSTRING_ASC, (const unsigned char *) k_OU, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (const unsigned char *) k_CN, -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"Email", MBSTRING_ASC, (const unsigned char *) k_Email, -1, -1, 0); X509_set_subject_name(cert,name); - if (!X509_sign(cert,pkey,EVP_md5())) { + if (!X509_sign(cert,pkey,EVP_sha256())) { X509_free(cert); EVP_PKEY_free(pkey); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BN_free(bne); +#endif Tcl_SetResult(interp,"Error signing certificate",NULL); return TCL_ERROR; } - out=BIO_new(BIO_s_file()); - BIO_write_filename(out,pemout); - - PEM_write_bio_X509(out,cert); - BIO_free_all(out); + if (isStr) { + out=BIO_new(BIO_s_mem()); + PEM_write_bio_X509(out,cert); + i=BIO_read(out,buffer,sizeof(buffer)-1); + i=(i<0) ? 0 : i; + buffer[i]='\0'; + Tcl_SetVar(interp,pemout,buffer,0); + BIO_flush(out); + BIO_free(out); + } else { + out=BIO_new(BIO_s_file()); + BIO_write_filename(out,pemout); + PEM_write_bio_X509(out,cert); + BIO_free_all(out); + } X509_free(cert); EVP_PKEY_free(pkey); - } else { - Tcl_SetResult(interp,"Error generating private key",NULL); - return TCL_ERROR; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BN_free(bne); +#endif } } break; default: break; } return TCL_OK; } +/********************/ +/* Init */ +/********************/ + /* *------------------------------------------------------------------- * * Tls_Free -- * @@ -1674,16 +2690,11 @@ * Frees all the state * *------------------------------------------------------------------- */ void -#if TCL_MAJOR_VERSION > 8 -Tls_Free( void *blockPtr ) -#else -Tls_Free( char *blockPtr ) -#endif -{ +Tls_Free(char *blockPtr) { State *statePtr = (State *)blockPtr; dprintf("Called"); Tls_Clean(statePtr); @@ -1717,10 +2728,14 @@ if (statePtr->timer != (Tcl_TimerToken) NULL) { Tcl_DeleteTimerHandler(statePtr->timer); statePtr->timer = NULL; } + if (statePtr->protos) { + ckfree(statePtr->protos); + statePtr->protos = NULL; + } if (statePtr->bio) { /* This will call SSL_shutdown. Bug 1414045 */ dprintf("BIO_free_all(%p)", statePtr->bio); BIO_free_all(statePtr->bio); statePtr->bio = NULL; @@ -1740,14 +2755,24 @@ } if (statePtr->password) { Tcl_DecrRefCount(statePtr->password); statePtr->password = NULL; } + if (statePtr->vcmd) { + Tcl_DecrRefCount(statePtr->vcmd); + statePtr->vcmd = NULL; + } dprintf("Returning"); } +#if TCL_MAJOR_VERSION > 8 +#define MIN_VERSION "9.0" +#else +#define MIN_VERSION "8.5" +#endif + /* *------------------------------------------------------------------- * * Tls_Init -- * @@ -1755,54 +2780,51 @@ * by Tcl when this package is to be added to an interpreter. * * Results: Ssl configured and loaded * * Side effects: - * create the ssl command, initialise ssl context + * create the ssl command, initialize ssl context * *------------------------------------------------------------------- */ - DLLEXPORT int Tls_Init(Tcl_Interp *interp) { - const char tlsTclInitScript[] = { + const char tlsTclInitScript[] = { #include "tls.tcl.h" - 0x00 - }; - - dprintf("Called"); - - /* - * We only support Tcl 8.4 or newer - */ - if ( + 0x00 + }; + + dprintf("Called"); + #ifdef USE_TCL_STUBS - Tcl_InitStubs(interp, "8.6-", 0) -#else - Tcl_PkgRequire(interp, "Tcl", "8.6-", 0) + if (Tcl_InitStubs(interp, MIN_VERSION, 0) == NULL) { + return TCL_ERROR; + } #endif - == NULL) { - return TCL_ERROR; - } - - if (TlsLibInit(0) != TCL_OK) { - Tcl_AppendResult(interp, "could not initialize SSL library", (char *)NULL); - return TCL_ERROR; - } - - Tcl_CreateObjCommand(interp, "tls::ciphers", CiphersObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - Tcl_CreateObjCommand(interp, "tls::handshake", HandshakeObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - Tcl_CreateObjCommand(interp, "tls::import", ImportObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - Tcl_CreateObjCommand(interp, "tls::unimport", UnimportObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - Tcl_CreateObjCommand(interp, "tls::status", StatusObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - Tcl_CreateObjCommand(interp, "tls::version", VersionObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - Tcl_CreateObjCommand(interp, "tls::misc", MiscObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); - - if (interp) { - Tcl_Eval(interp, tlsTclInitScript); - } - - return(Tcl_PkgProvide(interp, "tls", PACKAGE_VERSION)); + if (Tcl_PkgRequire(interp, "Tcl", MIN_VERSION, 0) == NULL) { + return TCL_ERROR; + } + + if (TlsLibInit(0) != TCL_OK) { + Tcl_AppendResult(interp, "could not initialize SSL library", (char *) NULL); + return TCL_ERROR; + } + + Tcl_CreateObjCommand(interp, "tls::ciphers", CiphersObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::connection", ConnectionInfoObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::handshake", HandshakeObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::import", ImportObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::unimport", UnimportObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::status", StatusObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::version", VersionObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::misc", MiscObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::protocols", ProtocolsObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + + if (interp) { + Tcl_Eval(interp, tlsTclInitScript); + } + + return Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION); } /* *------------------------------------------------------* * @@ -1811,22 +2833,21 @@ * ------------------------------------------------* * Standard procedure required by 'load'. * Initializes this extension for a safe interpreter. * ------------------------------------------------* * - * Sideeffects: + * Side effects: * As of 'Tls_Init' * * Result: * A standard Tcl error code. * *------------------------------------------------------* */ - DLLEXPORT int Tls_SafeInit(Tcl_Interp *interp) { - dprintf("Called"); - return(Tls_Init(interp)); + dprintf("Called"); + return(Tls_Init(interp)); } /* *------------------------------------------------------* * @@ -1835,92 +2856,82 @@ * ------------------------------------------------* * Initializes SSL library once per application * ------------------------------------------------* * * Side effects: - * initilizes SSL library + * initializes SSL library * * Result: * none * *------------------------------------------------------* */ static int TlsLibInit(int uninitialize) { - static int initialized = 0; - int status = TCL_OK; -#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - size_t num_locks; -#endif - - if (uninitialize) { - if (!initialized) { - dprintf("Asked to uninitialize, but we are not initialized"); - - return(TCL_OK); - } - - dprintf("Asked to uninitialize"); - -#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - Tcl_MutexLock(&init_mx); - - CRYPTO_set_locking_callback(NULL); - CRYPTO_set_id_callback(NULL); - - if (locks) { - free(locks); - locks = NULL; - locksCount = 0; - } -#endif - initialized = 0; - -#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - Tcl_MutexUnlock(&init_mx); -#endif - - return(TCL_OK); - } - - if (initialized) { - dprintf("Called, but using cached value"); - return(status); - } - - dprintf("Called"); - -#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - Tcl_MutexLock(&init_mx); -#endif - initialized = 1; - -#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) - num_locks = CRYPTO_num_locks(); - locksCount = num_locks; - locks = malloc(sizeof(*locks) * num_locks); - memset(locks, 0, sizeof(*locks) * num_locks); - - CRYPTO_set_locking_callback(CryptoThreadLockCallback); - CRYPTO_set_id_callback(CryptoThreadIdCallback); -#endif - - if (SSL_library_init() != 1) { - status = TCL_ERROR; - goto done; - } - - SSL_load_error_strings(); - ERR_load_crypto_strings(); - - BIO_new_tcl(NULL, 0); - -#if 0 - /* - * XXX:TODO: Remove this code and replace it with a check - * for enough entropy and do not try to create our own - * terrible entropy - */ + static int initialized = 0; + int status = TCL_OK; +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + size_t num_locks; +#endif + + if (uninitialize) { + if (!initialized) { + dprintf("Asked to uninitialize, but we are not initialized"); + + return(TCL_OK); + } + + dprintf("Asked to uninitialize"); + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + Tcl_MutexLock(&init_mx); + + if (locks) { + free(locks); + locks = NULL; + locksCount = 0; + } +#endif + initialized = 0; + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + Tcl_MutexUnlock(&init_mx); +#endif + + return(TCL_OK); + } + + if (initialized) { + dprintf("Called, but using cached value"); + return(status); + } + + dprintf("Called"); + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + Tcl_MutexLock(&init_mx); +#endif + initialized = 1; + +#if defined(OPENSSL_THREADS) && defined(TCL_THREADS) + num_locks = 1; + locksCount = (int) num_locks; + locks = malloc(sizeof(*locks) * num_locks); + memset(locks, 0, sizeof(*locks) * num_locks); +#endif + + /* Initialize BOTH libcrypto and libssl. */ + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS + | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); + + BIO_new_tcl(NULL, 0); + +#if 0 + /* + * XXX:TODO: Remove this code and replace it with a check + * for enough entropy and do not try to create our own + * terrible entropy + */ /* * Seed the random number generator in the SSL library, * using the do/while construct because of the bug note in the * OpenSSL FAQ at http://www.openssl.org/support/faq.html#USER1 * @@ -1936,12 +2947,11 @@ } RAND_seed(rnd_seed, sizeof(rnd_seed)); } while (RAND_status() != 1); #endif -done: #if defined(OPENSSL_THREADS) && defined(TCL_THREADS) Tcl_MutexUnlock(&init_mx); #endif - return(status); + return(status); } Index: generic/tls.h ================================================================== --- generic/tls.h +++ generic/tls.h @@ -13,10 +13,11 @@ * Also work done by the follow people provided the impetus to do this "right":- * tclSSL (Colin McCormack, Shared Technology) * SSLtcl (Peter Antman) * */ + #ifndef _TLS_H #define _TLS_H #include <tcl.h> Index: generic/tlsBIO.c ================================================================== --- generic/tlsBIO.c +++ generic/tlsBIO.c @@ -1,323 +1,325 @@ /* * Copyright (C) 1997-2000 Matt Newman <matt@novadigm.com> * - * Provides BIO layer to interface openssl to Tcl. + * Provides BIO layer to interface OpenSSL to TCL. */ #include "tlsInt.h" -#ifdef TCLTLS_OPENSSL_PRE_1_1_API -#define BIO_get_data(bio) ((bio)->ptr) -#define BIO_get_init(bio) ((bio)->init) -#define BIO_get_shutdown(bio) ((bio)->shutdown) -#define BIO_set_data(bio, val) (bio)->ptr = (val) -#define BIO_set_init(bio, val) (bio)->init = (val) -#define BIO_set_shutdown(bio, val) (bio)->shutdown = (val) - -/* XXX: This assumes the variable being assigned to is BioMethods */ -#define BIO_meth_new(type_, name_) (BIO_METHOD *)Tcl_Alloc(sizeof(BIO_METHOD)); \ - memset(BioMethods, 0, sizeof(BIO_METHOD)); \ - BioMethods->type = type_; \ - BioMethods->name = name_; -#define BIO_meth_set_write(bio, val) (bio)->bwrite = val; -#define BIO_meth_set_read(bio, val) (bio)->bread = val; -#define BIO_meth_set_puts(bio, val) (bio)->bputs = val; -#define BIO_meth_set_ctrl(bio, val) (bio)->ctrl = val; -#define BIO_meth_set_create(bio, val) (bio)->create = val; -#define BIO_meth_set_destroy(bio, val) (bio)->destroy = val; -#endif - -/* - * Forward declarations - */ - -static int BioWrite (BIO *h, const char *buf, int num); -static int BioRead (BIO *h, char *buf, int num); -static int BioPuts (BIO *h, const char *str); -static long BioCtrl (BIO *h, int cmd, long arg1, void *ptr); -static int BioNew (BIO *h); -static int BioFree (BIO *h); - -BIO *BIO_new_tcl(State *statePtr, int flags) { - BIO *bio; - static BIO_METHOD *BioMethods = NULL; -#ifdef TCLTLS_SSL_USE_FASTPATH - Tcl_Channel parentChannel; - const Tcl_ChannelType *parentChannelType; - void *parentChannelFdIn_p, *parentChannelFdOut_p; - int parentChannelFdIn, parentChannelFdOut, parentChannelFd; - int validParentChannelFd; - int tclGetChannelHandleRet; -#endif - - dprintf("BIO_new_tcl() called"); - - if (BioMethods == NULL) { - BioMethods = BIO_meth_new(BIO_TYPE_TCL, "tcl"); - BIO_meth_set_write(BioMethods, BioWrite); - BIO_meth_set_read(BioMethods, BioRead); - BIO_meth_set_puts(BioMethods, BioPuts); - BIO_meth_set_ctrl(BioMethods, BioCtrl); - BIO_meth_set_create(BioMethods, BioNew); - BIO_meth_set_destroy(BioMethods, BioFree); - } - - if (statePtr == NULL) { - dprintf("Asked to setup a NULL state, just creating the initial configuration"); - - return(NULL); - } - -#ifdef TCLTLS_SSL_USE_FASTPATH - /* - * If the channel can be mapped back to a file descriptor, just use the file descriptor - * with the SSL library since it will likely be optimized for this. - */ - parentChannel = Tls_GetParent(statePtr, 0); - parentChannelType = Tcl_GetChannelType(parentChannel); - - validParentChannelFd = 0; - if (strcmp(parentChannelType->typeName, "tcp") == 0) { - tclGetChannelHandleRet = Tcl_GetChannelHandle(parentChannel, TCL_READABLE, &parentChannelFdIn_p); - if (tclGetChannelHandleRet == TCL_OK) { - tclGetChannelHandleRet = Tcl_GetChannelHandle(parentChannel, TCL_WRITABLE, &parentChannelFdOut_p); - if (tclGetChannelHandleRet == TCL_OK) { - parentChannelFdIn = PTR2INT(parentChannelFdIn_p); - parentChannelFdOut = PTR2INT(parentChannelFdOut_p); - if (parentChannelFdIn == parentChannelFdOut) { - parentChannelFd = parentChannelFdIn; - validParentChannelFd = 1; - } - } - } - } - - if (validParentChannelFd) { - dprintf("We found a shortcut, this channel is backed by a socket: %i", parentChannelFdIn); - bio = BIO_new_socket(parentChannelFd, flags); - statePtr->flags |= TLS_TCL_FASTPATH; - return(bio); - } - - dprintf("Falling back to Tcl I/O for this channel"); -#endif - - bio = BIO_new(BioMethods); - BIO_set_data(bio, statePtr); - BIO_set_shutdown(bio, flags); - BIO_set_init(bio, 1); - - return(bio); -} - -static int BioWrite(BIO *bio, const char *buf, int bufLen) { - Tcl_Channel chan; - int ret; - int tclEofChan, tclErrno; - - chan = Tls_GetParent((State *) BIO_get_data(bio), 0); - - dprintf("[chan=%p] BioWrite(%p, <buf>, %d)", (void *)chan, (void *) bio, bufLen); - - ret = Tcl_WriteRaw(chan, buf, bufLen); - - tclEofChan = Tcl_Eof(chan); - tclErrno = Tcl_GetErrno(); - - dprintf("[chan=%p] BioWrite(%d) -> %d [tclEof=%d; tclErrno=%d]", (void *) chan, bufLen, ret, tclEofChan, Tcl_GetErrno()); - - BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); - - if (tclEofChan && ret <= 0) { - dprintf("Got EOF while reading, returning a Connection Reset error which maps to Soft EOF"); - Tcl_SetErrno(ECONNRESET); - ret = 0; - } else if (ret == 0) { - dprintf("Got 0 from Tcl_WriteRaw, and EOF is not set; ret = 0"); - dprintf("Setting retry read flag"); - BIO_set_retry_read(bio); - } else if (ret < 0) { - dprintf("We got some kind of I/O error"); - - if (tclErrno == EAGAIN) { - dprintf("It's EAGAIN"); - } else { - dprintf("It's an unepxected error: %s/%i", Tcl_ErrnoMsg(tclErrno), tclErrno); - } - } else { - dprintf("Successfully wrote some data"); - } - - if (ret != -1 || (ret == -1 && tclErrno == EAGAIN)) { - if (BIO_should_read(bio)) { - dprintf("Setting should retry read flag"); - - BIO_set_retry_read(bio); - } - } - - return(ret); -} - -static int BioRead(BIO *bio, char *buf, int bufLen) { - Tcl_Channel chan; - int ret = 0; - int tclEofChan, tclErrno; - - chan = Tls_GetParent((State *) BIO_get_data(bio), 0); - - dprintf("[chan=%p] BioRead(%p, <buf>, %d)", (void *) chan, (void *) bio, bufLen); - - if (buf == NULL) { - return 0; - } - - ret = Tcl_ReadRaw(chan, buf, bufLen); - - tclEofChan = Tcl_Eof(chan); - tclErrno = Tcl_GetErrno(); - - dprintf("[chan=%p] BioRead(%d) -> %d [tclEof=%d; tclErrno=%d]", (void *) chan, bufLen, ret, tclEofChan, tclErrno); - - BIO_clear_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); - - if (tclEofChan && ret <= 0) { - dprintf("Got EOF while reading, returning a Connection Reset error which maps to Soft EOF"); - Tcl_SetErrno(ECONNRESET); - ret = 0; - } else if (ret == 0) { - dprintf("Got 0 from Tcl_Read or Tcl_ReadRaw, and EOF is not set; ret = 0"); - dprintf("Setting retry read flag"); - BIO_set_retry_read(bio); - } else if (ret < 0) { - dprintf("We got some kind of I/O error"); - - if (tclErrno == EAGAIN) { - dprintf("It's EAGAIN"); - } else { - dprintf("It's an unepxected error: %s/%i", Tcl_ErrnoMsg(tclErrno), tclErrno); - } - } else { - dprintf("Successfully read some data"); - } - - if (ret != -1 || (ret == -1 && tclErrno == EAGAIN)) { - if (BIO_should_write(bio)) { - dprintf("Setting should retry write flag"); - - BIO_set_retry_write(bio); - } - } - - dprintf("BioRead(%p, <buf>, %d) [%p] returning %i", (void *) bio, bufLen, (void *) chan, ret); - - return(ret); +/* Called by SSL_write() */ +static int BioWrite(BIO *bio, const char *buf, int bufLen) { + Tcl_Channel chan; + Tcl_Size ret; + int tclEofChan, tclErrno; + + chan = Tls_GetParent((State *) BIO_get_data(bio), 0); + + dprintf("[chan=%p] BioWrite(%p, <buf>, %d)", (void *)chan, (void *) bio, bufLen); + + ret = Tcl_WriteRaw(chan, buf, (Tcl_Size) bufLen); + + tclEofChan = Tcl_Eof(chan); + tclErrno = Tcl_GetErrno(); + + dprintf("[chan=%p] BioWrite(%d) -> %" TCL_SIZE_MODIFIER "d [tclEof=%d; tclErrno=%d]", + (void *) chan, bufLen, ret, tclEofChan, tclErrno); + + BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); + + if (tclEofChan && ret <= 0) { + dprintf("Got EOF while reading, returning a Connection Reset error which maps to Soft EOF"); + Tcl_SetErrno(ECONNRESET); + ret = 0; + + } else if (ret == 0) { + dprintf("Got 0 from Tcl_WriteRaw, and EOF is not set; ret = 0"); + dprintf("Setting retry read flag"); + BIO_set_retry_read(bio); + + } else if (ret < 0) { + dprintf("We got some kind of I/O error"); + + if (tclErrno == EAGAIN) { + dprintf("It's EAGAIN"); + } else { + dprintf("It's an unexpected error: %s/%i", Tcl_ErrnoMsg(tclErrno), tclErrno); + } + + } else { + dprintf("Successfully wrote %" TCL_SIZE_MODIFIER "d bytes of data", ret); + } + + if (ret != -1 || (ret == -1 && tclErrno == EAGAIN)) { + if (BIO_should_read(bio)) { + dprintf("Setting should retry read flag"); + + BIO_set_retry_read(bio); + } + } + return((int) ret); +} + +/* Called by SSL_read()*/ +static int BioRead(BIO *bio, char *buf, int bufLen) { + Tcl_Channel chan; + Tcl_Size ret = 0; + int tclEofChan, tclErrno; + + chan = Tls_GetParent((State *) BIO_get_data(bio), 0); + + dprintf("[chan=%p] BioRead(%p, <buf>, %d)", (void *) chan, (void *) bio, bufLen); + + if (buf == NULL) { + return 0; + } + + ret = Tcl_ReadRaw(chan, buf, (Tcl_Size) bufLen); + + tclEofChan = Tcl_Eof(chan); + tclErrno = Tcl_GetErrno(); + + dprintf("[chan=%p] BioRead(%d) -> %" TCL_SIZE_MODIFIER "d [tclEof=%d; tclErrno=%d]", + (void *) chan, bufLen, ret, tclEofChan, tclErrno); + + BIO_clear_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); + + if (tclEofChan && ret <= 0) { + dprintf("Got EOF while reading, returning a Connection Reset error which maps to Soft EOF"); + Tcl_SetErrno(ECONNRESET); + ret = 0; + + } else if (ret == 0) { + dprintf("Got 0 from Tcl_Read or Tcl_ReadRaw, and EOF is not set; ret = 0"); + dprintf("Setting retry read flag"); + BIO_set_retry_read(bio); + + } else if (ret < 0) { + dprintf("We got some kind of I/O error"); + + if (tclErrno == EAGAIN) { + dprintf("It's EAGAIN"); + } else { + dprintf("It's an unexpected error: %s/%i", Tcl_ErrnoMsg(tclErrno), tclErrno); + } + + } else { + dprintf("Successfully read %" TCL_SIZE_MODIFIER "d bytes of data", ret); + } + + if (ret != -1 || (ret == -1 && tclErrno == EAGAIN)) { + if (BIO_should_write(bio)) { + dprintf("Setting should retry write flag"); + + BIO_set_retry_write(bio); + } + } + + dprintf("BioRead(%p, <buf>, %d) [%p] returning %" TCL_SIZE_MODIFIER "d", (void *) bio, + bufLen, (void *) chan, ret); + + return((int) ret); } static int BioPuts(BIO *bio, const char *str) { - dprintf("BioPuts(%p, <string:%p>) called", bio, str); + dprintf("BioPuts(%p, <string:%p>) called", bio, str); - return BioWrite(bio, str, (int) strlen(str)); + return(BioWrite(bio, str, (int) strlen(str))); } static long BioCtrl(BIO *bio, int cmd, long num, void *ptr) { - Tcl_Channel chan; - long ret = 1; - - chan = Tls_GetParent((State *) BIO_get_data(bio), 0); - - dprintf("BioCtrl(%p, 0x%x, 0x%lx, %p)", bio, cmd, num, ptr); - - switch (cmd) { - case BIO_CTRL_RESET: - dprintf("Got BIO_CTRL_RESET"); - num = 0; - ret = 0; - break; - case BIO_C_FILE_SEEK: - dprintf("Got BIO_C_FILE_SEEK"); - ret = 0; - break; - case BIO_C_FILE_TELL: - dprintf("Got BIO_C_FILE_TELL"); - ret = 0; - break; - case BIO_CTRL_INFO: - dprintf("Got BIO_CTRL_INFO"); - ret = 1; - break; - case BIO_C_SET_FD: - dprintf("Unsupported call: BIO_C_SET_FD"); - ret = -1; - break; - case BIO_C_GET_FD: - dprintf("Unsupported call: BIO_C_GET_FD"); - ret = -1; - break; - case BIO_CTRL_GET_CLOSE: - dprintf("Got BIO_CTRL_CLOSE"); - ret = BIO_get_shutdown(bio); - break; - case BIO_CTRL_SET_CLOSE: - dprintf("Got BIO_SET_CLOSE"); - BIO_set_shutdown(bio, num); - break; - case BIO_CTRL_EOF: - dprintf("Got BIO_CTRL_EOF"); - ret = Tcl_Eof(chan); - break; - case BIO_CTRL_PENDING: - dprintf("Got BIO_CTRL_PENDING"); - ret = ((chan) ? Tcl_InputBuffered(chan) : 0); - dprintf("BIO_CTRL_PENDING(%d)", (int) ret); - break; - case BIO_CTRL_WPENDING: - dprintf("Got BIO_CTRL_WPENDING"); - ret = 0; - break; - case BIO_CTRL_DUP: - dprintf("Got BIO_CTRL_DUP"); - break; - case BIO_CTRL_FLUSH: - dprintf("Got BIO_CTRL_FLUSH"); - ret = ((Tcl_WriteRaw(chan, "", 0) >= 0) ? 1 : -1); - dprintf("BIO_CTRL_FLUSH returning value %li", ret); - break; - default: - dprintf("Got unknown control command (%i)", cmd); - ret = -2; - break; - } - - return(ret); + Tcl_Channel chan; + long ret = 1; + + chan = Tls_GetParent((State *) BIO_get_data(bio), 0); + + dprintf("BioCtrl(%p, 0x%x, 0x%lx, %p)", (void *) bio, cmd, num, ptr); + + switch (cmd) { + case BIO_CTRL_RESET: + dprintf("Got BIO_CTRL_RESET"); + num = 0; + ret = 0; + break; + case BIO_C_FILE_SEEK: + dprintf("Got BIO_C_FILE_SEEK"); + ret = 0; + break; + case BIO_C_FILE_TELL: + dprintf("Got BIO_C_FILE_TELL"); + ret = 0; + break; + case BIO_CTRL_INFO: + dprintf("Got BIO_CTRL_INFO"); + ret = 1; + break; + case BIO_C_SET_FD: + dprintf("Unsupported call: BIO_C_SET_FD"); + ret = -1; + break; + case BIO_C_GET_FD: + dprintf("Unsupported call: BIO_C_GET_FD"); + ret = -1; + break; + case BIO_CTRL_GET_CLOSE: + dprintf("Got BIO_CTRL_CLOSE"); + ret = BIO_get_shutdown(bio); + break; + case BIO_CTRL_SET_CLOSE: + dprintf("Got BIO_SET_CLOSE"); + BIO_set_shutdown(bio, num); + break; + case BIO_CTRL_EOF: + dprintf("Got BIO_CTRL_EOF"); + ret = ((chan) ? Tcl_Eof(chan) : 1); + break; + case BIO_CTRL_PENDING: + dprintf("Got BIO_CTRL_PENDING"); + ret = ((chan) ? ((Tcl_InputBuffered(chan) ? 1 : 0)) : 0); + dprintf("BIO_CTRL_PENDING(%d)", (int) ret); + break; + case BIO_CTRL_WPENDING: + dprintf("Got BIO_CTRL_WPENDING"); + ret = 0; + break; + case BIO_CTRL_DUP: + dprintf("Got BIO_CTRL_DUP"); + break; + case BIO_CTRL_FLUSH: + dprintf("Got BIO_CTRL_FLUSH"); + ret = ((chan) && (Tcl_WriteRaw(chan, "", 0) >= 0) ? 1 : -1); + dprintf("BIO_CTRL_FLUSH returning value %li", ret); + break; + case BIO_CTRL_PUSH: + dprintf("Got BIO_CTRL_PUSH"); + ret = 0; + break; + case BIO_CTRL_POP: + dprintf("Got BIO_CTRL_POP"); + ret = 0; + break; + case BIO_CTRL_SET: + dprintf("Got BIO_CTRL_SET"); + ret = 0; + break; + case BIO_CTRL_GET : + dprintf("Got BIO_CTRL_GET "); + ret = 0; + break; +#ifdef BIO_CTRL_GET_KTLS_SEND + case BIO_CTRL_GET_KTLS_SEND: + dprintf("Got BIO_CTRL_GET_KTLS_SEND"); + ret = 0; + break; +#endif +#ifdef BIO_CTRL_GET_KTLS_RECV + case BIO_CTRL_GET_KTLS_RECV: + dprintf("Got BIO_CTRL_GET_KTLS_RECV"); + ret = 0; + break; +#endif + default: + dprintf("Got unknown control command (%i)", cmd); + ret = 0; + break; + } + return(ret); } static int BioNew(BIO *bio) { - dprintf("BioNew(%p) called", bio); + dprintf("BioNew(%p) called", bio); - BIO_set_init(bio, 0); - BIO_set_data(bio, NULL); - BIO_clear_flags(bio, -1); - - return(1); + BIO_set_init(bio, 0); + BIO_set_data(bio, NULL); + BIO_clear_flags(bio, -1); + return(1); } static int BioFree(BIO *bio) { - if (bio == NULL) { - return(0); - } - - dprintf("BioFree(%p) called", bio); - - if (BIO_get_shutdown(bio)) { - if (BIO_get_init(bio)) { - /*shutdown(bio->num, 2) */ - /*closesocket(bio->num) */ - } - - BIO_set_init(bio, 0); - BIO_clear_flags(bio, -1); - } - - return(1); + if (bio == NULL) { + return(0); + } + + dprintf("BioFree(%p) called", bio); + + if (BIO_get_shutdown(bio)) { + if (BIO_get_init(bio)) { + /*shutdown(bio->num, 2) */ + /*closesocket(bio->num) */ + } + + BIO_set_init(bio, 0); + BIO_clear_flags(bio, -1); + } + return(1); +} + +BIO *BIO_new_tcl(State *statePtr, int flags) { + BIO *bio; + static BIO_METHOD *BioMethods = NULL; +#ifdef TCLTLS_SSL_USE_FASTPATH + Tcl_Channel parentChannel; + const Tcl_ChannelType *parentChannelType; + void *parentChannelFdIn_p, *parentChannelFdOut_p; + int parentChannelFdIn, parentChannelFdOut, parentChannelFd; + int validParentChannelFd; + int tclGetChannelHandleRet; +#endif + + dprintf("BIO_new_tcl() called"); + + if (BioMethods == NULL) { + BioMethods = BIO_meth_new(BIO_TYPE_TCL, "tcl"); + BIO_meth_set_write(BioMethods, BioWrite); + BIO_meth_set_read(BioMethods, BioRead); + BIO_meth_set_puts(BioMethods, BioPuts); + BIO_meth_set_ctrl(BioMethods, BioCtrl); + BIO_meth_set_create(BioMethods, BioNew); + BIO_meth_set_destroy(BioMethods, BioFree); + } + + if (statePtr == NULL) { + dprintf("Asked to setup a NULL state, just creating the initial configuration"); + + return(NULL); + } + +#ifdef TCLTLS_SSL_USE_FASTPATH + /* + * If the channel can be mapped back to a file descriptor, just use the file descriptor + * with the SSL library since it will likely be optimized for this. + */ + parentChannel = Tls_GetParent(statePtr, 0); + parentChannelType = Tcl_GetChannelType(parentChannel); + + validParentChannelFd = 0; + if (strcmp(parentChannelType->typeName, "tcp") == 0) { + tclGetChannelHandleRet = Tcl_GetChannelHandle(parentChannel, TCL_READABLE, (ClientData) &parentChannelFdIn_p); + if (tclGetChannelHandleRet == TCL_OK) { + tclGetChannelHandleRet = Tcl_GetChannelHandle(parentChannel, TCL_WRITABLE, (ClientData) &parentChannelFdOut_p); + if (tclGetChannelHandleRet == TCL_OK) { + parentChannelFdIn = PTR2INT(parentChannelFdIn_p); + parentChannelFdOut = PTR2INT(parentChannelFdOut_p); + if (parentChannelFdIn == parentChannelFdOut) { + parentChannelFd = parentChannelFdIn; + validParentChannelFd = 1; + } + } + } + } + + if (validParentChannelFd) { + dprintf("We found a shortcut, this channel is backed by a socket: %i", parentChannelFdIn); + bio = BIO_new_socket(parentChannelFd, flags); + statePtr->flags |= TLS_TCL_FASTPATH; + return(bio); + } + + dprintf("Falling back to Tcl I/O for this channel"); +#endif + + bio = BIO_new(BioMethods); + BIO_set_data(bio, statePtr); + BIO_set_shutdown(bio, flags); + BIO_set_init(bio, 1); + return(bio); } Index: generic/tlsIO.c ================================================================== --- generic/tlsIO.c +++ generic/tlsIO.c @@ -10,937 +10,949 @@ * Addition credit is due for Andreas Kupries (a.kupries@westend.com), for * providing the Tcl_ReplaceChannel mechanism and working closely with me * to enhance it to support full fileevent semantics. * * Also work done by the follow people provided the impetus to do this "right": - * tclSSL (Colin McCormack, Shared Technology) - * SSLtcl (Peter Antman) + * tclSSL (Colin McCormack, Shared Technology) + * SSLtcl (Peter Antman) * */ #include "tlsInt.h" +#include <errno.h> /* * Forward declarations */ -static int TlsBlockModeProc (void *instanceData, int mode); -static int TlsCloseProc (void *instanceData, Tcl_Interp *interp); -static int TlsClose2Proc (void *instanceData, Tcl_Interp *interp, int flags); -static int TlsInputProc (void *instanceData, char *buf, int bufSize, int *errorCodePtr); -static int TlsOutputProc (void *instanceData, const char *buf, int toWrite, int *errorCodePtr); -static int TlsGetOptionProc (void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); -static void TlsWatchProc (void *instanceData, int mask); -static int TlsGetHandleProc (void *instanceData, int direction, void **handlePtr); -static int TlsNotifyProc (void *instanceData, int mask); -static void TlsChannelHandlerTimer (void *clientData); - -/* - * TLS Channel Type - */ -static const Tcl_ChannelType tlsChannelType = { - "tls", /* typeName */ - TCL_CHANNEL_VERSION_5, /* version */ - TlsCloseProc, /* closeProc */ - TlsInputProc, /* inputProc */ - TlsOutputProc, /* outputProc */ - 0, /* seekProc */ - 0, /* setOptionProc */ - TlsGetOptionProc, /* getOptionProc */ - TlsWatchProc, /* watchProc */ - TlsGetHandleProc, /* getHandleProc */ - TlsClose2Proc, /* close2Proc */ - TlsBlockModeProc, /* blockModeProc */ - 0, /* flushProc */ - TlsNotifyProc, /* handlerProc */ - 0, /* wideSeekProc */ - 0, /* threadActionProc */ - 0 /* truncateProc */ -}; - - -/* - *------------------------------------------------------------------- - * - * Tls_ChannelType -- - * - * Return the correct TLS channel driver info - * - * Results: - * The correct channel driver for the current version of Tcl. - * - * Side effects: - * None. - * - *------------------------------------------------------------------- - */ -const Tcl_ChannelType *Tls_ChannelType(void) { - return &tlsChannelType; -} +static void TlsChannelHandlerTimer(ClientData clientData); /* *------------------------------------------------------------------- * * TlsBlockModeProc -- * - * This procedure is invoked by the generic IO level + * This procedure is invoked by the generic IO level * to set blocking and nonblocking modes + * * Results: - * 0 if successful, errno when failed. + * 0 if successful or POSIX error code if failed. * * Side effects: - * Sets the device into blocking or nonblocking mode. + * Sets the device into blocking or nonblocking mode. * *------------------------------------------------------------------- */ -static int TlsBlockModeProc(void *instanceData, int mode) { - State *statePtr = (State *) instanceData; - - if (mode == TCL_MODE_NONBLOCKING) { - statePtr->flags |= TLS_TCL_ASYNC; - } else { - statePtr->flags &= ~(TLS_TCL_ASYNC); - } - - return(0); +static int TlsBlockModeProc(ClientData instanceData, int mode) { + State *statePtr = (State *) instanceData; + + if (mode == TCL_MODE_NONBLOCKING) { + statePtr->flags |= TLS_TCL_ASYNC; + } else { + statePtr->flags &= ~(TLS_TCL_ASYNC); + } + return(0); } /* *------------------------------------------------------------------- * * TlsCloseProc -- * - * This procedure is invoked by the generic IO level to perform - * channel-type-specific cleanup when a SSL socket based channel - * is closed. + * This procedure is invoked by the generic IO level to perform + * channel-type-specific cleanup when a SSL socket based channel + * is closed. * - * Note: we leave the underlying socket alone, is this right? + * Note: we leave the underlying socket alone, is this right? * * Results: - * 0 if successful, the value of Tcl_GetErrno() if failed. + * 0 if successful or POSIX error code if failed. * * Side effects: - * Closes the socket of the channel. + * Closes the socket of the channel. * *------------------------------------------------------------------- */ -static int TlsCloseProc(void *instanceData, TCL_UNUSED(Tcl_Interp *)) { - State *statePtr = (State *) instanceData; - - dprintf("TlsCloseProc(%p)", statePtr); - - Tls_Clean(statePtr); - Tcl_EventuallyFree(statePtr, Tls_Free); - - dprintf("Returning TCL_OK"); - - return(TCL_OK); -} - -static int TlsClose2Proc(void *instanceData, Tcl_Interp *interp, int flags) { - if (!(flags&(TCL_CLOSE_READ|TCL_CLOSE_WRITE))) { - return TlsCloseProc(instanceData, interp); - } - return EINVAL; +static int TlsCloseProc(ClientData instanceData, Tcl_Interp *interp) { + State *statePtr = (State *) instanceData; + + dprintf("TlsCloseProc(%p)", (void *) statePtr); + + Tls_Clean(statePtr); + Tcl_EventuallyFree((ClientData)statePtr, Tls_Free); + return(0); +} + +static int TlsClose2Proc(ClientData instanceData, /* The socket state. */ + Tcl_Interp *interp, /* For errors - can be NULL. */ + int flags) /* Flags to close read and/or write side of channel */ +{ + State *statePtr = (State *) instanceData; + + dprintf("TlsClose2Proc(%p)", (void *) statePtr); + + if ((flags & (TCL_CLOSE_READ|TCL_CLOSE_WRITE)) == 0) { + return TlsCloseProc(instanceData, interp); + } + return EINVAL; } /* *------------------------------------------------------* * - * Tls_WaitForConnect -- + * Tls_WaitForConnect -- * - * Sideeffects: - * Issues SSL_accept or SSL_connect + * Result: + * 0 if successful, -1 if failed. * - * Result: - * None. + * Side effects: + * Issues SSL_accept or SSL_connect * *------------------------------------------------------* */ int Tls_WaitForConnect(State *statePtr, int *errorCodePtr, int handshakeFailureIsPermanent) { - unsigned long backingError; - int err, rc; - int bioShouldRetry; - - dprintf("WaitForConnect(%p)", statePtr); - dprintFlags(statePtr); - - if (!(statePtr->flags & TLS_TCL_INIT)) { - dprintf("Tls_WaitForConnect called on already initialized channel -- returning with immediate success"); - *errorCodePtr = 0; - return(0); - } - - if (statePtr->flags & TLS_TCL_HANDSHAKE_FAILED) { - /* - * Different types of operations have different requirements - * SSL being established - */ - if (handshakeFailureIsPermanent) { - dprintf("Asked to wait for a TLS handshake that has already failed. Returning fatal error"); - *errorCodePtr = ECONNABORTED; - } else { - dprintf("Asked to wait for a TLS handshake that has already failed. Returning soft error"); - *errorCodePtr = ECONNRESET; - } - return(-1); - } - - for (;;) { - /* Not initialized yet! */ - if (statePtr->flags & TLS_TCL_SERVER) { - dprintf("Calling SSL_accept()"); - - err = SSL_accept(statePtr->ssl); - } else { - dprintf("Calling SSL_connect()"); - - err = SSL_connect(statePtr->ssl); - } - - if (err > 0) { - dprintf("That seems to have gone okay"); - - err = BIO_flush(statePtr->bio); - - if (err <= 0) { - dprintf("Flushing the lower layers failed, this will probably terminate this session"); - } - } - - rc = SSL_get_error(statePtr->ssl, err); - - dprintf("Got error: %i (rc = %i)", err, rc); - - bioShouldRetry = 0; - if (err <= 0) { - if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT || rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) { - bioShouldRetry = 1; - } else if (BIO_should_retry(statePtr->bio)) { - bioShouldRetry = 1; - } else if (rc == SSL_ERROR_SYSCALL && Tcl_GetErrno() == EAGAIN) { - bioShouldRetry = 1; - } - } else { - if (!SSL_is_init_finished(statePtr->ssl)) { - bioShouldRetry = 1; - } - } - - if (bioShouldRetry) { - dprintf("The I/O did not complete -- but we should try it again"); - - if (statePtr->flags & TLS_TCL_ASYNC) { - dprintf("Returning EAGAIN so that it can be retried later"); - - *errorCodePtr = EAGAIN; - - return(-1); - } else { - dprintf("Doing so now"); - - continue; - } - } - - dprintf("We have either completely established the session or completely failed it -- there is no more need to ever retry it though"); - break; - } - - - *errorCodePtr = EINVAL; - - switch (rc) { - case SSL_ERROR_NONE: - /* The connection is up, we are done here */ - dprintf("The connection is up"); - break; - case SSL_ERROR_ZERO_RETURN: - dprintf("SSL_ERROR_ZERO_RETURN: Connect returned an invalid value...") - return(-1); - case SSL_ERROR_SYSCALL: - backingError = ERR_get_error(); - - if (backingError == 0 && err == 0) { - dprintf("EOF reached") - *errorCodePtr = ECONNRESET; - } else if (backingError == 0 && err == -1) { - dprintf("I/O error occured (errno = %lu)", (unsigned long) Tcl_GetErrno()); - *errorCodePtr = Tcl_GetErrno(); - if (*errorCodePtr == ECONNRESET) { - *errorCodePtr = ECONNABORTED; - } - } else { - dprintf("I/O error occured (backingError = %lu)", backingError); - *errorCodePtr = backingError; - if (*errorCodePtr == ECONNRESET) { - *errorCodePtr = ECONNABORTED; - } - } - - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - - return(-1); - case SSL_ERROR_SSL: - dprintf("Got permanent fatal SSL error, aborting immediately"); - Tls_Error(statePtr, (char *)ERR_reason_error_string(ERR_get_error())); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNABORTED; - return(-1); - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_ACCEPT: - case SSL_ERROR_WANT_X509_LOOKUP: - default: - dprintf("We got a confusing reply: %i", rc); - *errorCodePtr = Tcl_GetErrno(); - dprintf("ERR(%d, %d) ", rc, *errorCodePtr); - return(-1); - } - -#if 0 - if (statePtr->flags & TLS_TCL_SERVER) { - dprintf("This is an TLS server, checking the certificate for the peer"); - - err = SSL_get_verify_result(statePtr->ssl); - if (err != X509_V_OK) { - dprintf("Invalid certificate, returning in failure"); - - Tls_Error(statePtr, (char *)X509_verify_cert_error_string(err)); - statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; - *errorCodePtr = ECONNABORTED; - return(-1); - } - } -#endif - - dprintf("Removing the \"TLS_TCL_INIT\" flag since we have completed the handshake"); - statePtr->flags &= ~TLS_TCL_INIT; - - dprintf("Returning in success"); - *errorCodePtr = 0; - - return(0); + unsigned long backingError; + int err, rc; + int bioShouldRetry; + *errorCodePtr = 0; + + dprintf("WaitForConnect(%p)", (void *) statePtr); + dprintFlags(statePtr); + + if (!(statePtr->flags & TLS_TCL_INIT)) { + dprintf("Tls_WaitForConnect called on already initialized channel -- returning with immediate success"); + return(0); + } + + if (statePtr->flags & TLS_TCL_HANDSHAKE_FAILED) { + /* + * Different types of operations have different requirements + * SSL being established + */ + if (handshakeFailureIsPermanent) { + dprintf("Asked to wait for a TLS handshake that has already failed. Returning fatal error"); + *errorCodePtr = ECONNABORTED; + } else { + dprintf("Asked to wait for a TLS handshake that has already failed. Returning soft error"); + *errorCodePtr = ECONNRESET; + } + Tls_Error(statePtr, "Wait for failed handshake"); + return(-1); + } + + for (;;) { + ERR_clear_error(); + + /* Not initialized yet! Also calls SSL_do_handshake. */ + if (statePtr->flags & TLS_TCL_SERVER) { + dprintf("Calling SSL_accept()"); + err = SSL_accept(statePtr->ssl); + + } else { + dprintf("Calling SSL_connect()"); + err = SSL_connect(statePtr->ssl); + } + + if (err > 0) { + dprintf("Accept or connect was successful"); + + err = BIO_flush(statePtr->bio); + if (err <= 0) { + dprintf("Flushing the lower layers failed, this will probably terminate this session"); + } + } else { + dprintf("Accept or connect failed"); + } + + rc = SSL_get_error(statePtr->ssl, err); + backingError = ERR_get_error(); + if (rc != SSL_ERROR_NONE) { + dprintf("Got error: %i (rc = %i)", err, rc); + dprintf("Got error: %s", ERR_reason_error_string(backingError)); + } + + bioShouldRetry = 0; + if (err <= 0) { + if (rc == SSL_ERROR_WANT_CONNECT || rc == SSL_ERROR_WANT_ACCEPT || rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) { + bioShouldRetry = 1; + } else if (BIO_should_retry(statePtr->bio)) { + bioShouldRetry = 1; + } else if (rc == SSL_ERROR_SYSCALL && Tcl_GetErrno() == EAGAIN) { + bioShouldRetry = 1; + } + } else { + if (!SSL_is_init_finished(statePtr->ssl)) { + bioShouldRetry = 1; + } + } + + if (bioShouldRetry) { + dprintf("The I/O did not complete -- but we should try it again"); + + if (statePtr->flags & TLS_TCL_ASYNC) { + dprintf("Returning EAGAIN so that it can be retried later"); + *errorCodePtr = EAGAIN; + Tls_Error(statePtr, "Handshake not complete, will retry later"); + return(-1); + } else { + dprintf("Doing so now"); + continue; + } + } + + dprintf("We have either completely established the session or completely failed it -- there is no more need to ever retry it though"); + break; + } + + switch (rc) { + case SSL_ERROR_NONE: + /* The TLS/SSL I/O operation completed */ + dprintf("The connection is good"); + *errorCodePtr = 0; + break; + + case SSL_ERROR_ZERO_RETURN: + /* The TLS/SSL peer has closed the connection for writing by sending the close_notify alert */ + dprintf("SSL_ERROR_ZERO_RETURN: Connect returned an invalid value..."); + *errorCodePtr = EINVAL; + Tls_Error(statePtr, "Peer has closed the connection for writing by sending the close_notify alert"); + return(-1); + + case SSL_ERROR_SYSCALL: + /* Some non-recoverable, fatal I/O error occurred */ + dprintf("SSL_ERROR_SYSCALL"); + + if (backingError == 0 && err == 0) { + dprintf("EOF reached") + *errorCodePtr = ECONNRESET; + Tls_Error(statePtr, "(unexpected) EOF reached"); + + } else if (backingError == 0 && err == -1) { + dprintf("I/O error occurred (errno = %lu)", (unsigned long) Tcl_GetErrno()); + *errorCodePtr = Tcl_GetErrno(); + if (*errorCodePtr == ECONNRESET) { + *errorCodePtr = ECONNABORTED; + } + Tls_Error(statePtr, (char *) Tcl_ErrnoMsg(*errorCodePtr)); + + } else { + dprintf("I/O error occurred (backingError = %lu)", backingError); + *errorCodePtr = Tcl_GetErrno(); + if (*errorCodePtr == ECONNRESET) { + *errorCodePtr = ECONNABORTED; + } + Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); + } + + statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; + return(-1); + + case SSL_ERROR_SSL: + /* A non-recoverable, fatal error in the SSL library occurred, usually a protocol error */ + dprintf("SSL_ERROR_SSL: Got permanent fatal SSL error, aborting immediately"); + if (SSL_get_verify_result(statePtr->ssl) != X509_V_OK) { + Tls_Error(statePtr, (char *) X509_verify_cert_error_string(SSL_get_verify_result(statePtr->ssl))); + } + if (backingError != 0) { + Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); + } + statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; + *errorCodePtr = ECONNABORTED; + return(-1); + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_X509_LOOKUP: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + case SSL_ERROR_WANT_ASYNC: + case SSL_ERROR_WANT_ASYNC_JOB: + case SSL_ERROR_WANT_CLIENT_HELLO_CB: + default: + /* The operation did not complete and should be retried later. */ + dprintf("Operation did not complete, call function again later: %i", rc); + *errorCodePtr = EAGAIN; + dprintf("ERR(%d, %d) ", rc, *errorCodePtr); + Tls_Error(statePtr, "Operation did not complete, call function again later"); + return(-1); + } + + dprintf("Removing the \"TLS_TCL_INIT\" flag since we have completed the handshake"); + statePtr->flags &= ~TLS_TCL_INIT; + + dprintf("Returning in success"); + *errorCodePtr = 0; + return(0); } /* *------------------------------------------------------------------- * * TlsInputProc -- * - * This procedure is invoked by the generic IO level + * This procedure is invoked by the generic IO level * to read input from a SSL socket based channel. * * Results: - * The number of bytes read is returned or -1 on error. An output - * argument contains the POSIX error code on error, or zero if no - * error occurred. - * - * Side effects: - * Reads input from the input device of the channel. - * - *------------------------------------------------------------------- - */ - -static int TlsInputProc(void *instanceData, char *buf, int bufSize, int *errorCodePtr) { - unsigned long backingError; - State *statePtr = (State *) instanceData; - int bytesRead; - int tlsConnect; - int err; - - *errorCodePtr = 0; - - dprintf("BIO_read(%d)", bufSize); - - if (statePtr->flags & TLS_TCL_CALLBACK) { - /* don't process any bytes while verify callback is running */ - dprintf("Callback is running, reading 0 bytes"); - return(0); - } - - dprintf("Calling Tls_WaitForConnect"); - tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 0); - if (tlsConnect < 0) { - dprintf("Got an error waiting to connect (tlsConnect = %i, *errorCodePtr = %i)", tlsConnect, *errorCodePtr); - - bytesRead = -1; - if (*errorCodePtr == ECONNRESET) { - dprintf("Got connection reset"); - /* Soft EOF */ - *errorCodePtr = 0; - bytesRead = 0; - } - - return(bytesRead); - } - - /* - * We need to clear the SSL error stack now because we sometimes reach - * this function with leftover errors in the stack. If BIO_read - * returns -1 and intends EAGAIN, there is a leftover error, it will be - * misconstrued as an error, not EAGAIN. - * - * Alternatively, we may want to handle the <0 return codes from - * BIO_read specially (as advised in the RSA docs). TLS's lower level BIO - * functions play with the retry flags though, and this seems to work - * correctly. Similar fix in TlsOutputProc. - hobbs - */ - ERR_clear_error(); - bytesRead = BIO_read(statePtr->bio, buf, bufSize); - dprintf("BIO_read -> %d", bytesRead); - - err = SSL_get_error(statePtr->ssl, bytesRead); - -#if 0 - if (bytesRead <= 0) { - if (BIO_should_retry(statePtr->bio)) { - dprintf("I/O failed, will retry based on EAGAIN"); - *errorCodePtr = EAGAIN; - } - } -#endif - - switch (err) { - case SSL_ERROR_NONE: - dprintBuffer(buf, bytesRead); - break; - case SSL_ERROR_SSL: - dprintf("SSL negotiation error, indicating that the connection has been aborted"); - - Tls_Error(statePtr, TCLTLS_SSL_ERROR(statePtr->ssl, bytesRead)); - *errorCodePtr = ECONNABORTED; - bytesRead = -1; - - break; - case SSL_ERROR_SYSCALL: - backingError = ERR_get_error(); - - if (backingError == 0 && bytesRead == 0) { - dprintf("EOF reached") - *errorCodePtr = 0; - bytesRead = 0; - } else if (backingError == 0 && bytesRead == -1) { - dprintf("I/O error occured (errno = %lu)", (unsigned long) Tcl_GetErrno()); - *errorCodePtr = Tcl_GetErrno(); - bytesRead = -1; - } else { - dprintf("I/O error occured (backingError = %lu)", backingError); - *errorCodePtr = backingError; - bytesRead = -1; - } - - break; - case SSL_ERROR_ZERO_RETURN: - dprintf("Got SSL_ERROR_ZERO_RETURN, this means an EOF has been reached"); - bytesRead = 0; - *errorCodePtr = 0; - break; - case SSL_ERROR_WANT_READ: - dprintf("Got SSL_ERROR_WANT_READ, mapping this to EAGAIN"); - bytesRead = -1; - *errorCodePtr = EAGAIN; - break; - default: - dprintf("Unknown error (err = %i), mapping to EOF", err); - *errorCodePtr = 0; - bytesRead = 0; - break; - } - - dprintf("Input(%d) -> %d [%d]", bufSize, bytesRead, *errorCodePtr); - return(bytesRead); + * Returns the number of bytes read or -1 on error. Sets errorCodePtr + * to a POSIX error code if an error occurred, or 0 if none. + * + * Side effects: + * Reads input from the input device of the channel. + * + *------------------------------------------------------------------- + */ +static int TlsInputProc(ClientData instanceData, char *buf, int bufSize, int *errorCodePtr) { + unsigned long backingError; + State *statePtr = (State *) instanceData; + int bytesRead; + int tlsConnect; + int err; + + *errorCodePtr = 0; + + dprintf("BIO_read(%d)", bufSize); + + if (statePtr->flags & TLS_TCL_CALLBACK) { + /* don't process any bytes while verify callback is running */ + dprintf("Callback is running, reading 0 bytes"); + return(0); + } + + dprintf("Calling Tls_WaitForConnect"); + tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 0); + if (tlsConnect < 0) { + dprintf("Got an error waiting to connect (tlsConnect = %i, *errorCodePtr = %i)", tlsConnect, *errorCodePtr); + Tls_Error(statePtr, strerror(*errorCodePtr)); + + bytesRead = -1; + if (*errorCodePtr == ECONNRESET) { + dprintf("Got connection reset"); + /* Soft EOF */ + *errorCodePtr = 0; + bytesRead = 0; + } + return(bytesRead); + } + + /* + * We need to clear the SSL error stack now because we sometimes reach + * this function with leftover errors in the stack. If BIO_read + * returns -1 and intends EAGAIN, there is a leftover error, it will be + * misconstrued as an error, not EAGAIN. + * + * Alternatively, we may want to handle the <0 return codes from + * BIO_read specially (as advised in the RSA docs). TLS's lower level BIO + * functions play with the retry flags though, and this seems to work + * correctly. Similar fix in TlsOutputProc. - hobbs + */ + ERR_clear_error(); + bytesRead = BIO_read(statePtr->bio, buf, bufSize); + dprintf("BIO_read -> %d", bytesRead); + + err = SSL_get_error(statePtr->ssl, bytesRead); + backingError = ERR_get_error(); + +#if 0 + if (bytesRead <= 0) { + if (BIO_should_retry(statePtr->bio)) { + dprintf("I/O failed, will retry based on EAGAIN"); + *errorCodePtr = EAGAIN; + } + } +#endif + + switch (err) { + case SSL_ERROR_NONE: + dprintBuffer(buf, bytesRead); + break; + + case SSL_ERROR_SSL: + /* A non-recoverable, fatal error in the SSL library occurred, usually a protocol error */ + dprintf("SSL error, indicating that the connection has been aborted"); + if (backingError != 0) { + Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); + } else if (SSL_get_verify_result(statePtr->ssl) != X509_V_OK) { + Tls_Error(statePtr, (char *) X509_verify_cert_error_string(SSL_get_verify_result(statePtr->ssl))); + } else { + Tls_Error(statePtr, "Unknown SSL error"); + } + *errorCodePtr = ECONNABORTED; + bytesRead = -1; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + /* Unexpected EOF from the peer for OpenSSL 3.0+ */ + if (ERR_GET_REASON(backingError) == SSL_R_UNEXPECTED_EOF_WHILE_READING) { + dprintf("(Unexpected) EOF reached") + *errorCodePtr = 0; + bytesRead = 0; + Tls_Error(statePtr, "EOF reached"); + } +#endif + break; + + case SSL_ERROR_SYSCALL: + /* Some non-recoverable, fatal I/O error occurred */ + + if (backingError == 0 && bytesRead == 0) { + /* Unexpected EOF from the peer for OpenSSL 1.1 */ + dprintf("(Unexpected) EOF reached") + *errorCodePtr = 0; + bytesRead = 0; + Tls_Error(statePtr, "EOF reached"); + + } else if (backingError == 0 && bytesRead == -1) { + dprintf("I/O error occurred (errno = %lu)", (unsigned long) Tcl_GetErrno()); + *errorCodePtr = Tcl_GetErrno(); + bytesRead = -1; + Tls_Error(statePtr, (char *) Tcl_ErrnoMsg(*errorCodePtr)); + + } else { + dprintf("I/O error occurred (backingError = %lu)", backingError); + *errorCodePtr = Tcl_GetErrno(); + bytesRead = -1; + Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); + } + break; + + case SSL_ERROR_ZERO_RETURN: + dprintf("Got SSL_ERROR_ZERO_RETURN, this means an EOF has been reached"); + bytesRead = 0; + *errorCodePtr = 0; + Tls_Error(statePtr, "Peer has closed the connection for writing by sending the close_notify alert"); + break; + + case SSL_ERROR_WANT_READ: + dprintf("Got SSL_ERROR_WANT_READ, mapping this to EAGAIN"); + bytesRead = -1; + *errorCodePtr = EAGAIN; + Tls_Error(statePtr, "SSL_ERROR_WANT_READ"); + break; + + default: + dprintf("Unknown error (err = %i), mapping to EOF", err); + *errorCodePtr = 0; + bytesRead = 0; + Tls_Error(statePtr, "Unknown error"); + break; + } + + dprintf("Input(%d) -> %d [%d]", bufSize, bytesRead, *errorCodePtr); + return(bytesRead); } /* *------------------------------------------------------------------- * * TlsOutputProc -- * - * This procedure is invoked by the generic IO level + * This procedure is invoked by the generic IO level * to write output to a SSL socket based channel. * * Results: - * The number of bytes written is returned. An output argument is - * set to a POSIX error code if an error occurred, or zero. - * - * Side effects: - * Writes output on the output device of the channel. - * - *------------------------------------------------------------------- - */ - -static int TlsOutputProc(void *instanceData, const char *buf, int toWrite, int *errorCodePtr) { - unsigned long backingError; - State *statePtr = (State *) instanceData; - int written, err; - int tlsConnect; - - *errorCodePtr = 0; - - dprintf("BIO_write(%p, %d)", (void *) statePtr, toWrite); - dprintBuffer(buf, toWrite); - - if (statePtr->flags & TLS_TCL_CALLBACK) { - dprintf("Don't process output while callbacks are running") - written = -1; - *errorCodePtr = EAGAIN; - return(-1); - } - - dprintf("Calling Tls_WaitForConnect"); - tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 1); - if (tlsConnect < 0) { - dprintf("Got an error waiting to connect (tlsConnect = %i, *errorCodePtr = %i)", tlsConnect, *errorCodePtr); - - written = -1; - if (*errorCodePtr == ECONNRESET) { - dprintf("Got connection reset"); - /* Soft EOF */ - *errorCodePtr = 0; - written = 0; - } - - return(written); - } - - if (toWrite == 0) { - dprintf("zero-write"); - err = BIO_flush(statePtr->bio); - - if (err <= 0) { - dprintf("Flushing failed"); - - *errorCodePtr = EIO; - written = 0; - return(-1); - } - - written = 0; - *errorCodePtr = 0; - return(0); - } - - /* - * We need to clear the SSL error stack now because we sometimes reach - * this function with leftover errors in the stack. If BIO_write - * returns -1 and intends EAGAIN, there is a leftover error, it will be - * misconstrued as an error, not EAGAIN. - * - * Alternatively, we may want to handle the <0 return codes from - * BIO_write specially (as advised in the RSA docs). TLS's lower level - * BIO functions play with the retry flags though, and this seems to - * work correctly. Similar fix in TlsInputProc. - hobbs - */ - ERR_clear_error(); - written = BIO_write(statePtr->bio, buf, toWrite); - dprintf("BIO_write(%p, %d) -> [%d]", (void *) statePtr, toWrite, written); - - err = SSL_get_error(statePtr->ssl, written); - switch (err) { - case SSL_ERROR_NONE: - if (written < 0) { - written = 0; - } - break; - case SSL_ERROR_WANT_WRITE: - dprintf("Got SSL_ERROR_WANT_WRITE, mapping it to EAGAIN"); - *errorCodePtr = EAGAIN; - written = -1; - break; - case SSL_ERROR_WANT_READ: - dprintf(" write R BLOCK"); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - dprintf(" write X BLOCK"); - break; - case SSL_ERROR_ZERO_RETURN: - dprintf(" closed"); - written = 0; - *errorCodePtr = 0; - break; - case SSL_ERROR_SYSCALL: - backingError = ERR_get_error(); - - if (backingError == 0 && written == 0) { - dprintf("EOF reached") - *errorCodePtr = 0; - written = 0; - } else if (backingError == 0 && written == -1) { - dprintf("I/O error occured (errno = %lu)", (unsigned long) Tcl_GetErrno()); - *errorCodePtr = Tcl_GetErrno(); - written = -1; - } else { - dprintf("I/O error occured (backingError = %lu)", backingError); - *errorCodePtr = backingError; - written = -1; - } - - break; - case SSL_ERROR_SSL: - Tls_Error(statePtr, TCLTLS_SSL_ERROR(statePtr->ssl, written)); - *errorCodePtr = ECONNABORTED; - written = -1; - break; - default: - dprintf(" unknown err: %d", err); - break; - } - - dprintf("Output(%d) -> %d", toWrite, written); - return(written); + * Returns the number of bytes written or -1 on error. Sets errorCodePtr + * to a POSIX error code if an error occurred, or 0 if none. + * + * Side effects: + * Writes output on the output device of the channel. + * + *------------------------------------------------------------------- + */ +static int TlsOutputProc(ClientData instanceData, const char *buf, int toWrite, int *errorCodePtr) { + unsigned long backingError; + State *statePtr = (State *) instanceData; + int written, err; + int tlsConnect; + + *errorCodePtr = 0; + + dprintf("BIO_write(%p, %d)", (void *) statePtr, toWrite); + dprintBuffer(buf, toWrite); + + if (statePtr->flags & TLS_TCL_CALLBACK) { + dprintf("Don't process output while callbacks are running"); + written = -1; + *errorCodePtr = EAGAIN; + return(-1); + } + + dprintf("Calling Tls_WaitForConnect"); + tlsConnect = Tls_WaitForConnect(statePtr, errorCodePtr, 1); + if (tlsConnect < 0) { + dprintf("Got an error waiting to connect (tlsConnect = %i, *errorCodePtr = %i)", tlsConnect, *errorCodePtr); + Tls_Error(statePtr, strerror(*errorCodePtr)); + + written = -1; + if (*errorCodePtr == ECONNRESET) { + dprintf("Got connection reset"); + /* Soft EOF */ + *errorCodePtr = 0; + written = 0; + } + return(written); + } + + if (toWrite == 0) { + dprintf("zero-write"); + err = BIO_flush(statePtr->bio); + + if (err <= 0) { + dprintf("Flushing failed"); + Tls_Error(statePtr, "Flush failed"); + + *errorCodePtr = EIO; + written = 0; + return(-1); + } + + written = 0; + *errorCodePtr = 0; + return(0); + } + + /* + * We need to clear the SSL error stack now because we sometimes reach + * this function with leftover errors in the stack. If BIO_write + * returns -1 and intends EAGAIN, there is a leftover error, it will be + * misconstrued as an error, not EAGAIN. + * + * Alternatively, we may want to handle the <0 return codes from + * BIO_write specially (as advised in the RSA docs). TLS's lower level + * BIO functions play with the retry flags though, and this seems to + * work correctly. Similar fix in TlsInputProc. - hobbs + */ + ERR_clear_error(); + written = BIO_write(statePtr->bio, buf, toWrite); + dprintf("BIO_write(%p, %d) -> [%d]", (void *) statePtr, toWrite, written); + + err = SSL_get_error(statePtr->ssl, written); + backingError = ERR_get_error(); + + switch (err) { + case SSL_ERROR_NONE: + if (written < 0) { + written = 0; + } + break; + + case SSL_ERROR_WANT_WRITE: + dprintf("Got SSL_ERROR_WANT_WRITE, mapping it to EAGAIN"); + *errorCodePtr = EAGAIN; + written = -1; + Tls_Error(statePtr, "SSL_ERROR_WANT_WRITE"); + break; + + case SSL_ERROR_WANT_READ: + dprintf(" write R BLOCK"); + Tls_Error(statePtr, "SSL_ERROR_WANT_READ"); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + dprintf(" write X BLOCK"); + Tls_Error(statePtr, "SSL_ERROR_WANT_X509_LOOKUP"); + break; + + case SSL_ERROR_ZERO_RETURN: + dprintf(" closed"); + written = 0; + *errorCodePtr = 0; + Tls_Error(statePtr, "Peer has closed the connection for writing by sending the close_notify alert"); + break; + + case SSL_ERROR_SYSCALL: + /* Some non-recoverable, fatal I/O error occurred */ + + if (backingError == 0 && written == 0) { + dprintf("EOF reached") + *errorCodePtr = 0; + written = 0; + Tls_Error(statePtr, "EOF reached"); + + } else if (backingError == 0 && written == -1) { + dprintf("I/O error occurred (errno = %lu)", (unsigned long) Tcl_GetErrno()); + *errorCodePtr = Tcl_GetErrno(); + written = -1; + Tls_Error(statePtr, (char *) Tcl_ErrnoMsg(*errorCodePtr)); + + } else { + dprintf("I/O error occurred (backingError = %lu)", backingError); + *errorCodePtr = Tcl_GetErrno(); + written = -1; + Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); + } + break; + + case SSL_ERROR_SSL: + /* A non-recoverable, fatal error in the SSL library occurred, usually a protocol error */ + dprintf("SSL error, indicating that the connection has been aborted"); + if (backingError != 0) { + Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); + } else if (SSL_get_verify_result(statePtr->ssl) != X509_V_OK) { + Tls_Error(statePtr, (char *) X509_verify_cert_error_string(SSL_get_verify_result(statePtr->ssl))); + } else { + Tls_Error(statePtr, "Unknown SSL error"); + } + *errorCodePtr = ECONNABORTED; + written = -1; + break; + + default: + dprintf("unknown error: %d", err); + Tls_Error(statePtr, "Unknown error"); + break; + } + + dprintf("Output(%d) -> %d", toWrite, written); + return(written); +} + +/* + *------------------------------------------------------------------- + * + * TlsSetOptionProc -- + * + * Sets an option value for a SSL socket based channel, or a + * list of all options and their values. + * + * Results: + * TCL_OK if successful or TCL_ERROR if failed. + * + * Side effects: + * Updates channel option to new value. + * + *------------------------------------------------------------------- + */ +static int +TlsSetOptionProc(ClientData instanceData, /* Socket state. */ + Tcl_Interp *interp, /* For errors - can be NULL. */ + const char *optionName, /* Name of the option to set the value for, or + * NULL to get all options and their values. */ + const char *optionValue) /* Value for option. */ +{ + State *statePtr = (State *) instanceData; + + Tcl_Channel downChan = Tls_GetParent(statePtr, TLS_TCL_FASTPATH); + Tcl_DriverSetOptionProc *setOptionProc; + + setOptionProc = Tcl_ChannelSetOptionProc(Tcl_GetChannelType(downChan)); + if (setOptionProc != NULL) { + return (*setOptionProc)(Tcl_GetChannelInstanceData(downChan), interp, optionName, optionValue); + } else if (optionName == (char*) NULL) { + /* + * Request is query for all options, this is ok. + */ + return TCL_OK; + } + /* + * Request for a specific option has to fail, we don't have any. + */ + return Tcl_BadChannelOption(interp, optionName, ""); } /* *------------------------------------------------------------------- * * TlsGetOptionProc -- * - * Computes an option value for a SSL socket based channel, or a - * list of all options and their values. + * Gets an option value for a SSL socket based channel, or a + * list of all options and their values. * * Results: - * A standard Tcl result. The value of the specified option or a - * list of all options and their values is returned in the - * supplied DString. + * A standard Tcl result. The value of the specified option or a + * list of all options and their values is returned in the + * supplied DString. * * Side effects: - * None. + * None. * *------------------------------------------------------------------- */ static int -TlsGetOptionProc(void *instanceData, /* Socket state. */ - Tcl_Interp *interp, /* For errors - can be NULL. */ - const char *optionName, /* Name of the option to - * retrieve the value for, or - * NULL to get all options and - * their values. */ - Tcl_DString *dsPtr) /* Where to store the computed value - * initialized by caller. */ +TlsGetOptionProc(ClientData instanceData, /* Socket state. */ + Tcl_Interp *interp, /* For errors - can be NULL. */ + const char *optionName, /* Name of the option to retrieve the value for, or + * NULL to get all options and their values. */ + Tcl_DString *optionValue) /* Where to store the computed value initialized by caller. */ { State *statePtr = (State *) instanceData; - Tcl_Channel downChan = Tls_GetParent(statePtr, TLS_TCL_FASTPATH); - Tcl_DriverGetOptionProc *getOptionProc; + Tcl_Channel downChan = Tls_GetParent(statePtr, TLS_TCL_FASTPATH); + Tcl_DriverGetOptionProc *getOptionProc; getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(downChan)); if (getOptionProc != NULL) { - return (*getOptionProc)(Tcl_GetChannelInstanceData(downChan), interp, optionName, dsPtr); + return (*getOptionProc)(Tcl_GetChannelInstanceData(downChan), interp, optionName, optionValue); } else if (optionName == (char*) NULL) { - /* - * Request is query for all options, this is ok. - */ - return TCL_OK; + /* + * Request is query for all options, this is ok. + */ + return TCL_OK; } /* * Request for a specific option has to fail, we don't have any. */ - return TCL_ERROR; + return Tcl_BadChannelOption(interp, optionName, ""); } /* *------------------------------------------------------------------- * * TlsWatchProc -- * - * Initialize the notifier to watch Tcl_Files from this channel. + * Initialize the notifier to watch Tcl_Files from this channel. * * Results: - * None. + * None. * * Side effects: - * Sets up the notifier so that a future event on the channel - * will be seen by Tcl. + * Sets up the notifier so that a future event on the channel + * will be seen by Tcl. * *------------------------------------------------------------------- */ - static void -TlsWatchProc(void *instanceData, /* The socket state. */ - int mask) /* Events of interest; an OR-ed - * combination of TCL_READABLE, - * TCL_WRITABLE and TCL_EXCEPTION. */ +TlsWatchProc(ClientData instanceData, /* The socket state. */ + int mask) /* Events of interest; an OR-ed combination of + * TCL_READABLE, TCL_WRITABLE and TCL_EXCEPTION. */ { Tcl_Channel downChan; State *statePtr = (State *) instanceData; + Tcl_DriverWatchProc *watchProc; dprintf("TlsWatchProc(0x%x)", mask); /* Pretend to be dead as long as the verify callback is running. * Otherwise that callback could be invoked recursively. */ if (statePtr->flags & TLS_TCL_CALLBACK) { - dprintf("Callback is on-going, doing nothing"); - return; + dprintf("Callback is on-going, doing nothing"); + return; } dprintFlags(statePtr); downChan = Tls_GetParent(statePtr, TLS_TCL_FASTPATH); if (statePtr->flags & TLS_TCL_HANDSHAKE_FAILED) { - dprintf("Asked to watch a socket with a failed handshake -- nothing can happen here"); - - dprintf("Unregistering interest in the lower channel"); - (Tcl_GetChannelType(downChan))->watchProc(Tcl_GetChannelInstanceData(downChan), 0); - - statePtr->watchMask = 0; - - return; - } - - statePtr->watchMask = mask; - - /* No channel handlers any more. We will be notified automatically - * about events on the channel below via a call to our - * 'TransformNotifyProc'. But we have to pass the interest down now. - * We are allowed to add additional 'interest' to the mask if we want - * to. But this transformation has no such interest. It just passes - * the request down, unchanged. - */ - - - dprintf("Registering our interest in the lower channel (chan=%p)", (void *) downChan); - (Tcl_GetChannelType(downChan)) - ->watchProc(Tcl_GetChannelInstanceData(downChan), mask); - - /* - * Management of the internal timer. - */ - - if (statePtr->timer != (Tcl_TimerToken) NULL) { - dprintf("A timer was found, deleting it"); - Tcl_DeleteTimerHandler(statePtr->timer); - statePtr->timer = (Tcl_TimerToken) NULL; - } - - if (mask & TCL_READABLE) { - if (Tcl_InputBuffered(statePtr->self) > 0 || BIO_ctrl_pending(statePtr->bio) > 0) { - /* - * There is interest in readable events and we actually have - * data waiting, so generate a timer to flush that. - */ - dprintf("Creating a new timer since data appears to be waiting"); - statePtr->timer = Tcl_CreateTimerHandler(TLS_TCL_DELAY, TlsChannelHandlerTimer, statePtr); - } - } + dprintf("Asked to watch a socket with a failed handshake -- nothing can happen here"); + dprintf("Unregistering interest in the lower channel"); + + watchProc = Tcl_ChannelWatchProc(Tcl_GetChannelType(downChan)); + watchProc(Tcl_GetChannelInstanceData(downChan), 0); + statePtr->watchMask = 0; + return; + } + + statePtr->watchMask = mask; + + /* No channel handlers any more. We will be notified automatically + * about events on the channel below via a call to our + * 'TransformNotifyProc'. But we have to pass the interest down now. + * We are allowed to add additional 'interest' to the mask if we want + * to. But this transformation has no such interest. It just passes + * the request down, unchanged. + */ + dprintf("Registering our interest in the lower channel (chan=%p)", (void *) downChan); + watchProc = Tcl_ChannelWatchProc(Tcl_GetChannelType(downChan)); + watchProc(Tcl_GetChannelInstanceData(downChan), mask); + + + /* + * Management of the internal timer. + */ + if (statePtr->timer != (Tcl_TimerToken) NULL) { + dprintf("A timer was found, deleting it"); + Tcl_DeleteTimerHandler(statePtr->timer); + statePtr->timer = (Tcl_TimerToken) NULL; + } + + if ((mask & TCL_READABLE) && + ((Tcl_InputBuffered(statePtr->self) > 0) || (BIO_ctrl_pending(statePtr->bio) > 0))) { + /* + * There is interest in readable events and we actually have + * data waiting, so generate a timer to flush that. + */ + dprintf("Creating a new timer since data appears to be waiting"); + statePtr->timer = Tcl_CreateTimerHandler(TLS_TCL_DELAY, TlsChannelHandlerTimer, (ClientData) statePtr); + } } /* *------------------------------------------------------------------- * * TlsGetHandleProc -- * - * Called from Tcl_GetChannelFile to retrieve o/s file handler - * from the SSL socket based channel. + * Called from Tcl_GetChannelFile to retrieve o/s file handler + * from the SSL socket based channel. * * Results: - * The appropriate Tcl_File or NULL if not present. + * The appropriate Tcl_File handle or NULL if none. * * Side effects: - * None. + * None. * *------------------------------------------------------------------- */ -static int TlsGetHandleProc(void *instanceData, int direction, void **handlePtr) { - State *statePtr = (State *) instanceData; +static int TlsGetHandleProc(ClientData instanceData, /* Socket state. */ + int direction, /* TCL_READABLE or TCL_WRITABLE */ + ClientData *handlePtr) /* Handle associated with the channel */ +{ + State *statePtr = (State *) instanceData; - return(Tcl_GetChannelHandle(Tls_GetParent(statePtr, TLS_TCL_FASTPATH), direction, handlePtr)); + return(Tcl_GetChannelHandle(Tls_GetParent(statePtr, TLS_TCL_FASTPATH), direction, handlePtr)); } /* *------------------------------------------------------------------- * * TlsNotifyProc -- * - * Handler called by Tcl to inform us of activity - * on the underlying channel. - * - * Results: - * None. - * - * Side effects: - * May process the incoming event by itself. - * - *------------------------------------------------------------------- - */ - -static int TlsNotifyProc(void *instanceData, int mask) { - State *statePtr = (State *) instanceData; - int errorCode; - - /* - * An event occured in the underlying channel. This - * transformation doesn't process such events thus returns the - * incoming mask unchanged. - */ - if (statePtr->timer != (Tcl_TimerToken) NULL) { - /* - * Delete an existing timer. It was not fired, yet we are - * here, so the channel below generated such an event and we - * don't have to. The renewal of the interest after the - * execution of channel handlers will eventually cause us to - * recreate the timer (in WatchProc). - */ - Tcl_DeleteTimerHandler(statePtr->timer); - statePtr->timer = (Tcl_TimerToken) NULL; - } - - if (statePtr->flags & TLS_TCL_CALLBACK) { - dprintf("Returning 0 due to callback"); - return 0; - } - - dprintf("Calling Tls_WaitForConnect"); - errorCode = 0; - if (Tls_WaitForConnect(statePtr, &errorCode, 1) < 0) { - if (errorCode == EAGAIN) { - dprintf("Async flag could be set (didn't check) and errorCode == EAGAIN: Returning 0"); - - return 0; - } - - dprintf("Tls_WaitForConnect returned an error"); - } - - dprintf("Returning %i", mask); - - return(mask); -} - -#if 0 -/* - *------------------------------------------------------* - * - * TlsChannelHandler -- - * - * ------------------------------------------------* - * Handler called by Tcl as a result of - * Tcl_CreateChannelHandler - to inform us of activity - * on the underlying channel. - * ------------------------------------------------* - * - * Sideeffects: - * May generate subsequent calls to - * Tcl_NotifyChannel. - * - * Result: - * None. - * - *------------------------------------------------------* - */ - -static void -TlsChannelHandler (clientData, mask) - void * clientData; - int mask; -{ - State *statePtr = (State *) clientData; - - dprintf("HANDLER(0x%x)", mask); - Tcl_Preserve(statePtr); - - if (mask & TCL_READABLE) { - BIO_set_flags(statePtr->p_bio, BIO_FLAGS_READ); - } else { - BIO_clear_flags(statePtr->p_bio, BIO_FLAGS_READ); - } - - if (mask & TCL_WRITABLE) { - BIO_set_flags(statePtr->p_bio, BIO_FLAGS_WRITE); - } else { - BIO_clear_flags(statePtr->p_bio, BIO_FLAGS_WRITE); - } - - mask = 0; - if (BIO_wpending(statePtr->bio)) { - mask |= TCL_WRITABLE; - } - if (BIO_pending(statePtr->bio)) { - mask |= TCL_READABLE; - } - - /* - * The following NotifyChannel calls seems to be important, but - * we don't know why. It looks like if the mask is ever non-zero - * that it will enter an infinite loop. - * - * Notify the upper channel of the current BIO state so the event - * continues to propagate up the chain. - * - * stanton: It looks like this could result in an infinite loop if - * the upper channel doesn't cause ChannelHandler to be removed - * before Tcl_NotifyChannel calls channel handlers on the lower channel. - */ - - Tcl_NotifyChannel(statePtr->self, mask); - - if (statePtr->timer != (Tcl_TimerToken)NULL) { - Tcl_DeleteTimerHandler(statePtr->timer); - statePtr->timer = (Tcl_TimerToken)NULL; - } - if ((mask & TCL_READABLE) && Tcl_InputBuffered(statePtr->self) > 0) { - /* - * Data is waiting, flush it out in short time - */ - statePtr->timer = Tcl_CreateTimerHandler(TLS_TCL_DELAY, - TlsChannelHandlerTimer, statePtr); - } - Tcl_Release(statePtr); -} -#endif - -/* - *------------------------------------------------------* - * - * TlsChannelHandlerTimer -- - * - * ------------------------------------------------* - * Called by the notifier (-> timer) to flush out - * information waiting in channel buffers. - * ------------------------------------------------* - * - * Sideeffects: - * As of 'TlsChannelHandler'. - * - * Result: - * None. - * - *------------------------------------------------------* - */ - -static void TlsChannelHandlerTimer(void *clientData) { - State *statePtr = (State *) clientData; - int mask = 0; - - dprintf("Called"); - - statePtr->timer = (Tcl_TimerToken) NULL; - - if (BIO_wpending(statePtr->bio)) { - dprintf("[chan=%p] BIO writable", statePtr->self); - - mask |= TCL_WRITABLE; - } - - if (BIO_pending(statePtr->bio)) { - dprintf("[chan=%p] BIO readable", statePtr->self); - - mask |= TCL_READABLE; - } - - dprintf("Notifying ourselves"); - Tcl_NotifyChannel(statePtr->self, mask); - - dprintf("Returning"); - - return; -} - -Tcl_Channel Tls_GetParent(State *statePtr, int maskFlags) { - dprintf("Requested to get parent of channel %p", statePtr->self); - - if ((statePtr->flags & ~maskFlags) & TLS_TCL_FASTPATH) { - dprintf("Asked to get the parent channel while we are using FastPath -- returning NULL"); - return(NULL); - } - - return(Tcl_GetStackedChannel(statePtr->self)); + * Handler called by Tcl to inform us of activity + * on the underlying channel. + * + * Results: + * Type of event or 0 if failed + * + * Side effects: + * May process the incoming event by itself. + * + *------------------------------------------------------------------- + */ +static int TlsNotifyProc(ClientData instanceData, /* Socket state. */ + int mask) /* type of event that occurred: + * OR-ed combination of TCL_READABLE or TCL_WRITABLE */ +{ + State *statePtr = (State *) instanceData; + int errorCode; + + /* + * An event occurred in the underlying channel. This + * transformation doesn't process such events thus returns the + * incoming mask unchanged. + */ + if (statePtr->timer != (Tcl_TimerToken) NULL) { + /* + * Delete an existing timer. It was not fired, yet we are + * here, so the channel below generated such an event and we + * don't have to. The renewal of the interest after the + * execution of channel handlers will eventually cause us to + * recreate the timer (in WatchProc). + */ + Tcl_DeleteTimerHandler(statePtr->timer); + statePtr->timer = (Tcl_TimerToken) NULL; + } + + if (statePtr->flags & TLS_TCL_CALLBACK) { + dprintf("Returning 0 due to callback"); + return 0; + } + + dprintf("Calling Tls_WaitForConnect"); + errorCode = 0; + if (Tls_WaitForConnect(statePtr, &errorCode, 1) < 0) { + Tls_Error(statePtr, strerror(errorCode)); + if (errorCode == EAGAIN) { + dprintf("Async flag could be set (didn't check) and errorCode == EAGAIN: Returning 0"); + + return 0; + } + + dprintf("Tls_WaitForConnect returned an error"); + } + + dprintf("Returning %i", mask); + + return(mask); +} + +/* + *------------------------------------------------------* + * + * TlsChannelHandlerTimer -- + * + * ------------------------------------------------* + * Called by the notifier (-> timer) to flush out + * information waiting in channel buffers. + * ------------------------------------------------* + * + * Side effects: + * As of 'TlsChannelHandler'. + * + * Result: + * None. + * + *------------------------------------------------------* + */ +static void TlsChannelHandlerTimer(ClientData clientData) { + State *statePtr = (State *) clientData; + int mask = 0; + + dprintf("Called"); + + statePtr->timer = (Tcl_TimerToken) NULL; + + if (BIO_wpending(statePtr->bio)) { + dprintf("[chan=%p] BIO writable", statePtr->self); + + mask |= TCL_WRITABLE; + } + + if (BIO_pending(statePtr->bio)) { + dprintf("[chan=%p] BIO readable", statePtr->self); + + mask |= TCL_READABLE; + } + + dprintf("Notifying ourselves"); + Tcl_NotifyChannel(statePtr->self, mask); + + dprintf("Returning"); + + return; +} + +Tcl_Channel Tls_GetParent(State *statePtr, int maskFlags) { + dprintf("Requested to get parent of channel %p", statePtr->self); + + if ((statePtr->flags & ~maskFlags) & TLS_TCL_FASTPATH) { + dprintf("Asked to get the parent channel while we are using FastPath -- returning NULL"); + return(NULL); + } + return(Tcl_GetStackedChannel(statePtr->self)); +} + +/* + *------------------------------------------------------------------- + * + * Tls_ChannelType -- + * + * Return the correct TLS channel driver info + * + * Results: + * The correct channel driver for the current version of Tcl. + * + * Side effects: + * None. + * + *------------------------------------------------------------------- + */ +static const Tcl_ChannelType tlsChannelType = { + "tls", /* Type name */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ + TlsCloseProc, /* Close proc */ + TlsInputProc, /* Input proc */ + TlsOutputProc, /* Output proc */ + NULL, /* Seek proc */ + TlsSetOptionProc, /* Set option proc */ + TlsGetOptionProc, /* Get option proc */ + TlsWatchProc, /* Initialize notifier */ + TlsGetHandleProc, /* Get OS handles out of channel */ + TlsClose2Proc, /* close2proc */ + TlsBlockModeProc, /* Set blocking/nonblocking mode*/ + NULL, /* Flush proc */ + TlsNotifyProc, /* Handling of events bubbling up */ + NULL, /* Wide seek proc */ + NULL, /* Thread action */ + NULL /* Truncate */ +}; + +const Tcl_ChannelType *Tls_ChannelType(void) { + return &tlsChannelType; } Index: generic/tlsInt.h ================================================================== --- generic/tlsInt.h +++ generic/tlsInt.h @@ -21,40 +21,41 @@ #include "tls.h" #include <errno.h> #include <string.h> #include <stdint.h> -#ifdef __WIN32__ +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <wincrypt.h> /* OpenSSL needs this on Windows */ #endif -#ifdef NO_PATENTS -# define NO_IDEA -# define NO_RC2 -# define NO_RC4 -# define NO_RC5 -# define NO_RSA -# ifndef NO_SSL2 -# define NO_SSL2 -# endif +/* Handle TCL 8.6 CONST changes */ +#ifndef CONST86 +# if TCL_MAJOR_VERSION > 8 +# define CONST86 const +# else +# define CONST86 +# endif +#endif + +/* + * Backwards compatibility for size type change + */ +#if TCL_MAJOR_VERSION < 9 && TCL_MINOR_VERSION < 7 + #ifndef Tcl_Size + typedef int Tcl_Size; + #endif + + #define TCL_SIZE_MODIFIER "" #endif #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> #include <openssl/opensslv.h> -/* - * Determine if we should use the pre-OpenSSL 1.1.0 API - */ -#undef TCLTLS_OPENSSL_PRE_1_1 -#if (defined(LIBRESSL_VERSION_NUMBER)) || OPENSSL_VERSION_NUMBER < 0x10100000L -# define TCLTLS_OPENSSL_PRE_1_1_API 1 -#endif - #ifndef ECONNABORTED #define ECONNABORTED 130 /* Software caused connection abort */ #endif #ifndef ECONNRESET #define ECONNRESET 131 /* Connection reset by peer */ @@ -61,138 +62,145 @@ #endif #ifdef TCLEXT_TCLTLS_DEBUG #include <ctype.h> #define dprintf(...) { \ - char dprintfBuffer[8192], *dprintfBuffer_p; \ - dprintfBuffer_p = &dprintfBuffer[0]; \ - dprintfBuffer_p += sprintf(dprintfBuffer_p, "%s:%i:%s():", __FILE__, __LINE__, __func__); \ - dprintfBuffer_p += sprintf(dprintfBuffer_p, __VA_ARGS__); \ - fprintf(stderr, "%s\n", dprintfBuffer); \ - } + char dprintfBuffer[8192], *dprintfBuffer_p; \ + dprintfBuffer_p = &dprintfBuffer[0]; \ + dprintfBuffer_p += sprintf(dprintfBuffer_p, "%s:%i:%s():", __FILE__, __LINE__, __func__); \ + dprintfBuffer_p += sprintf(dprintfBuffer_p, __VA_ARGS__); \ + fprintf(stderr, "%s\n", dprintfBuffer); \ +} #define dprintBuffer(bufferName, bufferLength) { \ - int dprintBufferIdx; \ - unsigned char dprintBufferChar; \ - fprintf(stderr, "%s:%i:%s():%s[%llu]={", __FILE__, __LINE__, __func__, #bufferName, (unsigned long long) bufferLength); \ - for (dprintBufferIdx = 0; dprintBufferIdx < bufferLength; dprintBufferIdx++) { \ - dprintBufferChar = bufferName[dprintBufferIdx]; \ - if (isalpha(dprintBufferChar) || isdigit(dprintBufferChar)) { \ - fprintf(stderr, "'%c' ", dprintBufferChar); \ - } else { \ - fprintf(stderr, "%02x ", (unsigned int) dprintBufferChar); \ - }; \ - }; \ - fprintf(stderr, "}\n"); \ - } + int dprintBufferIdx; \ + unsigned char dprintBufferChar; \ + fprintf(stderr, "%s:%i:%s():%s[%llu]={", __FILE__, __LINE__, __func__, #bufferName, (unsigned long long) bufferLength); \ + for (dprintBufferIdx = 0; dprintBufferIdx < bufferLength; dprintBufferIdx++) { \ + dprintBufferChar = bufferName[dprintBufferIdx]; \ + if (isalpha(dprintBufferChar) || isdigit(dprintBufferChar)) { \ + fprintf(stderr, "'%c' ", dprintBufferChar); \ + } else { \ + fprintf(stderr, "%02x ", (unsigned int) dprintBufferChar); \ + }; \ + }; \ + fprintf(stderr, "}\n"); \ +} #define dprintFlags(statePtr) { \ - char dprintfBuffer[8192], *dprintfBuffer_p; \ - dprintfBuffer_p = &dprintfBuffer[0]; \ - dprintfBuffer_p += sprintf(dprintfBuffer_p, "%s:%i:%s():%s->flags=0", __FILE__, __LINE__, __func__, #statePtr); \ - if (((statePtr)->flags & TLS_TCL_ASYNC) == TLS_TCL_ASYNC) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_ASYNC"); }; \ - if (((statePtr)->flags & TLS_TCL_SERVER) == TLS_TCL_SERVER) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_SERVER"); }; \ - if (((statePtr)->flags & TLS_TCL_INIT) == TLS_TCL_INIT) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_INIT"); }; \ - if (((statePtr)->flags & TLS_TCL_DEBUG) == TLS_TCL_DEBUG) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_DEBUG"); }; \ - if (((statePtr)->flags & TLS_TCL_CALLBACK) == TLS_TCL_CALLBACK) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_CALLBACK"); }; \ - if (((statePtr)->flags & TLS_TCL_HANDSHAKE_FAILED) == TLS_TCL_HANDSHAKE_FAILED) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_HANDSHAKE_FAILED"); }; \ - if (((statePtr)->flags & TLS_TCL_FASTPATH) == TLS_TCL_FASTPATH) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_FASTPATH"); }; \ - fprintf(stderr, "%s\n", dprintfBuffer); \ - } + char dprintfBuffer[8192], *dprintfBuffer_p; \ + dprintfBuffer_p = &dprintfBuffer[0]; \ + dprintfBuffer_p += sprintf(dprintfBuffer_p, "%s:%i:%s():%s->flags=0", __FILE__, __LINE__, __func__, #statePtr); \ + if (((statePtr)->flags & TLS_TCL_ASYNC) == TLS_TCL_ASYNC) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_ASYNC"); }; \ + if (((statePtr)->flags & TLS_TCL_SERVER) == TLS_TCL_SERVER) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_SERVER"); }; \ + if (((statePtr)->flags & TLS_TCL_INIT) == TLS_TCL_INIT) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_INIT"); }; \ + if (((statePtr)->flags & TLS_TCL_DEBUG) == TLS_TCL_DEBUG) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_DEBUG"); }; \ + if (((statePtr)->flags & TLS_TCL_CALLBACK) == TLS_TCL_CALLBACK) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_CALLBACK"); }; \ + if (((statePtr)->flags & TLS_TCL_HANDSHAKE_FAILED) == TLS_TCL_HANDSHAKE_FAILED) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_HANDSHAKE_FAILED"); }; \ + if (((statePtr)->flags & TLS_TCL_FASTPATH) == TLS_TCL_FASTPATH) { dprintfBuffer_p += sprintf(dprintfBuffer_p, "|TLS_TCL_FASTPATH"); }; \ + fprintf(stderr, "%s\n", dprintfBuffer); \ +} #else #define dprintf(...) if (0) { fprintf(stderr, __VA_ARGS__); } #define dprintBuffer(bufferName, bufferLength) /**/ #define dprintFlags(statePtr) /**/ #endif -#define TCLTLS_SSL_ERROR(ssl,err) ((char*)ERR_reason_error_string((unsigned long)SSL_get_error((ssl),(err)))) +#define GET_ERR_REASON() ERR_reason_error_string(ERR_get_error()) + +/* Common list append macros */ +#define LAPPEND_BARRAY(interp, obj, text, value, size) {\ + if (text != NULL) Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(text, -1)); \ + Tcl_ListObjAppendElement(interp, obj, Tcl_NewByteArrayObj(value, size)); \ +} +#define LAPPEND_STR(interp, obj, text, value, size) {\ + if (text != NULL) Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(text, -1)); \ + Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(value, size)); \ +} +#define LAPPEND_INT(interp, obj, text, value) {\ + if (text != NULL) Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(text, -1)); \ + Tcl_ListObjAppendElement(interp, obj, Tcl_NewIntObj(value)); \ +} +#define LAPPEND_LONG(interp, obj, text, value) {\ + if (text != NULL) Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(text, -1)); \ + Tcl_ListObjAppendElement(interp, obj, Tcl_NewLongObj(value)); \ +} +#define LAPPEND_BOOL(interp, obj, text, value) {\ + if (text != NULL) Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(text, -1)); \ + Tcl_ListObjAppendElement(interp, obj, Tcl_NewBooleanObj(value)); \ +} +#define LAPPEND_OBJ(interp, obj, text, listObj) {\ + if (text != NULL) Tcl_ListObjAppendElement(interp, obj, Tcl_NewStringObj(text, -1)); \ + Tcl_ListObjAppendElement(interp, obj, listObj); \ +} + /* * OpenSSL BIO Routines */ #define BIO_TYPE_TCL (19|0x0400) /* * Defines for State.flags */ -#define TLS_TCL_ASYNC (1<<0) /* non-blocking mode */ -#define TLS_TCL_SERVER (1<<1) /* Server-Side */ -#define TLS_TCL_INIT (1<<2) /* Initializing connection */ -#define TLS_TCL_DEBUG (1<<3) /* Show debug tracing */ +#define TLS_TCL_ASYNC (1<<0) /* non-blocking mode */ +#define TLS_TCL_SERVER (1<<1) /* Server-Side */ +#define TLS_TCL_INIT (1<<2) /* Initializing connection */ +#define TLS_TCL_DEBUG (1<<3) /* Show debug tracing */ #define TLS_TCL_CALLBACK (1<<4) /* In a callback, prevent update * looping problem. [Bug 1652380] */ -#define TLS_TCL_HANDSHAKE_FAILED (1<<5) /* Set on handshake failures and once - * set, all further I/O will result - * in ECONNABORTED errors. */ -#define TLS_TCL_FASTPATH (1<<6) /* The parent channel is being used directly by the SSL library */ +#define TLS_TCL_HANDSHAKE_FAILED (1<<5) /* Set on handshake failures and once set, all + * further I/O will result in ECONNABORTED errors. */ +#define TLS_TCL_FASTPATH (1<<6) /* The parent channel is being used directly by the SSL library */ #define TLS_TCL_DELAY (5) /* - * This structure describes the per-instance state - * of an ssl channel. + * This structure describes the per-instance state of an SSL channel. * * The SSL processing context is maintained here, in the ClientData */ typedef struct State { - Tcl_Channel self; /* this socket channel */ + Tcl_Channel self; /* this socket channel */ Tcl_TimerToken timer; - int flags; /* see State.flags above */ - int watchMask; /* current WatchProc mask */ - int mode; /* current mode of parent channel */ - - Tcl_Interp *interp; /* interpreter in which this resides */ - Tcl_Obj *callback; /* script called for tracing, verifying and errors */ - Tcl_Obj *password; /* script called for certificate password */ - - int vflags; /* verify flags */ - SSL *ssl; /* Struct for SSL processing */ - SSL_CTX *ctx; /* SSL Context */ - BIO *bio; /* Struct for SSL processing */ - BIO *p_bio; /* Parent BIO (that is layered on Tcl_Channel) */ - - const char *err; + int flags; /* see State.flags above */ + int watchMask; /* current WatchProc mask */ + int mode; /* current mode of parent channel */ + + Tcl_Interp *interp; /* interpreter in which this resides */ + Tcl_Obj *callback; /* script called for tracing, info, and errors */ + Tcl_Obj *password; /* script called for certificate password */ + Tcl_Obj *vcmd; /* script called to verify or validate protocol config */ + + int vflags; /* verify flags */ + SSL *ssl; /* Struct for SSL processing */ + SSL_CTX *ctx; /* SSL Context */ + BIO *bio; /* Struct for SSL processing */ + BIO *p_bio; /* Parent BIO (that is layered on Tcl_Channel) */ + + unsigned char *protos; /* List of supported protocols in protocol format */ + unsigned int protos_len; /* Length of protos */ + + char *err; } State; #ifdef USE_TCL_STUBS #ifndef Tcl_StackChannel #error "Unable to compile on this version of Tcl" #endif /* Tcl_GetStackedChannel */ #endif /* USE_TCL_STUBS */ -#ifndef JOIN -# define JOIN(a,b) JOIN1(a,b) -# define JOIN1(a,b) a##b -#endif - -#ifndef TCL_UNUSED -# if defined(__cplusplus) -# define TCL_UNUSED(T) T -# elif defined(__GNUC__) && (__GNUC__ > 2) -# define TCL_UNUSED(T) T JOIN(dummy, __LINE__) __attribute__((unused)) -# else -# define TCL_UNUSED(T) T JOIN(dummy, __LINE__) -# endif -#endif - -#if (TCL_MAJOR_VERSION < 9) && defined(TCL_MINOR_VERSION) && (TCL_MINOR_VERSION < 7) && !defined(Tcl_Size) -# define Tcl_Size int -#endif - /* * Forward declarations */ const Tcl_ChannelType *Tls_ChannelType(void); Tcl_Channel Tls_GetParent(State *statePtr, int maskFlags); Tcl_Obj *Tls_NewX509Obj(Tcl_Interp *interp, X509 *cert); +Tcl_Obj *Tls_NewCAObj(Tcl_Interp *interp, const SSL *ssl, int peer); void Tls_Error(State *statePtr, char *msg); -#if TCL_MAJOR_VERSION > 8 -void Tls_Free(void *blockPtr); -#else void Tls_Free(char *blockPtr); -#endif void Tls_Clean(State *statePtr); int Tls_WaitForConnect(State *statePtr, int *errorCodePtr, int handshakeFailureIsPermanent); BIO *BIO_new_tcl(State* statePtr, int flags); #define PTR2INT(x) ((int) ((intptr_t) (x))) #endif /* _TLSINT_H */ Index: generic/tlsX509.c ================================================================== --- generic/tlsX509.c +++ generic/tlsX509.c @@ -1,70 +1,356 @@ /* * Copyright (C) 1997-2000 Sensus Consulting Ltd. * Matt Newman <matt@sensus.org> - */ -#include "tlsInt.h" - -/* - * Ensure these are not macros - known to be defined on Win32 - */ -#ifdef min -#undef min -#endif - -#ifdef max -#undef max -#endif - -static int min(int a, int b) -{ - return (a < b) ? a : b; -} - -static int max(int a, int b) -{ - return (a > b) ? a : b; -} - -/* - * ASN1_UTCTIME_tostr -- - */ -static char * -ASN1_UTCTIME_tostr(ASN1_UTCTIME *tm) -{ - static char bp[128]; - char *v; - int gmt=0; - static char *mon[12]={ - "Jan","Feb","Mar","Apr","May","Jun", - "Jul","Aug","Sep","Oct","Nov","Dec"}; - int i; - int y=0,M=0,d=0,h=0,m=0,s=0; - - i=tm->length; - v=(char *)tm->data; - - if (i < 10) goto err; - if (v[i-1] == 'Z') gmt=1; - for (i=0; i<10; i++) - if ((v[i] > '9') || (v[i] < '0')) goto err; - y= (v[0]-'0')*10+(v[1]-'0'); - if (y < 70) y+=100; - M= (v[2]-'0')*10+(v[3]-'0'); - if ((M > 12) || (M < 1)) goto err; - d= (v[4]-'0')*10+(v[5]-'0'); - h= (v[6]-'0')*10+(v[7]-'0'); - m= (v[8]-'0')*10+(v[9]-'0'); - if ( (v[10] >= '0') && (v[10] <= '9') && - (v[11] >= '0') && (v[11] <= '9')) - s= (v[10]-'0')*10+(v[11]-'0'); - - sprintf(bp,"%s %2d %02d:%02d:%02d %d%s", - mon[M-1],d,h,m,s,y+1900,(gmt)?" GMT":""); - return bp; - err: - return "Bad time value"; + * Copyright (C) 2023 Brian O'Hagan + */ +#include <tcl.h> +#include <stdio.h> +#include <openssl/bio.h> +#include <openssl/sha.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/x509_vfy.h> +#include <openssl/asn1.h> +#include "tlsInt.h" + +/* Define maximum certificate size. Max PEM size 100kB and DER size is 24kB. */ +#define CERT_STR_SIZE 32768 + + +/* + * Binary string to hex string + */ +int String_to_Hex(unsigned char* input, int ilen, unsigned char *output, int olen) { + int count = 0; + unsigned char *iptr = input; + unsigned char *optr = &output[0]; + const char *hex = "0123456789abcdef"; + + for (int i = 0; i < ilen && count < olen - 1; i++, count += 2) { + *optr++ = hex[(*iptr>>4)&0xF]; + *optr++ = hex[(*iptr++)&0xF]; + } + *optr = 0; + return count; +} + +/* + * BIO to Buffer + */ +int BIO_to_Buffer(int result, BIO *bio, void *buffer, int size) { + int len = 0; + int pending = BIO_pending(bio); + + if (result) { + len = BIO_read(bio, buffer, (pending < size) ? pending : size); + (void)BIO_flush(bio); + if (len < 0) { + len = 0; + } + } + return len; +} + +/* + * Get X509 Certificate Extensions + */ +Tcl_Obj *Tls_x509Extensions(Tcl_Interp *interp, X509 *cert) { + const STACK_OF(X509_EXTENSION) *exts; + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + + if (listPtr == NULL) { + return NULL; + } + + if ((exts = X509_get0_extensions(cert)) != NULL) { + for (int i=0; i < X509_get_ext_count(cert); i++) { + X509_EXTENSION *ex = sk_X509_EXTENSION_value(exts, i); + ASN1_OBJECT *obj = X509_EXTENSION_get_object(ex); + /* ASN1_OCTET_STRING *data = X509_EXTENSION_get_data(ex); */ + int critical = X509_EXTENSION_get_critical(ex); + LAPPEND_BOOL(interp, listPtr, OBJ_nid2ln(OBJ_obj2nid(obj)), critical); + } + } + return listPtr; +} + +/* + * Get Authority and Subject Key Identifiers + */ +Tcl_Obj *Tls_x509Identifier(const ASN1_OCTET_STRING *astring) { + Tcl_Obj *resultPtr = NULL; + int len = 0; + unsigned char buffer[1024]; + + if (astring != NULL) { + len = String_to_Hex((unsigned char *)ASN1_STRING_get0_data(astring), + ASN1_STRING_length(astring), buffer, 1024); + } + resultPtr = Tcl_NewStringObj((char *) &buffer[0], (Tcl_Size) len); + return resultPtr; +} + +/* + * Get Key Usage + */ +Tcl_Obj *Tls_x509KeyUsage(Tcl_Interp *interp, X509 *cert, uint32_t xflags) { + uint32_t usage = X509_get_key_usage(cert); + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + + if (listPtr == NULL) { + return NULL; + } + + if ((xflags & EXFLAG_KUSAGE) && usage < UINT32_MAX) { + if (usage & KU_DIGITAL_SIGNATURE) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Digital Signature", -1)); + } + if (usage & KU_NON_REPUDIATION) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Non-Repudiation", -1)); + } + if (usage & KU_KEY_ENCIPHERMENT) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Key Encipherment", -1)); + } + if (usage & KU_DATA_ENCIPHERMENT) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Data Encipherment", -1)); + } + if (usage & KU_KEY_AGREEMENT) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Key Agreement", -1)); + } + if (usage & KU_KEY_CERT_SIGN) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Certificate Signing", -1)); + } + if (usage & KU_CRL_SIGN) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("CRL Signing", -1)); + } + if (usage & KU_ENCIPHER_ONLY) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Encipher Only", -1)); + } + if (usage & KU_DECIPHER_ONLY) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Decipher Only", -1)); + } + } else { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("unrestricted", -1)); + } + return listPtr; +} + +/* + * Get Certificate Purpose + */ +char *Tls_x509Purpose(X509 *cert) { + char *purpose = NULL; + + if (X509_check_purpose(cert, X509_PURPOSE_SSL_CLIENT, 0) > 0) { + purpose = "SSL Client"; + } else if (X509_check_purpose(cert, X509_PURPOSE_SSL_SERVER, 0) > 0) { + purpose = "SSL Server"; + } else if (X509_check_purpose(cert, X509_PURPOSE_NS_SSL_SERVER, 0) > 0) { + purpose = "MSS SSL Server"; + } else if (X509_check_purpose(cert, X509_PURPOSE_SMIME_SIGN, 0) > 0) { + purpose = "SMIME Signing"; + } else if (X509_check_purpose(cert, X509_PURPOSE_SMIME_ENCRYPT, 0) > 0) { + purpose = "SMIME Encryption"; + } else if (X509_check_purpose(cert, X509_PURPOSE_CRL_SIGN, 0) > 0) { + purpose = "CRL Signing"; + } else if (X509_check_purpose(cert, X509_PURPOSE_ANY, 0) > 0) { + purpose = "Any"; + } else if (X509_check_purpose(cert, X509_PURPOSE_OCSP_HELPER, 0) > 0) { + purpose = "OCSP Helper"; + } else if (X509_check_purpose(cert, X509_PURPOSE_TIMESTAMP_SIGN, 0) > 0) { + purpose = "Timestamp Signing"; + } else { + purpose = ""; + } + return purpose; +} + +/* + * For each purpose, get certificate applicability + */ +Tcl_Obj *Tls_x509Purposes(Tcl_Interp *interp, X509 *cert) { + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + X509_PURPOSE *ptmp; + + if (listPtr == NULL) { + return NULL; + } + + for (int i = 0; i < X509_PURPOSE_get_count(); i++) { + ptmp = X509_PURPOSE_get0(i); + Tcl_Obj *tmpPtr = Tcl_NewListObj(0, NULL); + + for (int j = 0; j < 2; j++) { + int idret = X509_check_purpose(cert, X509_PURPOSE_get_id(ptmp), j); + Tcl_ListObjAppendElement(interp, tmpPtr, Tcl_NewStringObj(j ? "CA" : "nonCA", -1)); + Tcl_ListObjAppendElement(interp, tmpPtr, Tcl_NewStringObj(idret == 1 ? "Yes" : "No", -1)); + } + LAPPEND_OBJ(interp, listPtr, X509_PURPOSE_get0_name(ptmp), tmpPtr); + } + return listPtr; +} + +/* + * Get Subject Alternate Names (SAN) and Issuer Alternate Names + */ +Tcl_Obj *Tls_x509Names(Tcl_Interp *interp, X509 *cert, int nid, BIO *bio) { + STACK_OF(GENERAL_NAME) *names; + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + int len; + char buffer[1024]; + + if (listPtr == NULL) { + return NULL; + } + + if ((names = X509_get_ext_d2i(cert, nid, NULL, NULL)) != NULL) { + for (int i=0; i < sk_GENERAL_NAME_num(names); i++) { + const GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); + + len = BIO_to_Buffer(name && GENERAL_NAME_print(bio, (GENERAL_NAME *) name), bio, buffer, 1024); + LAPPEND_STR(interp, listPtr, NULL, buffer, (Tcl_Size) len); + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + } + return listPtr; +} + +/* + * Get EXtended Key Usage + */ +Tcl_Obj *Tls_x509ExtKeyUsage(Tcl_Interp *interp, X509 *cert, uint32_t xflags) { + uint32_t usage = X509_get_key_usage(cert); + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + + if (listPtr == NULL) { + return NULL; + } + + if ((xflags & EXFLAG_XKUSAGE) && usage < UINT32_MAX) { + usage = X509_get_extended_key_usage(cert); + + if (usage & XKU_SSL_SERVER) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("TLS Web Server Authentication", -1)); + } + if (usage & XKU_SSL_CLIENT) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("TLS Web Client Authentication", -1)); + } + if (usage & XKU_SMIME) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("E-mail Protection", -1)); + } + if (usage & XKU_CODE_SIGN) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Code Signing", -1)); + } + if (usage & XKU_SGC) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("SGC", -1)); + } + if (usage & XKU_OCSP_SIGN) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("OCSP Signing", -1)); + } + if (usage & XKU_TIMESTAMP) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Time Stamping", -1)); + } + if (usage & XKU_DVCS ) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("DVCS", -1)); + } + if (usage & XKU_ANYEKU) { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("Any Extended Key Usage", -1)); + } + } else { + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj("unrestricted", -1)); + } + return listPtr; +} + +/* + * Get CRL Distribution Points + */ +Tcl_Obj *Tls_x509CrlDp(Tcl_Interp *interp, X509 *cert) { + STACK_OF(DIST_POINT) *crl; + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + + if (listPtr == NULL) { + return NULL; + } + + if ((crl = X509_get_ext_d2i(cert, NID_crl_distribution_points, NULL, NULL)) != NULL) { + for (int i=0; i < sk_DIST_POINT_num(crl); i++) { + DIST_POINT *dp = sk_DIST_POINT_value(crl, i); + DIST_POINT_NAME *distpoint = dp->distpoint; + + if (distpoint->type == 0) { + /* full-name GENERALIZEDNAME */ + for (int j = 0; j < sk_GENERAL_NAME_num(distpoint->name.fullname); j++) { + GENERAL_NAME *gen = sk_GENERAL_NAME_value(distpoint->name.fullname, j); + int type; + ASN1_STRING *uri = GENERAL_NAME_get0_value(gen, &type); + if (type == GEN_URI) { + LAPPEND_STR(interp, listPtr, (char *) NULL, (char *) ASN1_STRING_get0_data(uri), (Tcl_Size) ASN1_STRING_length(uri)); + } + } + } else if (distpoint->type == 1) { + /* relative-name X509NAME */ + STACK_OF(X509_NAME_ENTRY) *sk_relname = distpoint->name.relativename; + for (int j = 0; j < sk_X509_NAME_ENTRY_num(sk_relname); j++) { + X509_NAME_ENTRY *e = sk_X509_NAME_ENTRY_value(sk_relname, j); + ASN1_STRING *d = X509_NAME_ENTRY_get_data(e); + LAPPEND_STR(interp, listPtr, (char *) NULL, (char *) ASN1_STRING_data(d), (Tcl_Size) ASN1_STRING_length(d)); + } + } + } + CRL_DIST_POINTS_free(crl); + } + return listPtr; +} + +/* + * Get On-line Certificate Status Protocol (OSCP) URL + */ +Tcl_Obj *Tls_x509Oscp(Tcl_Interp *interp, X509 *cert) { + STACK_OF(OPENSSL_STRING) *ocsp; + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + + if (listPtr == NULL) { + return NULL; + } + + if ((ocsp = X509_get1_ocsp(cert)) != NULL) { + for (int i = 0; i < sk_OPENSSL_STRING_num(ocsp); i++) { + LAPPEND_STR(interp, listPtr, NULL, sk_OPENSSL_STRING_value(ocsp, i), -1); + } + X509_email_free(ocsp); + } + return listPtr; +} + +/* + * Get Certificate Authority (CA) Issuers URL + */ +Tcl_Obj *Tls_x509CaIssuers(Tcl_Interp *interp, X509 *cert) { + STACK_OF(ACCESS_DESCRIPTION) *ads; + ACCESS_DESCRIPTION *ad; + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + unsigned char *buf; + int len; + + if ((ads = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL)) != NULL) { + for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(ads); i++) { + ad = sk_ACCESS_DESCRIPTION_value(ads, i); + if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers && ad->location) { + if (ad->location->type == GEN_URI) { + len = ASN1_STRING_to_UTF8(&buf, ad->location->d.uniformResourceIdentifier); + Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj((char *) buf, (Tcl_Size) len)); + OPENSSL_free(buf); + break; + } + } + } + /* sk_ACCESS_DESCRIPTION_pop_free(ads, ACCESS_DESCRIPTION_free); */ + AUTHORITY_INFO_ACCESS_free(ads); + } + return listPtr; } /* *------------------------------------------------------* * @@ -72,138 +358,256 @@ * * ------------------------------------------------* * Converts a X509 certificate into a Tcl_Obj * ------------------------------------------------* * - * Sideeffects: + * Side effects: * None * * Result: * A Tcl List Object representing the provided * X509 certificate. * *------------------------------------------------------* */ -#define CERT_STR_SIZE 16384 - -Tcl_Obj* -Tls_NewX509Obj( interp, cert) - Tcl_Interp *interp; - X509 *cert; -{ - Tcl_Obj *certPtr = Tcl_NewListObj( 0, NULL); - BIO *bio; - int n; - unsigned long flags; - char subject[BUFSIZ]; - char issuer[BUFSIZ]; - char serial[BUFSIZ]; - char notBefore[BUFSIZ]; - char notAfter[BUFSIZ]; - char certStr[CERT_STR_SIZE], *certStr_p; - int certStr_len, toRead; -#ifndef NO_SSL_SHA - int shai; - char sha_hash_ascii[SHA_DIGEST_LENGTH * 2 + 1]; - unsigned char sha_hash_binary[SHA_DIGEST_LENGTH]; - const char *shachars="0123456789ABCDEF"; - - sha_hash_ascii[SHA_DIGEST_LENGTH * 2] = '\0'; -#endif - - certStr[0] = 0; - if ((bio = BIO_new(BIO_s_mem())) == NULL) { - subject[0] = 0; - issuer[0] = 0; - serial[0] = 0; - } else { - flags = XN_FLAG_RFC2253 | ASN1_STRFLGS_UTF8_CONVERT; - flags &= ~ASN1_STRFLGS_ESC_MSB; - - X509_NAME_print_ex(bio, X509_get_subject_name(cert), 0, flags); - n = BIO_read(bio, subject, min(BIO_pending(bio), BUFSIZ - 1)); - n = max(n, 0); - subject[n] = 0; - (void)BIO_flush(bio); - - X509_NAME_print_ex(bio, X509_get_issuer_name(cert), 0, flags); - n = BIO_read(bio, issuer, min(BIO_pending(bio), BUFSIZ - 1)); - n = max(n, 0); - issuer[n] = 0; - (void)BIO_flush(bio); - - i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert)); - n = BIO_read(bio, serial, min(BIO_pending(bio), BUFSIZ - 1)); - n = max(n, 0); - serial[n] = 0; - (void)BIO_flush(bio); - - if (PEM_write_bio_X509(bio, cert)) { - certStr_p = certStr; - certStr_len = 0; - while (1) { - toRead = min(BIO_pending(bio), CERT_STR_SIZE - certStr_len - 1); - toRead = min(toRead, BUFSIZ); - if (toRead == 0) { - break; - } - dprintf("Reading %i bytes from the certificate...", toRead); - n = BIO_read(bio, certStr_p, toRead); - if (n <= 0) { - break; - } - certStr_len += n; - certStr_p += n; - } - *certStr_p = '\0'; - (void)BIO_flush(bio); - } - - BIO_free(bio); - } - - strcpy( notBefore, ASN1_UTCTIME_tostr( X509_get_notBefore(cert) )); - strcpy( notAfter, ASN1_UTCTIME_tostr( X509_get_notAfter(cert) )); - -#ifndef NO_SSL_SHA - X509_digest(cert, EVP_sha1(), sha_hash_binary, NULL); - for (shai = 0; shai < SHA_DIGEST_LENGTH; shai++) { - sha_hash_ascii[shai * 2] = shachars[(sha_hash_binary[shai] & 0xF0) >> 4]; - sha_hash_ascii[shai * 2 + 1] = shachars[(sha_hash_binary[shai] & 0x0F)]; - } - Tcl_ListObjAppendElement( interp, certPtr, Tcl_NewStringObj("sha1_hash", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, Tcl_NewStringObj(sha_hash_ascii, SHA_DIGEST_LENGTH * 2) ); - -#endif - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( "subject", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( subject, -1) ); - - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( "issuer", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( issuer, -1) ); - - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( "notBefore", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( notBefore, -1) ); - - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( "notAfter", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( notAfter, -1) ); - - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( "serial", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( serial, -1) ); - - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( "certificate", -1) ); - Tcl_ListObjAppendElement( interp, certPtr, - Tcl_NewStringObj( certStr, -1) ); - +Tcl_Obj* +Tls_NewX509Obj(Tcl_Interp *interp, X509 *cert) { + Tcl_Obj *certPtr = Tcl_NewListObj(0, NULL); + BIO *bio = BIO_new(BIO_s_mem()); + int mdnid, pknid, bits, len; + unsigned int ulen; + uint32_t xflags; + char buffer[BUFSIZ]; + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned long flags = XN_FLAG_RFC2253 | ASN1_STRFLGS_UTF8_CONVERT; + flags &= ~ASN1_STRFLGS_ESC_MSB; + + if (interp == NULL || cert == NULL || bio == NULL || certPtr == NULL) { + return NULL; + } + + /* Signature algorithm and value - RFC 5280 section 4.1.1.2 and 4.1.1.3 */ + /* signatureAlgorithm is the id of the cryptographic algorithm used by the + CA to sign this cert. signatureValue is the digital signature computed + upon the ASN.1 DER encoded tbsCertificate. */ + { + const X509_ALGOR *sig_alg; + const ASN1_BIT_STRING *sig; + int sig_nid; + + X509_get0_signature(&sig, &sig_alg, cert); + /* sig_nid = X509_get_signature_nid(cert) */ + sig_nid = OBJ_obj2nid(sig_alg->algorithm); + LAPPEND_STR(interp, certPtr, "signatureAlgorithm", OBJ_nid2ln(sig_nid), -1); + len = (sig_nid != NID_undef) ? String_to_Hex(sig->data, sig->length, (unsigned char *) buffer, BUFSIZ) : 0; + LAPPEND_STR(interp, certPtr, "signatureValue", buffer, (Tcl_Size) len); + } + + /* Version of the encoded certificate - RFC 5280 section 4.1.2.1 */ + LAPPEND_LONG(interp, certPtr, "version", X509_get_version(cert)+1); + + /* Unique number assigned by CA to certificate - RFC 5280 section 4.1.2.2 */ + len = BIO_to_Buffer(i2a_ASN1_INTEGER(bio, X509_get0_serialNumber(cert)), bio, buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "serialNumber", buffer, (Tcl_Size) len); + + /* Signature algorithm used by the CA to sign the certificate. Must match + signatureAlgorithm. RFC 5280 section 4.1.2.3 */ + LAPPEND_STR(interp, certPtr, "signature", OBJ_nid2ln(X509_get_signature_nid(cert)), -1); + + /* Issuer identifies the entity that signed and issued the cert. RFC 5280 section 4.1.2.4 */ + len = BIO_to_Buffer(X509_NAME_print_ex(bio, X509_get_issuer_name(cert), 0, flags), bio, buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "issuer", buffer, (Tcl_Size) len); + + /* Certificate validity period is the interval the CA warrants that it will + maintain info on the status of the certificate. RFC 5280 section 4.1.2.5 */ + /* Get Validity - Not Before */ + len = BIO_to_Buffer(ASN1_TIME_print(bio, X509_get0_notBefore(cert)), bio, buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "notBefore", buffer, (Tcl_Size) len); + + /* Get Validity - Not After */ + len = BIO_to_Buffer(ASN1_TIME_print(bio, X509_get0_notAfter(cert)), bio, buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "notAfter", buffer, (Tcl_Size) len); + + /* Subject identifies the entity associated with the public key stored in + the subject public key field. RFC 5280 section 4.1.2.6 */ + len = BIO_to_Buffer(X509_NAME_print_ex(bio, X509_get_subject_name(cert), 0, flags), bio, buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "subject", buffer, (Tcl_Size) len); + + /* SHA1 Digest (Fingerprint) of cert - DER representation */ + if (X509_digest(cert, EVP_sha1(), md, &ulen)) { + len = String_to_Hex(md, len, (unsigned char *) buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "sha1_hash", buffer, (Tcl_Size) ulen); + } + + /* SHA256 Digest (Fingerprint) of cert - DER representation */ + if (X509_digest(cert, EVP_sha256(), md, &ulen)) { + len = String_to_Hex(md, len, (unsigned char *) buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "sha256_hash", buffer, (Tcl_Size) ulen); + } + + /* Subject Public Key Info specifies the public key and identifies the + algorithm with which the key is used. RFC 5280 section 4.1.2.7 */ + if (X509_get_signature_info(cert, &mdnid, &pknid, &bits, &xflags)) { + ASN1_BIT_STRING *key; + unsigned int n; + + LAPPEND_STR(interp, certPtr, "signingDigest", OBJ_nid2ln(mdnid), -1); + LAPPEND_STR(interp, certPtr, "publicKeyAlgorithm", OBJ_nid2ln(pknid), -1); + LAPPEND_INT(interp, certPtr, "bits", bits); /* Effective security bits */ + + key = X509_get0_pubkey_bitstr(cert); + len = String_to_Hex(key->data, key->length, (unsigned char *) buffer, BUFSIZ); + LAPPEND_STR(interp, certPtr, "publicKey", buffer, (Tcl_Size) len); + + len = 0; + if (X509_pubkey_digest(cert, EVP_get_digestbynid(pknid), md, &n)) { + len = String_to_Hex(md, (int) n, (unsigned char *) buffer, BUFSIZ); + } + LAPPEND_STR(interp, certPtr, "publicKeyHash", buffer, (Tcl_Size) len); + + /* digest of the DER representation of the certificate */ + len = 0; + if (X509_digest(cert, EVP_get_digestbynid(mdnid), md, &n)) { + len = String_to_Hex(md, (int) n, (unsigned char *) buffer, BUFSIZ); + } + LAPPEND_STR(interp, certPtr, "signatureHash", buffer, (Tcl_Size) len); + } + + /* Certificate Purpose. Call before checking for extensions. */ + LAPPEND_STR(interp, certPtr, "purpose", Tls_x509Purpose(cert), -1); + LAPPEND_OBJ(interp, certPtr, "certificatePurpose", Tls_x509Purposes(interp, cert)); + + /* Get extensions flags */ + xflags = X509_get_extension_flags(cert); + LAPPEND_INT(interp, certPtr, "extFlags", xflags); + + /* Check if cert was issued by CA cert issuer or self signed */ + LAPPEND_BOOL(interp, certPtr, "selfIssued", xflags & EXFLAG_SI); + LAPPEND_BOOL(interp, certPtr, "selfSigned", xflags & EXFLAG_SS); + LAPPEND_BOOL(interp, certPtr, "isProxyCert", xflags & EXFLAG_PROXY); + LAPPEND_BOOL(interp, certPtr, "extInvalid", xflags & EXFLAG_INVALID); + LAPPEND_BOOL(interp, certPtr, "isCACert", X509_check_ca(cert)); + + /* The Unique Ids are used to handle the possibility of reuse of subject + and/or issuer names over time. RFC 5280 section 4.1.2.8 */ + { + const ASN1_BIT_STRING *iuid, *suid; + X509_get0_uids(cert, &iuid, &suid); + + Tcl_ListObjAppendElement(interp, certPtr, Tcl_NewStringObj("issuerUniqueId", -1)); + if (iuid != NULL) { + Tcl_ListObjAppendElement(interp, certPtr, Tcl_NewByteArrayObj((const unsigned char *)iuid->data, (Tcl_Size) iuid->length)); + } else { + Tcl_ListObjAppendElement(interp, certPtr, Tcl_NewStringObj("", -1)); + } + + Tcl_ListObjAppendElement(interp, certPtr, Tcl_NewStringObj("subjectUniqueId", -1)); + if (suid != NULL) { + Tcl_ListObjAppendElement(interp, certPtr, Tcl_NewByteArrayObj((const unsigned char *)suid->data, (Tcl_Size) suid->length)); + } else { + Tcl_ListObjAppendElement(interp, certPtr, Tcl_NewStringObj("", -1)); + } + } + + /* X509 v3 Extensions - RFC 5280 section 4.1.2.9 */ + LAPPEND_INT(interp, certPtr, "extCount", X509_get_ext_count(cert)); + LAPPEND_OBJ(interp, certPtr, "extensions", Tls_x509Extensions(interp, cert)); + + /* Authority Key Identifier (AKI) is the Subject Key Identifier (SKI) of + its signer (the CA). RFC 5280 section 4.2.1.1, NID_authority_key_identifier */ + LAPPEND_OBJ(interp, certPtr, "authorityKeyIdentifier", + Tls_x509Identifier(X509_get0_authority_key_id(cert))); + + /* Subject Key Identifier (SKI) is used to identify certificates that contain + a particular public key. RFC 5280 section 4.2.1.2, NID_subject_key_identifier */ + LAPPEND_OBJ(interp, certPtr, "subjectKeyIdentifier", + Tls_x509Identifier(X509_get0_subject_key_id(cert))); + + /* Key usage extension defines the purpose (e.g., encipherment, signature, certificate + signing) of the key in the certificate. RFC 5280 section 4.2.1.3, NID_key_usage */ + LAPPEND_OBJ(interp, certPtr, "keyUsage", Tls_x509KeyUsage(interp, cert, xflags)); + + /* Certificate Policies - indicates the issuing CA considers its issuerDomainPolicy + equivalent to the subject CA's subjectDomainPolicy. RFC 5280 section 4.2.1.4, NID_certificate_policies */ + if (xflags & EXFLAG_INVALID_POLICY) { + /* Reject cert */ + } + + /* Policy Mappings - RFC 5280 section 4.2.1.5, NID_policy_mappings */ + + /* Subject Alternative Name (SAN) contains additional URLs, DNS names, or IP + addresses bound to certificate. RFC 5280 section 4.2.1.6, NID_subject_alt_name */ + LAPPEND_OBJ(interp, certPtr, "subjectAltName", Tls_x509Names(interp, cert, NID_subject_alt_name, bio)); + + /* Issuer Alternative Name is used to associate Internet style identities + with the certificate issuer. RFC 5280 section 4.2.1.7, NID_issuer_alt_name */ + LAPPEND_OBJ(interp, certPtr, "issuerAltName", Tls_x509Names(interp, cert, NID_issuer_alt_name, bio)); + + /* Subject Directory Attributes provides identification attributes (e.g., nationality) + of the subject. RFC 5280 section 4.2.1.8 (subjectDirectoryAttributes) */ + + /* Basic Constraints identifies whether the subject of the cert is a CA and + the max depth of valid cert paths for this cert. RFC 5280 section 4.2.1.9, NID_basic_constraints */ + if (!(xflags & EXFLAG_PROXY)) { + LAPPEND_LONG(interp, certPtr, "pathLen", X509_get_pathlen(cert)); + } else { + LAPPEND_LONG(interp, certPtr, "pathLen", X509_get_proxy_pathlen(cert)); + } + LAPPEND_BOOL(interp, certPtr, "basicConstraintsCA", xflags & EXFLAG_CA); + + /* Name Constraints is only used in CA certs to indicate the name space for + all subject names in subsequent certificates in a certification path + MUST be located. RFC 5280 section 4.2.1.10, NID_name_constraints */ + + /* Policy Constraints is only used in CA certs to limit the length of a + cert chain for that CA. RFC 5280 section 4.2.1.11, NID_policy_constraints */ + + /* Extended Key Usage indicates the purposes the certified public key may be + used, beyond the basic purposes. RFC 5280 section 4.2.1.12, NID_ext_key_usage */ + LAPPEND_OBJ(interp, certPtr, "extendedKeyUsage", Tls_x509ExtKeyUsage(interp, cert, xflags)); + + /* CRL Distribution Points identifies where CRL information can be obtained. + RFC 5280 section 4.2.1.13*/ + LAPPEND_OBJ(interp, certPtr, "crlDistributionPoints", Tls_x509CrlDp(interp, cert)); + + /* Freshest CRL extension */ + if (xflags & EXFLAG_FRESHEST) { + } + + /* Authority Information Access indicates how to access info and services + for the certificate issuer. RFC 5280 section 4.2.2.1, NID_info_access */ + + /* Get On-line Certificate Status Protocol (OSCP) Responders URL */ + LAPPEND_OBJ(interp, certPtr, "ocspResponders", Tls_x509Oscp(interp, cert)); + + /* Get Certificate Authority (CA) Issuers URL */ + LAPPEND_OBJ(interp, certPtr, "caIssuers", Tls_x509CaIssuers(interp, cert)); + + /* Subject Information Access - RFC 5280 section 4.2.2.2, NID_sinfo_access */ + + /* Certificate Alias. If uses a PKCS#12 structure, alias will reflect the + friendlyName attribute (RFC 2985). */ + { + len = 0; + unsigned char *string = X509_alias_get0(cert, &len); + LAPPEND_STR(interp, certPtr, "alias", (char *) string, (Tcl_Size) len); + string = X509_keyid_get0(cert, &len); + LAPPEND_STR(interp, certPtr, "keyId", (char *) string, (Tcl_Size) len); + } + + /* Certificate and dump all data */ + { + char certStr[CERT_STR_SIZE]; + + /* Get certificate */ + len = BIO_to_Buffer(PEM_write_bio_X509(bio, cert), bio, certStr, CERT_STR_SIZE); + LAPPEND_STR(interp, certPtr, "certificate", certStr, (Tcl_Size) len); + + /* Get all cert info */ + len = BIO_to_Buffer(X509_print_ex(bio, cert, flags, 0), bio, certStr, CERT_STR_SIZE); + LAPPEND_STR(interp, certPtr, "all", certStr, (Tcl_Size) len); + } + + BIO_free(bio); return certPtr; } Index: library/tls.tcl ================================================================== --- library/tls.tcl +++ library/tls.tcl @@ -30,30 +30,38 @@ variable socketOptionRules { {0 -async sopts 0} {* -myaddr sopts 1} {0 -myport sopts 1} {* -type sopts 1} + {* -alpn iopts 1} {* -cadir iopts 1} {* -cafile iopts 1} {* -cert iopts 1} {* -certfile iopts 1} {* -cipher iopts 1} + {* -ciphersuites iopts 1} {* -command iopts 1} {* -dhparams iopts 1} {* -key iopts 1} {* -keyfile iopts 1} {* -password iopts 1} + {* -post_handshake iopts 1} {* -request iopts 1} {* -require iopts 1} + {* -securitylevel iopts 1} {* -autoservername discardOpts 1} + {* -server iopts 1} {* -servername iopts 1} + {* -session_id iopts 1} {* -ssl2 iopts 1} {* -ssl3 iopts 1} {* -tls1 iopts 1} {* -tls1.1 iopts 1} {* -tls1.2 iopts 1} {* -tls1.3 iopts 1} + {* -validatecommand iopts 1} + {* -vcmd iopts 1} } # tls::socket and tls::init options as a humane readable string variable socketOptionsNoServer variable socketOptionsServer @@ -142,11 +150,11 @@ # dlls must be copied out of the virtual filesystem to the disk # where Windows will find them when resolving the dependency in # the tls dll. We choose to make them siblings of the executable. package require starkit set dst [file nativename [file dirname $starkit::topdir]] - foreach sdll [glob -nocomplain -directory $dir -tails *eay32.dll] { + foreach sdll [glob -nocomplain -directory $dir -tails libssl32.dll libcrypto*.dll libssl*.dll libssp*.dll] { catch {file delete -force $dst/$sdll} catch {file copy -force $dir/$sdll $dst/$sdll} } } set res [catch {uplevel #0 [list load [file join [pwd] $dll]]} err] @@ -303,10 +311,11 @@ error $err $::errorInfo $::errorCode } else { log 2 "tls::_accept - called \"$callback\" succeeded" } } + # # Sample callback for hooking: - # # error # verify @@ -316,16 +325,71 @@ variable debug #log 2 [concat $option $args] switch -- $option { - "error" { + "error" { foreach {chan msg} $args break log 0 "TLS/$chan: error: $msg" } - "verify" { + "info" { + # poor man's lassign + foreach {chan major minor msg type} $args break + + if {$msg != ""} { + append state ": $msg" + } + # For tracing + upvar #0 tls::$chan cb + set cb($major) $minor + + log 2 "TLS/$chan: $major/$minor: $state" + } + "message" { + # poor man's lassign + foreach {chan direction version content_type msg} $args break + + log 0 "TLS/$chan: info: $direction $msg" + } + "session" { + foreach {chan session_id ticket lifetime} $args break + + log 0 "TLS/$chan: session: lifetime $lifetime" + } + default { + return -code error "bad option \"$option\":\ + must be one of error, info, or session" + } + } +} + +# +# Sample callback when return value is needed +# +proc tls::validate_command {option args} { + variable debug + + #log 2 [concat $option $args] + + switch -- $option { + "alpn" { + foreach {chan protocol match} $args break + + log 0 "TLS/$chan: alpn: $protocol $match" + } + "hello" { + foreach {chan servername} $args break + + log 0 "TLS/$chan: hello: $servername" + } + "sni" { + foreach {chan servername} $args break + + log 0 "TLS/$chan: sni: $servername" + } + "verify" { # poor man's lassign foreach {chan depth cert rc err} $args break array set c $cert @@ -338,28 +402,16 @@ return 1; # FORCE OK } else { return $rc } } - "info" { - # poor man's lassign - foreach {chan major minor state msg} $args break - - if {$msg != ""} { - append state ": $msg" - } - # For tracing - upvar #0 tls::$chan cb - set cb($major) $minor - - log 2 "TLS/$chan: $major/$minor: $state" - } default { return -code error "bad option \"$option\":\ - must be one of error, info, or verify" + must be one of alpn, info, or verify" } } + return 1 } proc tls::xhandshake {chan} { upvar #0 tls::$chan cb @@ -376,11 +428,11 @@ return 1 } } } -proc tls::password {} { +proc tls::password {rwflag size} { log 0 "TLS/Password: did you forget to set your passwd!" # Return the worlds best kept secret password. return "secret" } ADDED tests/README.txt Index: tests/README.txt ================================================================== --- /dev/null +++ tests/README.txt @@ -0,0 +1,23 @@ +Create Test Cases + +1. Create the test case *.csv file. You can use multiple files. Generally it's a good idea to group like functions in the same file. + +2. Add test cases to *.csv files. Each test case is on a separate line. The column titles correspond to the tcltest tool options. Leave a column blank if not used. + +3. Define any common functions in a common.tcl or in *.csv file. + +4. To create the test cases script, execute make_test_files.tcl. This will use the *.csv files to create the *.test files. + + +Execute Test Suite + +5. To run the test suite, execute the all.tcl file. + + +Special Notes + +On systems that don't use a standard OpenSSL installation, the following environment variables can be used to set SSL cert info: + +SSL_CERT_FILE = Set to file with SSL CA certificates in OpenSSL compatible format. The usual file name is /path/to/cacert.pem. + +SSL_CERT_DIR = Path to directory with CA files. Index: tests/all.tcl ================================================================== --- tests/all.tcl +++ tests/all.tcl @@ -7,53 +7,47 @@ # Copyright (c) 1998-2000 by Ajuba Solutions. # All rights reserved. # # RCS: @(#) $Id: all.tcl,v 1.5 2000/08/15 18:45:01 hobbs Exp $ +set path [file normalize [file dirname [file join [pwd] [info script]]]] #set auto_path [linsert $auto_path 0 [file normalize [file join [file dirname [info script]] ..]]] -set auto_path [linsert $auto_path 0 [file normalize [pwd]]] +set auto_path [linsert $auto_path 0 [file dirname $path] [file normalize [pwd]]] if {[lsearch [namespace children] ::tcltest] == -1} { package require tcltest namespace import ::tcltest::* } + +# Get common functions +if {[file exists [file join $path common.tcl]]} { + source [file join $path common.tcl] +} set ::tcltest::testSingleFile false set ::tcltest::testsDirectory [file dir [info script]] # We should ensure that the testsDirectory is absolute. # This was introduced in Tcl 8.3+'s tcltest, so we need a catch. catch {::tcltest::normalizePath ::tcltest::testsDirectory} -puts stdout "Tests running in interp: [info nameofexecutable]" -puts stdout "Tests running in working dir: $::tcltest::testsDirectory" -if {[llength $::tcltest::skip] > 0} { - puts stdout "Skipping tests that match: $::tcltest::skip" -} -if {[llength $::tcltest::match] > 0} { - puts stdout "Only running tests that match: $::tcltest::match" -} - -if {[llength $::tcltest::skipFiles] > 0} { - puts stdout "Skipping test files that match: $::tcltest::skipFiles" -} -if {[llength $::tcltest::matchFiles] > 0} { - puts stdout "Only sourcing test files that match: $::tcltest::matchFiles" -} - -set timeCmd {clock format [clock seconds]} -puts stdout "Tests began at [eval $timeCmd]" - -# source each of the specified tests -foreach file [lsort [::tcltest::getMatchingFiles]] { - set tail [file tail $file] - puts stdout $tail - if {[catch {source $file} msg]} { - puts stdout $msg - } -} - -# cleanup -puts stdout "\nTests ended at [eval $timeCmd]" -::tcltest::cleanupTests 1 -return - +# +# Run all tests in current and any sub directories with an all.tcl file. +# +set exitCode 0 +if {[package vsatisfies [package require tcltest] 2.5-]} { + if {[::tcltest::runAllTests] == 1} { + set exitCode 1 + } + +} else { + # Hook to determine if any of the tests failed. Then we can exit with the + # proper exit code: 0=all passed, 1=one or more failed + proc tcltest::cleanupTestsHook {} { + variable numTests + set exitCode [expr {$numTests(Total) == 0 || $numTests(Failed) > 0}] + } + ::tcltest::runAllTests +} + +# Exit code: 0=all passed, 1=one or more failed +exit $exitCode ADDED tests/badssl.csv Index: tests/badssl.csv ================================================================== --- /dev/null +++ tests/badssl.csv @@ -0,0 +1,78 @@ +# Group,Name,Constraints,Setup,Body,Cleanup,Match,Result,Output,Error Output,Return Codes +command,package require tls,,,,,,,,, +,,,,,,,,,, +command,# Constraints,,,,,,,,, +command,source [file join [file dirname [info script]] common.tcl],,,,,,,,, +,,,,,,,,,, +command,# Helper functions,,,,,,,,, +command,"proc badssl {url} {set port 443;lassign [split $url "":""] url port;if {$port eq """"} {set port 443};set cmd [list tls::socket -autoservername 1 -require 1];if {[info exists ::env(SSL_CERT_FILE)]} {lappend cmd -cafile $::env(SSL_CERT_FILE)};lappend cmd $url $port;set ch [eval $cmd];if {[catch {tls::handshake $ch} err]} {close $ch;return -code error $err} else {close $ch}}",,,,,,,,, +,,,,,,,,,, +command,# BadSSL.com Tests,,,,,,,,, +BadSSL,1000-sans,,,badssl 1000-sans.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,10000-sans,,,badssl 10000-sans.badssl.com,,,handshake failed: excessive message size,,,1 +BadSSL,3des,,,badssl 3des.badssl.com,,,handshake failed: sslv3 alert handshake failure,,,1 +BadSSL,captive-portal,old_api,,badssl captive-portal.badssl.com,,,"handshake failed: certificate verify failed due to ""Hostname mismatch""",,,1 +BadSSL,captive-portal,new_api,,badssl captive-portal.badssl.com,,,"handshake failed: certificate verify failed due to ""hostname mismatch""",,,1 +BadSSL,cbc,,,badssl cbc.badssl.com,,,,,, +BadSSL,client-cert-missing,,,badssl client-cert-missing.badssl.com,,,,,, +BadSSL,client,,,badssl client.badssl.com,,,,,, +BadSSL,dh-composite,old_api,,badssl dh-composite.badssl.com,,,,,, +BadSSL,dh-composite,new_api,,badssl dh-composite.badssl.com,,,handshake failed: dh key too small,,,1 +BadSSL,dh-small-subgroup,,,badssl dh-small-subgroup.badssl.com,,,,,, +BadSSL,dh480,old_api,,badssl dh480.badssl.com,,,handshake failed: dh key too small,,,1 +BadSSL,dh480,new_api,,badssl dh480.badssl.com,,,handshake failed: modulus too small,,,1 +BadSSL,dh512,,,badssl dh512.badssl.com,,,handshake failed: dh key too small,,,1 +BadSSL,dh1024,old_api,,badssl dh1024.badssl.com,,,,,, +BadSSL,dh1024,new_api,,badssl dh1024.badssl.com,,,handshake failed: dh key too small,,,1 +BadSSL,dh2048,,,badssl dh2048.badssl.com,,,,,, +BadSSL,dsdtestprovider,,,badssl dsdtestprovider.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,ecc256,,,badssl ecc256.badssl.com,,,,,, +BadSSL,ecc384,,,badssl ecc384.badssl.com,,,,,, +BadSSL,edellroot,,,badssl edellroot.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,expired,,,badssl expired.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,extended-validation,,,badssl extended-validation.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,hsts,,,badssl hsts.badssl.com,,,,,, +BadSSL,https-everywhere,,,badssl https-everywhere.badssl.com,,,,,, +BadSSL,incomplete-chain,,,badssl incomplete-chain.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,invalid-expected-sct,,,badssl invalid-expected-sct.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,long-extended-subdomain-name-containing-many-letters-and-dashes,,,badssl long-extended-subdomain-name-containing-many-letters-and-dashes.badssl.com,,,,,, +BadSSL,longextendedsubdomainnamewithoutdashesinordertotestwordwrapping,,,badssl longextendedsubdomainnamewithoutdashesinordertotestwordwrapping.badssl.com,,,,,, +BadSSL,mitm-software,,,badssl mitm-software.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,no-common-name,,,badssl no-common-name.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,no-sct,,,badssl no-sct.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,no-subject,,,badssl no-subject.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,null,,,badssl null.badssl.com,,,handshake failed: sslv3 alert handshake failure,,,1 +BadSSL,pinning-test,,,badssl pinning-test.badssl.com,,,,,, +BadSSL,preact-cli,,,badssl preact-cli.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,preloaded-hsts,,,badssl preloaded-hsts.badssl.com,,,,,, +BadSSL,rc4-md5,,,badssl rc4-md5.badssl.com,,,handshake failed: sslv3 alert handshake failure,,,1 +BadSSL,rc4,,,badssl rc4.badssl.com,,,handshake failed: sslv3 alert handshake failure,,,1 +BadSSL,revoked,,,badssl revoked.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,rsa2048,,,badssl rsa2048.badssl.com,,,,,, +BadSSL,rsa4096,,,badssl rsa4096.badssl.com,,,,,, +BadSSL,rsa8192,,,badssl rsa8192.badssl.com,,,,,, +BadSSL,self-signed,old_api,,badssl self-signed.badssl.com,,,"handshake failed: certificate verify failed due to ""self signed certificate""",,,1 +BadSSL,self-signed,new_api,,badssl self-signed.badssl.com,,,"handshake failed: certificate verify failed due to ""self-signed certificate""",,,1 +BadSSL,sha1-2016,,,badssl sha1-2016.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,sha1-2017,old_api,,badssl sha1-2017.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,sha1-2017,new_api,,badssl sha1-2017.badssl.com,,,"handshake failed: certificate verify failed due to ""CA signature digest algorithm too weak""",,,1 +BadSSL,sha1-intermediate,,,badssl sha1-intermediate.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,sha256,,,badssl sha256.badssl.com,,,,,, +BadSSL,sha384,,,badssl sha384.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,sha512,,,badssl sha512.badssl.com,,,"handshake failed: certificate verify failed due to ""certificate has expired""",,,1 +BadSSL,static-rsa,,,badssl static-rsa.badssl.com,,,,,, +BadSSL,subdomain.preloaded-hsts,old_api,,badssl subdomain.preloaded-hsts.badssl.com,,,"handshake failed: certificate verify failed due to ""Hostname mismatch""",,,1 +BadSSL,subdomain.preloaded-hsts,new_api,,badssl subdomain.preloaded-hsts.badssl.com,,,"handshake failed: certificate verify failed due to ""hostname mismatch""",,,1 +BadSSL,superfish,,,badssl superfish.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,tls-v1-0:1010,tls1 old_api,,badssl tls-v1-0.badssl.com:1010,,,,,, +BadSSL,tls-v1-0:1010,tls1 new_api,,badssl tls-v1-0.badssl.com:1010,,,handshake failed: unsupported protocol,,,1 +BadSSL,tls-v1-1:1011,tls1.1 old_api,,badssl tls-v1-1.badssl.com:1011,,,,,, +BadSSL,tls-v1-1:1011,tls1.1 new_api,,badssl tls-v1-1.badssl.com:1011,,,handshake failed: unsupported protocol,,,1 +BadSSL,tls-v1-2:1012,tls1.2,,badssl tls-v1-2.badssl.com:1012,,,,,, +BadSSL,untrusted-root,old_api,,badssl untrusted-root.badssl.com,,,"handshake failed: certificate verify failed due to ""self signed certificate in certificate chain""",,,1 +BadSSL,untrusted-root,new_api,,badssl untrusted-root.badssl.com,,,"handshake failed: certificate verify failed due to ""self-signed certificate in certificate chain""",,,1 +BadSSL,upgrade,,,badssl upgrade.badssl.com,,,,,, +BadSSL,webpack-dev-server,,,badssl webpack-dev-server.badssl.com,,,"handshake failed: certificate verify failed due to ""unable to get local issuer certificate""",,,1 +BadSSL,wrong.host,old_api,,badssl wrong.host.badssl.com,,,"handshake failed: certificate verify failed due to ""Hostname mismatch""",,,1 +BadSSL,wrong.host,new_api,,badssl wrong.host.badssl.com,,,"handshake failed: certificate verify failed due to ""hostname mismatch""",,,1 +BadSSL,mozilla-modern,,,badssl mozilla-modern.badssl.com,,,,,, ADDED tests/badssl.test Index: tests/badssl.test ================================================================== --- /dev/null +++ tests/badssl.test @@ -0,0 +1,296 @@ +# Auto generated test cases for badssl.csv + +# Load Tcl Test package +if {[lsearch [namespace children] ::tcltest] == -1} { + package require tcltest + namespace import ::tcltest::* +} + +set auto_path [concat [list [file dirname [file dirname [info script]]]] $auto_path] + +package require tls + +# Constraints +source [file join [file dirname [info script]] common.tcl] + +# Helper functions +proc badssl {url} {set port 443;lassign [split $url ":"] url port;if {$port eq ""} {set port 443};set cmd [list tls::socket -autoservername 1 -require 1];if {[info exists ::env(SSL_CERT_FILE)]} {lappend cmd -cafile $::env(SSL_CERT_FILE)};lappend cmd $url $port;set ch [eval $cmd];if {[catch {tls::handshake $ch} err]} {close $ch;return -code error $err} else {close $ch}} + +# BadSSL.com Tests + + +test BadSSL-1.1 {1000-sans} -body { + badssl 1000-sans.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.2 {10000-sans} -body { + badssl 10000-sans.badssl.com + } -result {handshake failed: excessive message size} -returnCodes {1} + +test BadSSL-1.3 {3des} -body { + badssl 3des.badssl.com + } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} + +test BadSSL-1.4 {captive-portal} -constraints {old_api} -body { + badssl captive-portal.badssl.com + } -result {handshake failed: certificate verify failed due to "Hostname mismatch"} -returnCodes {1} + +test BadSSL-1.5 {captive-portal} -constraints {new_api} -body { + badssl captive-portal.badssl.com + } -result {handshake failed: certificate verify failed due to "hostname mismatch"} -returnCodes {1} + +test BadSSL-1.6 {cbc} -body { + badssl cbc.badssl.com + } + +test BadSSL-1.7 {client-cert-missing} -body { + badssl client-cert-missing.badssl.com + } + +test BadSSL-1.8 {client} -body { + badssl client.badssl.com + } + +test BadSSL-1.9 {dh-composite} -constraints {old_api} -body { + badssl dh-composite.badssl.com + } + +test BadSSL-1.10 {dh-composite} -constraints {new_api} -body { + badssl dh-composite.badssl.com + } -result {handshake failed: dh key too small} -returnCodes {1} + +test BadSSL-1.11 {dh-small-subgroup} -body { + badssl dh-small-subgroup.badssl.com + } + +test BadSSL-1.12 {dh480} -constraints {old_api} -body { + badssl dh480.badssl.com + } -result {handshake failed: dh key too small} -returnCodes {1} + +test BadSSL-1.13 {dh480} -constraints {new_api} -body { + badssl dh480.badssl.com + } -result {handshake failed: modulus too small} -returnCodes {1} + +test BadSSL-1.14 {dh512} -body { + badssl dh512.badssl.com + } -result {handshake failed: dh key too small} -returnCodes {1} + +test BadSSL-1.15 {dh1024} -constraints {old_api} -body { + badssl dh1024.badssl.com + } + +test BadSSL-1.16 {dh1024} -constraints {new_api} -body { + badssl dh1024.badssl.com + } -result {handshake failed: dh key too small} -returnCodes {1} + +test BadSSL-1.17 {dh2048} -body { + badssl dh2048.badssl.com + } + +test BadSSL-1.18 {dsdtestprovider} -body { + badssl dsdtestprovider.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.19 {ecc256} -body { + badssl ecc256.badssl.com + } + +test BadSSL-1.20 {ecc384} -body { + badssl ecc384.badssl.com + } + +test BadSSL-1.21 {edellroot} -body { + badssl edellroot.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.22 {expired} -body { + badssl expired.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.23 {extended-validation} -body { + badssl extended-validation.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.24 {hsts} -body { + badssl hsts.badssl.com + } + +test BadSSL-1.25 {https-everywhere} -body { + badssl https-everywhere.badssl.com + } + +test BadSSL-1.26 {incomplete-chain} -body { + badssl incomplete-chain.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.27 {invalid-expected-sct} -body { + badssl invalid-expected-sct.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.28 {long-extended-subdomain-name-containing-many-letters-and-dashes} -body { + badssl long-extended-subdomain-name-containing-many-letters-and-dashes.badssl.com + } + +test BadSSL-1.29 {longextendedsubdomainnamewithoutdashesinordertotestwordwrapping} -body { + badssl longextendedsubdomainnamewithoutdashesinordertotestwordwrapping.badssl.com + } + +test BadSSL-1.30 {mitm-software} -body { + badssl mitm-software.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.31 {no-common-name} -body { + badssl no-common-name.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.32 {no-sct} -body { + badssl no-sct.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.33 {no-subject} -body { + badssl no-subject.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.34 {null} -body { + badssl null.badssl.com + } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} + +test BadSSL-1.35 {pinning-test} -body { + badssl pinning-test.badssl.com + } + +test BadSSL-1.36 {preact-cli} -body { + badssl preact-cli.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.37 {preloaded-hsts} -body { + badssl preloaded-hsts.badssl.com + } + +test BadSSL-1.38 {rc4-md5} -body { + badssl rc4-md5.badssl.com + } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} + +test BadSSL-1.39 {rc4} -body { + badssl rc4.badssl.com + } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} + +test BadSSL-1.40 {revoked} -body { + badssl revoked.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.41 {rsa2048} -body { + badssl rsa2048.badssl.com + } + +test BadSSL-1.42 {rsa4096} -body { + badssl rsa4096.badssl.com + } + +test BadSSL-1.43 {rsa8192} -body { + badssl rsa8192.badssl.com + } + +test BadSSL-1.44 {self-signed} -constraints {old_api} -body { + badssl self-signed.badssl.com + } -result {handshake failed: certificate verify failed due to "self signed certificate"} -returnCodes {1} + +test BadSSL-1.45 {self-signed} -constraints {new_api} -body { + badssl self-signed.badssl.com + } -result {handshake failed: certificate verify failed due to "self-signed certificate"} -returnCodes {1} + +test BadSSL-1.46 {sha1-2016} -body { + badssl sha1-2016.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.47 {sha1-2017} -constraints {old_api} -body { + badssl sha1-2017.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.48 {sha1-2017} -constraints {new_api} -body { + badssl sha1-2017.badssl.com + } -result {handshake failed: certificate verify failed due to "CA signature digest algorithm too weak"} -returnCodes {1} + +test BadSSL-1.49 {sha1-intermediate} -body { + badssl sha1-intermediate.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.50 {sha256} -body { + badssl sha256.badssl.com + } + +test BadSSL-1.51 {sha384} -body { + badssl sha384.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.52 {sha512} -body { + badssl sha512.badssl.com + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} + +test BadSSL-1.53 {static-rsa} -body { + badssl static-rsa.badssl.com + } + +test BadSSL-1.54 {subdomain.preloaded-hsts} -constraints {old_api} -body { + badssl subdomain.preloaded-hsts.badssl.com + } -result {handshake failed: certificate verify failed due to "Hostname mismatch"} -returnCodes {1} + +test BadSSL-1.55 {subdomain.preloaded-hsts} -constraints {new_api} -body { + badssl subdomain.preloaded-hsts.badssl.com + } -result {handshake failed: certificate verify failed due to "hostname mismatch"} -returnCodes {1} + +test BadSSL-1.56 {superfish} -body { + badssl superfish.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.57 {tls-v1-0:1010} -constraints {tls1 old_api} -body { + badssl tls-v1-0.badssl.com:1010 + } + +test BadSSL-1.58 {tls-v1-0:1010} -constraints {tls1 new_api} -body { + badssl tls-v1-0.badssl.com:1010 + } -result {handshake failed: unsupported protocol} -returnCodes {1} + +test BadSSL-1.59 {tls-v1-1:1011} -constraints {tls1.1 old_api} -body { + badssl tls-v1-1.badssl.com:1011 + } + +test BadSSL-1.60 {tls-v1-1:1011} -constraints {tls1.1 new_api} -body { + badssl tls-v1-1.badssl.com:1011 + } -result {handshake failed: unsupported protocol} -returnCodes {1} + +test BadSSL-1.61 {tls-v1-2:1012} -constraints {tls1.2} -body { + badssl tls-v1-2.badssl.com:1012 + } + +test BadSSL-1.62 {untrusted-root} -constraints {old_api} -body { + badssl untrusted-root.badssl.com + } -result {handshake failed: certificate verify failed due to "self signed certificate in certificate chain"} -returnCodes {1} + +test BadSSL-1.63 {untrusted-root} -constraints {new_api} -body { + badssl untrusted-root.badssl.com + } -result {handshake failed: certificate verify failed due to "self-signed certificate in certificate chain"} -returnCodes {1} + +test BadSSL-1.64 {upgrade} -body { + badssl upgrade.badssl.com + } + +test BadSSL-1.65 {webpack-dev-server} -body { + badssl webpack-dev-server.badssl.com + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} + +test BadSSL-1.66 {wrong.host} -constraints {old_api} -body { + badssl wrong.host.badssl.com + } -result {handshake failed: certificate verify failed due to "Hostname mismatch"} -returnCodes {1} + +test BadSSL-1.67 {wrong.host} -constraints {new_api} -body { + badssl wrong.host.badssl.com + } -result {handshake failed: certificate verify failed due to "hostname mismatch"} -returnCodes {1} + +test BadSSL-1.68 {mozilla-modern} -body { + badssl mozilla-modern.badssl.com + } + +# Cleanup +::tcltest::cleanupTests +return Index: tests/certs/ca.pem ================================================================== --- tests/certs/ca.pem +++ tests/certs/ca.pem @@ -1,18 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIC2jCCAkOgAwIBAgIBADANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJDQTEZ -MBcGA1UECBMQQnJpdGlzaCBDb2x1bWJpYTESMBAGA1UEBxMJVmFuY291dmVyMRow -GAYDVQQKExFTYW1wbGUgQ2VydHMgSW50bDAeFw0wMTA2MjEyMDI2MDRaFw0wMTA3 -MjEyMDI2MDRaMFgxCzAJBgNVBAYTAkNBMRkwFwYDVQQIExBCcml0aXNoIENvbHVt -YmlhMRIwEAYDVQQHEwlWYW5jb3V2ZXIxGjAYBgNVBAoTEVNhbXBsZSBDZXJ0cyBJ -bnRsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJeHe1yfuw7YCx6nZ4hcyl -qe1JgOXSMqAzHwfHf/EdGtQUhsfsmgx9cZCKgtuZaoRKidl60MFeW2zq12ORuPUB -w90mQh46KDPRNWm1jViI/xmKUY+so6F5P/c6aA0QYqcpDhM7GgMvaAbEuY70gQ0l -uhxMv75mKMWC4RuzFyVVjwIDAQABo4GzMIGwMB0GA1UdDgQWBBTwwtcIvZ/wpImV -VC/e3C/I9qXWVTCBgAYDVR0jBHkwd4AU8MLXCL2f8KSJlVQv3twvyPal1lWhXKRa -MFgxCzAJBgNVBAYTAkNBMRkwFwYDVQQIExBCcml0aXNoIENvbHVtYmlhMRIwEAYD -VQQHEwlWYW5jb3V2ZXIxGjAYBgNVBAoTEVNhbXBsZSBDZXJ0cyBJbnRsggEAMAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEANprDWDEI9/UUkIL4kxvK8Woy -akWYabFR3s2RnxwCMDi0d7eKh+8k+NHLjD1FnWt9VNmub3sd8+PdTMk41PlLfroG -lCAd31HnYqoi498ivgpczwFj3BQSssmhld+aCFyE83KVIeMuP55fcp44vxQuEmcn -EWnH66cMUxI1D3jcQWE= +MIIDkTCCAnmgAwIBAgIUPg6RCIdGBkdlV10XlcfJxHJINeowDQYJKoZIhvcNAQEL +BQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29sdW1iaWExEjAQ +BgNVBAcMCVZhbmNvdXZlcjEaMBgGA1UECgwRU2FtcGxlIENlcnRzIEludGwwHhcN +MTkwNzE4MTEyNjM0WhcNMTkwODE3MTEyNjM0WjBYMQswCQYDVQQGEwJDQTEZMBcG +A1UECAwQQnJpdGlzaCBDb2x1bWJpYTESMBAGA1UEBwwJVmFuY291dmVyMRowGAYD +VQQKDBFTYW1wbGUgQ2VydHMgSW50bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALbsv1fBwo9o/xGkbZgnYTycPMcvttk0w4LPamni++zl6XkV+mGnGUEl +5Zpn0Ynchsi/dhwhIEY+il9FWeWJ5Dgl4i/fHUTz3L+NZYafGTwCuSFmPCbGd/ho +54tyZoax5CLQuBAnHGaYlIy9uJRL7eELqZICzxd3mD897DuQMX3fLLOAf0S94VDv +KytPilY6I9hvwBoy8WH4tQfj3xTIpE/+VB6A6hnG2jTRzwKGlCMpXV8Fj4ZMrGTp +XTufon4wO/G1YJ8WGDpnJth6y9N0B4yni4xv7MJO+6R69CvpK8udCTfd8exjceAt +iqjY9bbErVh4FPTUYK2dmMhtuMQFYFECAwEAAaNTMFEwHQYDVR0OBBYEFB88aJVh +lJEffxFp17pVhAJk/FFRMB8GA1UdIwQYMBaAFB88aJVhlJEffxFp17pVhAJk/FFR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHbA2u+peV8gYB3c +IDSN1+p3q1rJuW0sLeo1AEzOuu/HfrEDtIUBYI1OAY0FfJHnfDHPSy/FGWWydDzZ +POQP8k/0HHT5CvzrDGUWjPQwkFyAvm/7VrJNfUg5RALWUhZIC3b5/gbVUEh/exzY +eSElOjy3M32t1HQuOrGSHBsoal5D4xyeTdf55hMkxZJZfbhUt3/5ZmcPw1QryX/4 +cqp6QJTDZhVOLHPhVNGIckCy7+DTBo7BDqDt2cEUE7NQ6w2BQTSY5WHCFHcHgJJ6 +5jKQzsrIXe3K0cl23KxW+JC0vYBkckIalhQQ8hQLIQU6gF0wwFjE+H7yNixtH3bt +BXkdO6c= -----END CERTIFICATE----- Index: tests/certs/client.key ================================================================== --- tests/certs/client.key +++ tests/certs/client.key @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDByy+QiN7Gde+Pf6Wvjk4OZlXfbV68mzmLh/xrXIdGQL5KqRhi -ydUZSUU87TZ/poZAFGA8kds0pmD1TWy4lGiJjoU5pxeIvl8d08Sqbh6Srxv1CKJm -J7RIp4RvEpviOSaDUC0wkLMvAAaAu2ZpNEncsotV4eSaE/WhvCHamBjpSwIDAQAB -AoGAP0q48h+Bgpep8dfiqP91BsbtbNcvhbG8jZGQIxBJLeyfOYsYZ8s7SdLgRhHD -JtWgKvV8qMuKKBvetr7erznpGdHcDDw1wutL2PagET756BjAtxcZ0lEx129eXThH -10+09QEbSlO9XRd1OvAdLCb80H97+jZXMVJ6eb/uMuVzUMECQQDzikOjJLK678fa -haesVYBqmsFAihGIUK+7Ki1F8wS6/oKLHWKDdFYoI/3Zve0qdFGFdvZicFqLAjKl -QOXxBGrpAkEAy7Vf1nmp8FAj2p1/0383EuIhjmMjQw2SHYMbTaCwbnYGJrPoeMwE -dwaaWwfgmXFeoc6lzBRUeDVz2EE6EyzqEwJBAO9XR4eSrlAHDFsWlSVJVg3ujtO1 -nOthmIKRPbML1O9M5tB/DWzxLSb/0B9ohyb8740Bz7wIfQM2Ir3DXPeThtkCQQDH -zSYrHznnUzNXgZOWxfgmtVVkayhy5CSkfauSAEIMlgaCf4NMuA7JD9jl4FwTJHdF -DYLhIC+ZmBP/0Do+BJexAkEAjrF928xMKcsrVmr7zlEhl+4B75kDkXm8TDV42PQI -WzmYuHZHwWZApU42VVlWEToIog2s0RVBOyHdiQsNwrL6Rw== +MIIEpAIBAAKCAQEAxzY90F1CGLDXKlOiJsvZ9kZks/TBVTx9WDJBrZDG328/V03O +4R7+kBRwF5rayitOHf0EWB6wMknpWMpz3JStJ9+Dl/K9ix9VUHmySySIh8eVxEO2 +/wjsou2QyBnzgeLFn2Y+m1Sb3NYeEbjayMbqTnZ1ySV6xVLFQLKGTUhH1pE78L8b +bt0HAQ8ZS3mb9vBQ0seJrx0dOM+SP6DXDtgNolpCLKbnJkk/hknb0+5a2ctTf/JN +gyl4MyuV7ZHv56ofTwPx6uldQxfSxxyS0SAkC1gKa498b8kbB6fB7SXL9JJ5Jz27 +vWnsROFxaquiVuVb0m014G5rGC23YozY8ELIMwIDAQABAoIBAEQ7WNq4Rn5Me7X8 +pUpiggovPCjDCUEXkdsWg5ZeQy+eW/ScKuLCifBxf58mJBAg2wW8drNz92eSF0GZ +PivsJI7GqLzvoGo5VjBVLnM8VSMkgjCR/OjHdr2rXu6arOPs90FMdN8hEK2IDQ6G +4TDpqLEtM9SsaKuTWQp62zM3MZvU0eUFP9oiZ6cp/D7CpSq76cQFpCQekR8a4f9Q +eGkVzK/cwCauiB055CUB8Jzc5ZmmQal7mjVJ955d6kWWH4J1HP6ktNSiPLxaKA4W +IvPwtTKOpy0XblZ7hVTTiVhuTZnmF+xqDMj0RsDPO5IAH7/2HZ03ku2yOEIMmg4i +5eK5yFECgYEA7mbEpQHRJxQiFkx0hQKISMvy0afYN41s7qmtoY4nzFMSOI54YgcU +w91ZsR+Ac4yLNCCJYxIyuRm4j15YvH3NK4etINJ+9pWPzbTRuKNOHgIySA4QyYhG +azkO+6pwiy6IOhyPNBek//EaGgnfEFSGcSeEUUcQy/QwXW1QsUB3SnkCgYEA1erh +yM8IOkcp2c41klZTYhzZQYxdWJBGw9AhlVo082K4qIOLSaZCO5nn5xya2veUl9qz +0Ee+14wSP/zOX1lXkd9iLoqHmqo2u/nw+MnFdyqwtH0aBnG/bhP+2v6dar2JH/sp +DDQh9y753vcat8H1eWSWKXrkDHZg3fsnjtiR/QsCgYEA09mjGXa5494yRFqAmMod +TYNfLgvXSdZ1XMiPsSbgGuQfJv8D3yinvT3wPEPgI85azWG0dMNxK6e9qDmQ5T9t +mSciJC6qAHn7pjLuwwLroiMVh45oQI7G9PVpaR6WkDgzemByqTnxuDcKmOT4wkw6 +hEc3f/qE5JkEeaFwuXKuDxECgYA9O+c13E22oPhR6L1dDmkABNIL+WofF+2pz8JN +Rm0x3miNlFoi0vzSotPHTGDnnUdj0K74SBFREj+HIY7RrHlswE0SlYULuP3CAWIB +VYC5A/dhMw5oGdd6Yy7o8UmObIL8LKErZSDz6PaN5J45S8RA45I4fX0aNCi2YlaI +hWYE0QKBgQC69xGEbQUXLnR5XQ6i5RVHwIJUts9mk7IJjhEdktm73Ke2aUdovwQ+ +MZqZyJOjiuh+pllgLjitnKYCLaF6cDmlTXBg19rXYA65D7TZVj3+zu3+WaBCc4zq +J7++4DvtVqyzGRAiCez+lMRm6tB7QvdYHCDAUxVUKdS7EqV1grkjxw== -----END RSA PRIVATE KEY----- Index: tests/certs/client.pem ================================================================== --- tests/certs/client.pem +++ tests/certs/client.pem @@ -1,14 +1,19 @@ -----BEGIN CERTIFICATE----- -MIICHzCCAYgCAQEwDQYJKoZIhvcNAQEEBQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNV -BAgTEEJyaXRpc2ggQ29sdW1iaWExEjAQBgNVBAcTCVZhbmNvdXZlcjEaMBgGA1UE -ChMRU2FtcGxlIENlcnRzIEludGwwHhcNMDEwNjIxMjAyOTU4WhcNMDEwNzIxMjAy -OTU4WjBYMQswCQYDVQQGEwJDQTEZMBcGA1UECBMQQnJpdGlzaCBDb2x1bWJpYTES -MBAGA1UEBxMJVmFuY291dmVyMRowGAYDVQQKExFTYW1wbGUgQ2VydHMgSW50bDCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwcsvkIjexnXvj3+lr45ODmZV321e -vJs5i4f8a1yHRkC+SqkYYsnVGUlFPO02f6aGQBRgPJHbNKZg9U1suJRoiY6FOacX -iL5fHdPEqm4ekq8b9QiiZie0SKeEbxKb4jkmg1AtMJCzLwAGgLtmaTRJ3LKLVeHk -mhP1obwh2pgY6UsCAwEAATANBgkqhkiG9w0BAQQFAAOBgQC9llXASadBxwkaEIZ7 -bmCYMWIB6+jjxa0YCY2jYgqCslny/bkLgIuxIcxf83ouFfXU52r/mq04jfuRfyRt -zCT8C+Z9nhKHdHA0cVYJ+tNuZfssQ+cFHUfjDOsCEFTJ1OoooafnIHpPXub1FcYr -SCLdcK0BwPbCcJUZrIHwu3Nu7g== +MIIDJDCCAgwCAQQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNV +BAgMEEJyaXRpc2ggQ29sdW1iaWExEjAQBgNVBAcMCVZhbmNvdXZlcjEaMBgGA1UE +CgwRU2FtcGxlIENlcnRzIEludGwwHhcNMTkwNzE4MTEzMzQwWhcNMTkwODE3MTEz +MzQwWjBYMQswCQYDVQQGEwJDQTEZMBcGA1UECAwQQnJpdGlzaCBDb2x1bWJpYTES +MBAGA1UEBwwJVmFuY291dmVyMRowGAYDVQQKDBFTYW1wbGUgQ2VydHMgSW50bDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMc2PdBdQhiw1ypToibL2fZG +ZLP0wVU8fVgyQa2Qxt9vP1dNzuEe/pAUcBea2sorTh39BFgesDJJ6VjKc9yUrSff +g5fyvYsfVVB5skskiIfHlcRDtv8I7KLtkMgZ84HixZ9mPptUm9zWHhG42sjG6k52 +dcklesVSxUCyhk1IR9aRO/C/G27dBwEPGUt5m/bwUNLHia8dHTjPkj+g1w7YDaJa +Qiym5yZJP4ZJ29PuWtnLU3/yTYMpeDMrle2R7+eqH08D8erpXUMX0sccktEgJAtY +CmuPfG/JGwenwe0ly/SSeSc9u71p7EThcWqrolblW9JtNeBuaxgtt2KM2PBCyDMC +AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAgEps7DSYpNrN7VXdCw+AsOLikSyWZbOg +kgeiYJWzemghHZJ62dj60aOmlxiYvPHONkds/d39wOuJkURcSBZL56VTqXIOuTXO +pdBTIxJK9qroZphTt+5up4Z2YaBKb5mBdE/sldwJuxkw5pylLWbBtSaw0i9K40Q7 +7xY/+IDMZB6Duc+lDIWvaVk84U5wHxdzUJcgdBRcUCXlmDP672j3KsILSjx5737g +yKil2uagRp/QaZgSv3vkwcwX/RiqPHoIBBiLscaSxPIwiOCJJO1CP3rlPfu/1rlH +765wwtoimMIV503aUe0cMOO7z71zUjsDQkNgjTJtqQFC78ZZsayLFg== -----END CERTIFICATE----- Index: tests/certs/client.req ================================================================== --- tests/certs/client.req +++ tests/certs/client.req @@ -1,11 +1,17 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIBmDCCAQECAQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNVBAgTEEJyaXRpc2ggQ29s -dW1iaWExEjAQBgNVBAcTCVZhbmNvdXZlcjEaMBgGA1UEChMRU2FtcGxlIENlcnRz -IEludGwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMHLL5CI3sZ1749/pa+O -Tg5mVd9tXrybOYuH/Gtch0ZAvkqpGGLJ1RlJRTztNn+mhkAUYDyR2zSmYPVNbLiU -aImOhTmnF4i+Xx3TxKpuHpKvG/UIomYntEinhG8Sm+I5JoNQLTCQsy8ABoC7Zmk0 -Sdyyi1Xh5JoT9aG8IdqYGOlLAgMBAAGgADANBgkqhkiG9w0BAQQFAAOBgQB8xq+d -On5JqJBZcc9rW70jmSU7AlSZ48UQlmNmlUSj4YznWUCbDawEfHWv0Xpfho+bio+L -hFuzt0WsotTW1sboFpG3csHyCpGmIxw5Lacv2x5+dDx0jRbyI426+CUn+ZPv5pv8 -iiVrlyiX2P3jifQjhv39Kgbs5cOr/Ic8KKz5rg== +MIICnTCCAYUCAQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29s +dW1iaWExEjAQBgNVBAcMCVZhbmNvdXZlcjEaMBgGA1UECgwRU2FtcGxlIENlcnRz +IEludGwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHNj3QXUIYsNcq +U6Imy9n2RmSz9MFVPH1YMkGtkMbfbz9XTc7hHv6QFHAXmtrKK04d/QRYHrAySelY +ynPclK0n34OX8r2LH1VQebJLJIiHx5XEQ7b/COyi7ZDIGfOB4sWfZj6bVJvc1h4R +uNrIxupOdnXJJXrFUsVAsoZNSEfWkTvwvxtu3QcBDxlLeZv28FDSx4mvHR04z5I/ +oNcO2A2iWkIspucmST+GSdvT7lrZy1N/8k2DKXgzK5Xtke/nqh9PA/Hq6V1DF9LH +HJLRICQLWAprj3xvyRsHp8HtJcv0knknPbu9aexE4XFqq6JW5VvSbTXgbmsYLbdi +jNjwQsgzAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAXColfK+WzYiOxHzNnObF +7dQwvSd8d97Q0vZLjV0H5ZTQVSwswb9WKWrXnX2VPTMlcxc3K9UJaHtiJZQX1Xhr +N13z6D41ZcSGOk16NUkRy/zNtWn4RNiEMyLs1wiQrsPbgAn0KomwE+3FeOMhWKfi +KFWhV8E8RNIhUM+Wejyrrw2f6Cv13RH6xiQ6ZEvcI8tq3RlM5GfaC0nn3pp/H+Ag +QmCpmr+OUGpz1XtGBJ0GiEIntl4XnRSkzmB5dwAkUF5XdiRnA285i04WSbxoheQo +PPav26T16pNlyjlkm+Rub1K79SV5Rk0EJLcopFDc3csjnlQQokpVm+oBw8oCCY4W +xQ== -----END CERTIFICATE REQUEST----- Index: tests/certs/file.srl ================================================================== --- tests/certs/file.srl +++ tests/certs/file.srl @@ -1,1 +1,1 @@ -02 +04 Index: tests/certs/privkey.pem ================================================================== --- tests/certs/privkey.pem +++ tests/certs/privkey.pem @@ -1,18 +1,30 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,E5670F088D470CF8 - -j53yMhP9QC8ZElMlyTENZ9rI6mq9hjQepTGBhku8W0JuGIDSQTbDieGNJ7myTLEo -AckDGFndIPMJFxz3GU2OYZ40sZE7CL6lkc5JsgSvt2QEp5qK30l9Ij6NnXN/BfpQ -ETliDPzDNWD0ILM43C2J/sNwgwu2SgAMj7BIn2adNuT5AN1nNXdxUg+tbGrEeH39 -eiHKTBRS+40t6KMxW1ftl85zl6WRSRM+3/URdNKUbVq0DQmpFpXT1XcKGxv4GVao -X4jyj6pE5L610cIiT3vy0qK3B3UKsQNOE8Z7aTV9eKvGk7F4LVSQpFz+DDgv/nLb -f2CLIR75MAv7FhcD/Ko+RzxfExPJB0BBsYZGarZcyd1R3rVl/rQAmd+xnZZfM5kV -iRtl7ux8NldaFkZ7XU71ZkLIiivHPDEY6gKWXe3ANsXzVxSO3Zh9okT1P7jyMaNt -Ucz7xD0T7+hnmIV4EU10h849o99F37eN3Ygjjy2xZmMsCfs/Qaem1mlJF0d87472 -7pZcOd+PgBpV2W2O9NTerd6+TPhyqGhgtucrQLID7B+eheLXaexAjgBYwHv9LbOo -uCYPS9s4DBJgvoPhz+IZ/PEZVpY/w5QJ9DsBe0xOv+KWWt9KdcA0SWRYtJUznNSS -YX3eVKZD0C3d5hgr0vSDUe/p6nsgvubHH/v/9EbruXql6PCVu0akO34n+91374pi -85G3EWEuzUwxmKDCr228W5NB2bqFet9CgtHycnQ8cjM61AYpLZx4iTCxH8s6m+lY -WRr1sFm38il8oTODZTQ6o/w91RELhyMd9MTJUZNEqqsgN4y0/r7Dww== ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIBWemhNjEyFQCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECPdy2pJ53hrdBIIEyDerixPd/2vy +eVgr1L6PQSzoyTUx7zrddw7w3Wg8N/TIjFqKGw98edDDPcNlKXbaalpnjL+aMTPa +fqirXYffJKrMkUhbldKiJca4q25Y+if+K7+TExdltGxEF+OvadcQzjKEFFUZsMkq +d89EOGecjICvuQblVX7YlSjZbaIHcK/l0lTQ2vWwi8ZQjLQhjoeO5bXl8b1m2s7H +uWDi+M0jHPf5FN4Exmi0296L5bdUjESyGNIDgJEPDtb+U+k2fbryE0uusGCHXpWZ +Y9UnKE9zDm0aUbD1L2RiztORW5GCRD+2QGQSNHJnWfIVMvOQLC6xSDFS80UfZ29i +eoiPwmObliBM6TsG98qBeye4Bn9+vEJeOJwCEJJgRZy57oK3M3NwD681FgRLiIqx +PDOkwg6yUxKjpn8JfAG3fVkaQ6mt2rAIzR9ClbK3Tk0i6b0B7iWW6VF6ihe5Eqlc ++nX37hXtw6RH6iCqnySoo64PoTtxvG5y9s3bxp1sVmRCJv85bUIp7xLbh/VoyqXf +aHUYHy48M7lHdarl6tgDAMizJor7m9ZeZqAdOnkUMePehkJ9svIUaudRiO9FzVGP +3aIGTXw4KfaLwFsT5O0DqsjFnhmrt0c8NOQyCbcEZZS2D7o2g548oEd/k7PH8u2M +pPnQOI/S2Vb/K4EeUMDHsT6sV/6MFASwKaT4rXBXeyg7ryvqbBTzqAA50kAsUeG7 +/euNHuenZVTIfSYEaZzcBJ9M38ouhbvtdzVIqyJR3DzPXc/olPMudlg2l1ES7CUr +muGTs1TrFT/Ucu07rPlYYFPUuk6nUC5llhTN3n9GfmHcnkeiE8PPfbX3y3I7z3Xh +Fj4atR2VB26kxu77oKy8MM4ANd8uKvHAWEornuIZ/H3BbLOZsTnIR0+HhrXq9oT+ +FxxiUFzrKIZKt9dZHfLo27YjvnlP1uQKfaljvTt4OTTbAfV5Q8mWj8OBAyQc0WRv +EHgHxcItGiwnGhoParqQFO6Cbtw+1G71t1b5Jd5tbEpbHMlugGP5jPWlM/KfJi8e +mDi+30jTft5+1TEOmQK4hlpINfGOM1h07FPI8bjm3k44FW7FFJPZ8XlCT/JTJ87Y +Fo92Z7uLW1O6oasj5ooiFYN0F9VyTYRhEcjGzt0aQso/9shNHHFn6T9ECzTsgEJq +lAYpGESd6nlOkhDAwO2DWbrPYuJTnlj5j2tR6mV1u8mSEbMfB/p3AG4vmdrz0u4c +hm7+H/pC0bVR+vwZslijZe77jMrWE5VQgDgcdCUS5V8hyRt0nt6AXr48jX4Rnpjh +90Tb/wJH7AhIzG+Lr7jHoPNe4JWEN8zaBLT5N1l2YDO0+6lSVLMN1lk3q9M3ff4Q +ExAxsEb7ueox2lPS40XdEBn2Umv4u/66nwiST2oLPp8+esYlu8xOa0ODHeuI+5ya +E5byUlt+qYTo1iVCtYswGD/AIQCFjaDycBST2RSPfIPpY/cCPlPKniYuX9JokRfS +3GgEOCJ6lynBSByn7roDDOc/SbE4dUQ6tXvwVx16KwaXOPOXhDJz+LtUTb9X4ShC +54jI4In+cKe+gBOBXpeGiS6/bPjwCGdNXJ1YN/hyBJy+3L45JKZR5e4nzly7Ebpk +/3WGW9dRZb210rEH5OllKA== +-----END ENCRYPTED PRIVATE KEY----- Index: tests/certs/server.key ================================================================== --- tests/certs/server.key +++ tests/certs/server.key @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDCE6cHPOkPnOSpobuRDKTLcvjdmh1vAYmwOvXLcBkpN+PkN443 -2KURytg0rw4w7+HDS+KV13pAF5D5mSl/OOsfwQzi/dQKSVF0zlbz5L7rcBqIt2cG -Xz7gsX8VRMycXH0XC3QAAZUW32zYeo0G28uCttAh6wt8YCKu99+TNhRIWQIDAQAB -AoGAaMHQ48BGEO5gIwwwwW+wuDycBom8n4GV/7EjoaclfbE0aqhuNMjU+RCjuXRQ -Vav2EcOxT65ax6Ow1nmNA6YGi1GUAcktgMmY+Cl72iVyEqz8kUwUS1TBj0EqysCW -E57CJo6S9Htnhq9/qrJL1LvW2iH9mWobZnMbI6+jN8C/eTECQQDmrnS72ZzNJcLc -yU9uahH5BaX2vUWpWdurjYend3L9sHII3hZznYTOBn5a4kCfF2CD1FYlL7LMuV4q -qab8O5QNAkEA12CzTV3lpK8LOFX5CTT4gM5XAZvP0+YiThnRrGh15JRgZoV6Larn -X+Tvk8qYGRZdjILnNaOCqp9j3z7Mpvt2fQJAR+Z6dg6m4/5wFTcd7fFbtr1+9EAc -VWOvp3IOpTEDA3WapY7reo/PVBQMEDHTKIM1zwFA9IhAd7UTV8LXTGkZhQJAVUBU -mLojDRWwdkMpiShreOiz7dIT6Ic+avWzVfAfQjQtGEebPfpZDU8cOb7Gh5+ftd+W -z1eCgDEJIjPEZBBDLQJBAKnXJh9w47et8NZHsXjdqV/nWiZ2uzxijbEBCQTgLhcT -e4oSQidcpEPRAB5jsCZAa5czv74kDIRqYCjFL8fAT+4= +MIIEowIBAAKCAQEArSh1yY1FEue1zn9rlugTp+T1StUiRHWyVs9K5rIQjlBB38zv +JUfACZcuCNa8yo8ZGVdgDfB/BnCpmRKNle3qDRiNJDMkP0eiZjDp+ZslaWLhDOLv +97WFI6A9zHPKaz3WvjFTyEfTKGEy+kT6zQqi/AOgA74+7o6NK9ig9me5W/0i1eOW +5SH0NrV+E3VQlSw1j9UfeqNvsRnwq0TiTWp1FeK/eoaJ07wR/OH9nfaBKinKdH/w +DG8FOGNKSwYQt+6cjwf3sHg5iDAIe6CRFr75QGfU7XVKQ+vCzhYta5wOwEPBOq8S +p1Ga9PvPyzrBXxM8vBEldnjk0wuhzIcfq4hgnQIDAQABAoIBAQCSLzZBkiJec3/p +dWk/XW46r/Dl3EmxwittXlO9r1aKzvbOGhVLQ+e8MQWMML3xxB1MZ5eQLRkQNsz3 +jdI6YUDXDYMarJJNWgygeWsObwyGjBOy7WPpnDVqfj2t/ZNGNk61Aq/YxcperLB8 +2P9jWzd9yxGsF1DJ1U8ZVSmO3MKABTCllTlflFeGWlFo8rPHxh6JbY5IBj6CB0eb +JKUsabMM3LykEefbzAh37ff647XA7292wfQ/+aZVBQnQ01xMuGnkxrFcyYpE3NJx +6GBjdwrlEtZPb3TzXWdCoj5U0YT/4L3Up654MZ9Zhyw8ah8AT/m9XbDUdInwXG+/ +vfm5RmuBAoGBAOHMN4YmWWyFw5oFZ3oSmPQmbhONurjInJpga8k/HjGWBctsBKCD +hXZ3MIaGPDLyNOMrsjBNcKmnY1+Jk8fxVgKnTiut3aSFRIVeHrlmGMj1w55csMBw +V38i4L1vJUXU7ErOTPGzNXJQMc3KWqBMx3nnz335j4SHhpk5da+I351HAoGBAMRR +wFrUCnWBCcRauzv1WhL/CahT3ZRfD5QF8TsIAq3aG7AXnrp2UkaEG9k3lMTLjQkp +e2AWuvXHlQV0K77t4ocdP2UXNUZS10SQk2CX0HfS3pI0pWzLDf8dnfcPfZ/TVDc/ +8Gb82GKh8hsoTgNDPD30kt/2vgBT7gJfoH8hU3T7AoGAJsU+/2zUS/sH5AlrhB1v +X/S9T+Q1HIdtxGAsAckxsQf2hMBOZxVONFIw5dhku8a06BDXrs2NO8Q/HudrlZhN +0XTSylM3TImJg3Duy5zJQrBYX3fA7boce/sMJdrQxpXR4OJded7wnWlBs/k76Gxm +j/sKuLHCP9pZdTPVjkdtRf8CgYA9PO8OphP5IV5FlLPQ+TC2uj1t93Mn5Bs85jmg +W1hSmLWIUWXe7iet/WmecVsDpCcDU6A7kfuRzUbr45f9v8CouvPaecnRfOfPaHXA +bLrHlcx9uNRdQl3EVZ2/wmJCZ65eaaB4z6hD5BZcaE8lb2SsQs1J9XLBMW0N6nxr +C5833wKBgCZNl1qA8avepAOnqm/e2Pl54xsPjhzli2z8Ppe9S6rhe9QvYv2OsJkQ +8Ja7zGTqRXJU4Rahcs6OYULommkgq5LpVCsszOTZDQH5WgICLvhBsxo2dEGyd3ov +RTWdXnPYoWlj77ofnSdOsejF4kM4nHRD9Btq+VZ4NEAxe3FiUYbH -----END RSA PRIVATE KEY----- Index: tests/certs/server.pem ================================================================== --- tests/certs/server.pem +++ tests/certs/server.pem @@ -1,14 +1,19 @@ -----BEGIN CERTIFICATE----- -MIICHzCCAYgCAQAwDQYJKoZIhvcNAQEEBQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNV -BAgTEEJyaXRpc2ggQ29sdW1iaWExEjAQBgNVBAcTCVZhbmNvdXZlcjEaMBgGA1UE -ChMRU2FtcGxlIENlcnRzIEludGwwHhcNMDEwNjIxMjAyODUyWhcNMDEwNzIxMjAy -ODUyWjBYMQswCQYDVQQGEwJDQTEZMBcGA1UECBMQQnJpdGlzaCBDb2x1bWJpYTES -MBAGA1UEBxMJVmFuY291dmVyMRowGAYDVQQKExFTYW1wbGUgQ2VydHMgSW50bDCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwhOnBzzpD5zkqaG7kQyky3L43Zod -bwGJsDr1y3AZKTfj5DeON9ilEcrYNK8OMO/hw0vildd6QBeQ+ZkpfzjrH8EM4v3U -CklRdM5W8+S+63AaiLdnBl8+4LF/FUTMnFx9Fwt0AAGVFt9s2HqNBtvLgrbQIesL -fGAirvffkzYUSFkCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBXJZfVMqZw9T4EgXQo -nM0geAByeqyOCoR+4dPv4hipf/c1m8sZgG1SxrXVThey4i4UkZenKz+VlPGDX0++ -sJBKod+aa24wcR5IQBTDuxzwduwuKkbjzGG+zdBXjOgxdcLxw7ozNciSSALYVnez -0uX7n/lAP92SlcEXhoUroMjeLQ== +MIIDJDCCAgwCAQMwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNV +BAgMEEJyaXRpc2ggQ29sdW1iaWExEjAQBgNVBAcMCVZhbmNvdXZlcjEaMBgGA1UE +CgwRU2FtcGxlIENlcnRzIEludGwwHhcNMTkwNzE4MTEzMTUzWhcNMTkwODE3MTEz +MTUzWjBYMQswCQYDVQQGEwJDQTEZMBcGA1UECAwQQnJpdGlzaCBDb2x1bWJpYTES +MBAGA1UEBwwJVmFuY291dmVyMRowGAYDVQQKDBFTYW1wbGUgQ2VydHMgSW50bDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0odcmNRRLntc5/a5boE6fk +9UrVIkR1slbPSuayEI5QQd/M7yVHwAmXLgjWvMqPGRlXYA3wfwZwqZkSjZXt6g0Y +jSQzJD9HomYw6fmbJWli4Qzi7/e1hSOgPcxzyms91r4xU8hH0yhhMvpE+s0KovwD +oAO+Pu6OjSvYoPZnuVv9ItXjluUh9Da1fhN1UJUsNY/VH3qjb7EZ8KtE4k1qdRXi +v3qGidO8Efzh/Z32gSopynR/8AxvBThjSksGELfunI8H97B4OYgwCHugkRa++UBn +1O11SkPrws4WLWucDsBDwTqvEqdRmvT7z8s6wV8TPLwRJXZ45NMLocyHH6uIYJ0C +AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAj5gWwGYUjNK3v9fvIRu58bvg7r43SK7e +4w1UEe7x8ZyquG7flomqdBoI5SwQo4C3VMu0Ds9c+psG6GUjnUB5Gki9GE34pkQS +LOlfOyitvJYO+UaD4C+H0ZWyPAvHPfVwAk4CofKoIBp5eNkTIZASzgYXPbNSuO6K +59cOM9/hPq4sJ0Pr+XEMYTYYozc5ewvjzRzCvPPkO2DT5kIoyslpRxnidG9+Ugxx +Bo1WG05QQLN8HYH40fmUNou0omN1T8D7CCcTkWp1EU28vir6omwke0YTaEiFYqMH +6CFN7/Z5sn0Vj3b3+f7w8Wdqw7DfsyL6DJD7vl8UjuYDHXDLLYVbUw== -----END CERTIFICATE----- Index: tests/certs/server.req ================================================================== --- tests/certs/server.req +++ tests/certs/server.req @@ -1,11 +1,17 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIBmDCCAQECAQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNVBAgTEEJyaXRpc2ggQ29s -dW1iaWExEjAQBgNVBAcTCVZhbmNvdXZlcjEaMBgGA1UEChMRU2FtcGxlIENlcnRz -IEludGwwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMITpwc86Q+c5Kmhu5EM -pMty+N2aHW8BibA69ctwGSk34+Q3jjfYpRHK2DSvDjDv4cNL4pXXekAXkPmZKX84 -6x/BDOL91ApJUXTOVvPkvutwGoi3ZwZfPuCxfxVEzJxcfRcLdAABlRbfbNh6jQbb -y4K20CHrC3xgIq7335M2FEhZAgMBAAGgADANBgkqhkiG9w0BAQQFAAOBgQBsiv9V -OdF/lp3ovGfYj3DF3QyfH6p0fCuUADKgReLKOilMDPR77WE/kExxqRR9dTzlTY4n -dEmvzfmV3Vbj8KKs3L9NoLo6vF/ZeSt+RyJQlJblzXuFqxMlpZJoYcFSZO1E0Jl8 -iHe6QMOI58MBe/waEPxvIyFo2L30wScEyy/Ynw== +MIICnTCCAYUCAQAwWDELMAkGA1UEBhMCQ0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29s +dW1iaWExEjAQBgNVBAcMCVZhbmNvdXZlcjEaMBgGA1UECgwRU2FtcGxlIENlcnRz +IEludGwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtKHXJjUUS57XO +f2uW6BOn5PVK1SJEdbJWz0rmshCOUEHfzO8lR8AJly4I1rzKjxkZV2AN8H8GcKmZ +Eo2V7eoNGI0kMyQ/R6JmMOn5myVpYuEM4u/3tYUjoD3Mc8prPda+MVPIR9MoYTL6 +RPrNCqL8A6ADvj7ujo0r2KD2Z7lb/SLV45blIfQ2tX4TdVCVLDWP1R96o2+xGfCr +ROJNanUV4r96honTvBH84f2d9oEqKcp0f/AMbwU4Y0pLBhC37pyPB/eweDmIMAh7 +oJEWvvlAZ9TtdUpD68LOFi1rnA7AQ8E6rxKnUZr0+8/LOsFfEzy8ESV2eOTTC6HM +hx+riGCdAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAhSwccC2Oke5E6j/f7CjT +SK4ExJfi/1Ze1OBkzaxLny0hSxMbK8iARSciOD7LLcJ1ZAq6aWwnxutHRLpGfO1t +Nw+OG/AXeoonfLQJzLcU+w/GFOyfSjrSrNo8ePrflOzH6WKMuVH7tNw6PNWDggdG +khDNq+VklBt6YxZ0X4FbPFuOKjOvjAfKyYY5ZfMSnOYtiZBb7aQEEoeBwcJkiL8D +QQfwvtlKF8SWdeM61R8fibEw02XelXoIyyQZpL+7BIVPe84AMaJEUI5ijJ/dDOsP +JFCpozCuNS8P49INvxH+2FdXk05V+/AcMmqJpNEJ916PecwjSTAlcmFmnq43+jM8 +rA== -----END CERTIFICATE REQUEST----- ADDED tests/ciphers.csv Index: tests/ciphers.csv ================================================================== --- /dev/null +++ tests/ciphers.csv @@ -0,0 +1,46 @@ +# Group,Name,Constraints,Setup,Body,Cleanup,Match,Result,Output,Error Output,Return Codes +command,package require tls,,,,,,,,, +command,,,,,,,,,, +command,# Make sure path includes location of OpenSSL executable,,,,,,,,, +command,"if {[info exists ::env(OPENSSL)]} {set ::env(path) [string cat [file join $::env(OPENSSL) bin] "";"" $::env(path)}",,,,,,,,, +command,,,,,,,,,, +command,# Constraints,,,,,,,,, +command,set protocols [list ssl2 ssl3 tls1 tls1.1 tls1.2 tls1.3],,,,,,,,, +command,foreach protocol $protocols {::tcltest::testConstraint $protocol 0},,,,,,,,, +command,foreach protocol [::tls::protocols] {::tcltest::testConstraint $protocol 1},,,,,,,,, +command,"::tcltest::testConstraint OpenSSL [string match ""OpenSSL*"" [::tls::version]]",,,,,,,,, +,,,,,,,,,, +command,# Helper functions,,,,,,,,, +command,"proc lcompare {list1 list2} {set m """";set u """";foreach i $list1 {if {$i ni $list2} {lappend m $i}};foreach i $list2 {if {$i ni $list1} {lappend u $i}};return [list ""missing"" $m ""unexpected"" $u]}",,,,,,,,, +command,proc exec_get {delim args} {return [split [exec openssl {*}$args] $delim]},,,,,,,,, +,,,,,,,,,, +command,# Test protocols,,,,,,,,, +Protocols,All,,,lcompare $protocols [::tls::protocols],,,missing {ssl2 ssl3} unexpected {},,, +,,,,,,,,,, +command,# Test ciphers,,,,,,,,, +CiphersAll,SSL2,ssl2,,"lcompare [exec_get "":"" ciphers -ssl2] [::tls::ciphers ssl2]",,,missing {} unexpected {},,, +CiphersAll,SSL3,ssl3,,"lcompare [exec_get "":"" ciphers -ssl3] [::tls::ciphers ssl3]",,,missing {} unexpected {},,, +CiphersAll,TLS1,tls1,,"lcompare [exec_get "":"" ciphers -tls1] [::tls::ciphers tls1]",,,missing {} unexpected {},,, +CiphersAll,TLS1.1,tls1.1,,"lcompare [exec_get "":"" ciphers -tls1_1] [::tls::ciphers tls1.1]",,,missing {} unexpected {},,, +CiphersAll,TLS1.2,tls1.2,,"lcompare [exec_get "":"" ciphers -tls1_2] [::tls::ciphers tls1.2]",,,missing {} unexpected {},,, +CiphersAll,TLS1.3,tls1.3,,"lcompare [exec_get "":"" ciphers -tls1_3] [::tls::ciphers tls1.3]",,,missing {} unexpected {},,, +,,,,,,,,,, +command,# Test cipher descriptions,,,,,,,,, +CiphersDesc,SSL2,ssl2,,"lcompare [exec_get ""\r\n"" ciphers -ssl2 -v] [split [string trim [::tls::ciphers ssl2 1]] \n]",,,missing {} unexpected {},,, +CiphersDesc,SSL3,ssl3,,"lcompare [exec_get ""\r\n"" ciphers -ssl3 -v] [split [string trim [::tls::ciphers ssl3 1]] \n]",,,missing {} unexpected {},,, +CiphersDesc,TLS1,tls1,,"lcompare [exec_get ""\r\n"" ciphers -tls1 -v] [split [string trim [::tls::ciphers tls1 1]] \n]",,,missing {} unexpected {},,, +CiphersDesc,TLS1.1,tls1.1,,"lcompare [exec_get ""\r\n"" ciphers -tls1_1 -v] [split [string trim [::tls::ciphers tls1.1 1]] \n]",,,missing {} unexpected {},,, +CiphersDesc,TLS1.2,tls1.2,,"lcompare [exec_get ""\r\n"" ciphers -tls1_2 -v] [split [string trim [::tls::ciphers tls1.2 1]] \n]",,,missing {} unexpected {},,, +CiphersDesc,TLS1.3,tls1.3,,"lcompare [exec_get ""\r\n"" ciphers -tls1_3 -v] [split [string trim [::tls::ciphers tls1.3 1]] \n]",,,missing {} unexpected {},,, +,,,,,,,,,, +command,# Test protocol specific ciphers,,,,,,,,, +CiphersSpecific,SSL2,ssl2,,"lcompare [exec_get "":"" ciphers -ssl2 -s] [::tls::ciphers ssl2 0 1]",,,missing {} unexpected {},,, +CiphersSpecific,SSL3,ssl3,,"lcompare [exec_get "":"" ciphers -ssl3 -s] [::tls::ciphers ssl3 0 1]",,,missing {} unexpected {},,, +CiphersSpecific,TLS1,tls1,,"lcompare [exec_get "":"" ciphers -tls1 -s] [::tls::ciphers tls1 0 1]",,,missing {} unexpected {},,, +CiphersSpecific,TLS1.1,tls1.1,,"lcompare [exec_get "":"" ciphers -tls1_1 -s] [::tls::ciphers tls1.1 0 1]",,,missing {} unexpected {},,, +CiphersSpecific,TLS1.2,tls1.2,,"lcompare [exec_get "":"" ciphers -tls1_2 -s] [::tls::ciphers tls1.2 0 1]",,,missing {} unexpected {},,, +CiphersSpecific,TLS1.3,tls1.3,,"lcompare [exec_get "":"" ciphers -tls1_3 -s] [::tls::ciphers tls1.3 0 1]",,,missing {} unexpected {},,, +,,,,,,,,,, +command,# Test version,,,,,,,,, +Version,All,,,::tls::version,,glob,*,,, +Version,OpenSSL,OpenSSL,,::tls::version,,glob,OpenSSL*,,, Index: tests/ciphers.test ================================================================== --- tests/ciphers.test +++ tests/ciphers.test @@ -1,157 +1,121 @@ -# Commands covered: tls::ciphers -# -# This file contains a collection of tests for one or more of the Tcl -# built-in commands. Sourcing this file into Tcl runs the tests and -# generates output for errors. No output means no errors were found. -# - -# All rights reserved. -# -# See the file "license.terms" for information on usage and redistribution -# of this file, and for a DISCLAIMER OF ALL WARRANTIES. -# - -if {[lsearch [namespace children] ::tcltest] == -1} { - package require tcltest - namespace import ::tcltest::* -} - -# The build dir is added as the first element of $PATH +# Auto generated test cases for ciphers_and_protocols.csv + +# Load Tcl Test package +if {[lsearch [namespace children] ::tcltest] == -1} { + package require tcltest + namespace import ::tcltest::* +} + +set auto_path [concat [list [file dirname [file dirname [info script]]]] $auto_path] + package require tls -# One of these should == 1, depending on what type of ssl library -# tls was compiled against. (RSA BSAFE SSL-C or OpenSSL). -# -set ::tcltest::testConstraints(rsabsafe) 0 -set ::tcltest::testConstraints(openssl) [string match "OpenSSL*" [tls::version]] - -set ::EXPECTEDCIPHERS(rsabsafe) { - EDH-DSS-RC4-SHA - EDH-RSA-DES-CBC3-SHA - EDH-DSS-DES-CBC3-SHA - DES-CBC3-SHA - RC4-SHA - RC4-MD5 - EDH-RSA-DES-CBC-SHA - EDH-DSS-DES-CBC-SHA - DES-CBC-SHA - EXP-EDH-DSS-DES-56-SHA - EXP-EDH-DSS-RC4-56-SHA - EXP-DES-56-SHA - EXP-RC4-56-SHA - EXP-EDH-RSA-DES-CBC-SHA - EXP-EDH-DSS-DES-CBC-SHA - EXP-DES-CBC-SHA - EXP-RC2-CBC-MD5 - EXP-RC4-MD5 -} - -set ::EXPECTEDCIPHERS(openssl) { - AES128-SHA - AES256-SHA - DES-CBC-SHA - DES-CBC3-SHA - DHE-DSS-AES128-SHA - DHE-DSS-AES256-SHA - DHE-DSS-RC4-SHA - DHE-RSA-AES128-SHA - DHE-RSA-AES256-SHA - EDH-DSS-DES-CBC-SHA - EDH-DSS-DES-CBC3-SHA - EDH-RSA-DES-CBC-SHA - EDH-RSA-DES-CBC3-SHA - EXP-DES-CBC-SHA - EXP-EDH-DSS-DES-CBC-SHA - EXP-EDH-RSA-DES-CBC-SHA - EXP-RC2-CBC-MD5 - EXP-RC4-MD5 - EXP1024-DES-CBC-SHA - EXP1024-DHE-DSS-DES-CBC-SHA - EXP1024-DHE-DSS-RC4-SHA - EXP1024-RC2-CBC-MD5 - EXP1024-RC4-MD5 - EXP1024-RC4-SHA - IDEA-CBC-SHA - RC4-MD5 - RC4-SHA -} - -set ::EXPECTEDCIPHERS(openssl0.9.8) { - DHE-RSA-AES256-SHA - DHE-DSS-AES256-SHA - AES256-SHA - EDH-RSA-DES-CBC3-SHA - EDH-DSS-DES-CBC3-SHA - DES-CBC3-SHA - DHE-RSA-AES128-SHA - DHE-DSS-AES128-SHA - AES128-SHA - IDEA-CBC-SHA - RC4-SHA - RC4-MD5 - EDH-RSA-DES-CBC-SHA - EDH-DSS-DES-CBC-SHA - DES-CBC-SHA - EXP-EDH-RSA-DES-CBC-SHA - EXP-EDH-DSS-DES-CBC-SHA - EXP-DES-CBC-SHA - EXP-RC2-CBC-MD5 - EXP-RC4-MD5 -} - -set version "" -if {[string match "OpenSSL*" [tls::version]]} { - regexp {OpenSSL ([\d\.]+)} [tls::version] -> version -} -if {![info exists ::EXPECTEDCIPHERS(openssl$version)]} { - set version "" -} - -proc listcompare {wants haves} { - array set want {} - array set have {} - foreach item $wants { set want($item) 1 } - foreach item $haves { set have($item) 1 } - foreach item [lsort -dictionary [array names have]] { - if {[info exists want($item)]} { - unset want($item) have($item) - } - } - if {[array size want] || [array size have]} { - return [list MISSING [array names want] UNEXPECTED [array names have]] - } -} - -test ciphers-1.1 {Tls::ciphers for ssl3} {rsabsafe} { - # This will fail if you compiled against OpenSSL. - # Change the constraint setting above. - listcompare $::EXPECTEDCIPHERS(rsabsafe) [tls::ciphers ssl3] -} {} - -test ciphers-1.2 {Tls::ciphers for tls1} {rsabsafe} { - # This will fail if you compiled against OpenSSL. - # Change the constraint setting above. - listcompare $::EXPECTEDCIPHERS(rsabsafe) [tls::ciphers tls1] -} {} - -test ciphers-1.3 {Tls::ciphers for ssl3} {openssl} { - # This will fail if you compiled against RSA bsafe or with a - # different set of defines than the default. - # Change the constraint setting above. - listcompare $::EXPECTEDCIPHERS(openssl$version) [tls::ciphers ssl3] -} {} - -# This version of the test is correct for OpenSSL only. -# An equivalent test for the RSA BSAFE SSL-C is earlier in this file. - -test ciphers-1.4 {Tls::ciphers for tls1} {openssl} { - # This will fail if you compiled against RSA bsafe or with a - # different set of defines than the default. - # Change the constraint setting in all.tcl - listcompare $::EXPECTEDCIPHERS(openssl$version) [tls::ciphers tls1] -} {} - - -# cleanup +# Make sure path includes location of OpenSSL executable +if {[info exists ::env(OPENSSL)]} {set ::env(path) [string cat [file join $::env(OPENSSL) bin] ";" $::env(path)} + +# Constraints +set protocols [list ssl2 ssl3 tls1 tls1.1 tls1.2 tls1.3] +foreach protocol $protocols {::tcltest::testConstraint $protocol 0} +foreach protocol [::tls::protocols] {::tcltest::testConstraint $protocol 1} +::tcltest::testConstraint OpenSSL [string match "OpenSSL*" [::tls::version]] +# Helper functions +proc lcompare {list1 list2} {set m "";set u "";foreach i $list1 {if {$i ni $list2} {lappend m $i}};foreach i $list2 {if {$i ni $list1} {lappend u $i}};return [list "missing" $m "unexpected" $u]} +proc exec_get {delim args} {return [split [exec openssl {*}$args] $delim]} +# Test protocols + + +test Protocols-1.1 {All} -body { + lcompare $protocols [::tls::protocols] + } -result {missing {ssl2 ssl3} unexpected {}} +# Test ciphers + + +test CiphersAll-2.1 {SSL2} -constraints {ssl2} -body { + lcompare [exec_get ":" ciphers -ssl2] [::tls::ciphers ssl2] + } -result {missing {} unexpected {}} + +test CiphersAll-2.2 {SSL3} -constraints {ssl3} -body { + lcompare [exec_get ":" ciphers -ssl3] [::tls::ciphers ssl3] + } -result {missing {} unexpected {}} + +test CiphersAll-2.3 {TLS1} -constraints {tls1} -body { + lcompare [exec_get ":" ciphers -tls1] [::tls::ciphers tls1] + } -result {missing {} unexpected {}} + +test CiphersAll-2.4 {TLS1.1} -constraints {tls1.1} -body { + lcompare [exec_get ":" ciphers -tls1_1] [::tls::ciphers tls1.1] + } -result {missing {} unexpected {}} + +test CiphersAll-2.5 {TLS1.2} -constraints {tls1.2} -body { + lcompare [exec_get ":" ciphers -tls1_2] [::tls::ciphers tls1.2] + } -result {missing {} unexpected {}} + +test CiphersAll-2.6 {TLS1.3} -constraints {tls1.3} -body { + lcompare [exec_get ":" ciphers -tls1_3] [::tls::ciphers tls1.3] + } -result {missing {} unexpected {}} +# Test cipher descriptions + + +test CiphersDesc-3.1 {SSL2} -constraints {ssl2} -body { + lcompare [exec_get "\r\n" ciphers -ssl2 -v] [split [string trim [::tls::ciphers ssl2 1]] \n] + } -result {missing {} unexpected {}} + +test CiphersDesc-3.2 {SSL3} -constraints {ssl3} -body { + lcompare [exec_get "\r\n" ciphers -ssl3 -v] [split [string trim [::tls::ciphers ssl3 1]] \n] + } -result {missing {} unexpected {}} + +test CiphersDesc-3.3 {TLS1} -constraints {tls1} -body { + lcompare [exec_get "\r\n" ciphers -tls1 -v] [split [string trim [::tls::ciphers tls1 1]] \n] + } -result {missing {} unexpected {}} + +test CiphersDesc-3.4 {TLS1.1} -constraints {tls1.1} -body { + lcompare [exec_get "\r\n" ciphers -tls1_1 -v] [split [string trim [::tls::ciphers tls1.1 1]] \n] + } -result {missing {} unexpected {}} + +test CiphersDesc-3.5 {TLS1.2} -constraints {tls1.2} -body { + lcompare [exec_get "\r\n" ciphers -tls1_2 -v] [split [string trim [::tls::ciphers tls1.2 1]] \n] + } -result {missing {} unexpected {}} + +test CiphersDesc-3.6 {TLS1.3} -constraints {tls1.3} -body { + lcompare [exec_get "\r\n" ciphers -tls1_3 -v] [split [string trim [::tls::ciphers tls1.3 1]] \n] + } -result {missing {} unexpected {}} +# Test protocol specific ciphers + + +test CiphersSpecific-4.1 {SSL2} -constraints {ssl2} -body { + lcompare [exec_get ":" ciphers -ssl2 -s] [::tls::ciphers ssl2 0 1] + } -result {missing {} unexpected {}} + +test CiphersSpecific-4.2 {SSL3} -constraints {ssl3} -body { + lcompare [exec_get ":" ciphers -ssl3 -s] [::tls::ciphers ssl3 0 1] + } -result {missing {} unexpected {}} + +test CiphersSpecific-4.3 {TLS1} -constraints {tls1} -body { + lcompare [exec_get ":" ciphers -tls1 -s] [::tls::ciphers tls1 0 1] + } -result {missing {} unexpected {}} + +test CiphersSpecific-4.4 {TLS1.1} -constraints {tls1.1} -body { + lcompare [exec_get ":" ciphers -tls1_1 -s] [::tls::ciphers tls1.1 0 1] + } -result {missing {} unexpected {}} + +test CiphersSpecific-4.5 {TLS1.2} -constraints {tls1.2} -body { + lcompare [exec_get ":" ciphers -tls1_2 -s] [::tls::ciphers tls1.2 0 1] + } -result {missing {} unexpected {}} + +test CiphersSpecific-4.6 {TLS1.3} -constraints {tls1.3} -body { + lcompare [exec_get ":" ciphers -tls1_3 -s] [::tls::ciphers tls1.3 0 1] + } -result {missing {} unexpected {}} +# Test version + + +test Version-5.1 {All} -body { + ::tls::version + } -match {glob} -result {*} + +test Version-5.2 {OpenSSL} -constraints {OpenSSL} -body { + ::tls::version + } -match {glob} -result {OpenSSL*} + +# Cleanup ::tcltest::cleanupTests return ADDED tests/common.tcl Index: tests/common.tcl ================================================================== --- /dev/null +++ tests/common.tcl @@ -0,0 +1,28 @@ + +# Common Constraints +package require tls + +# Supported protocols +set protocols [list ssl2 ssl3 tls1 tls1.1 tls1.2 tls1.3] +foreach protocol $protocols { + ::tcltest::testConstraint $protocol 0 + ::tcltest::testConstraint !$protocol 1 +} + +foreach protocol [::tls::protocols] { + ::tcltest::testConstraint $protocol 1 + ::tcltest::testConstraint !$protocol 0 +} + +# OpenSSL version +::tcltest::testConstraint OpenSSL [string match "OpenSSL*" [::tls::version]] + +# Legacy OpenSSL v1.1.1 vs new v3.x +scan [lindex [split [::tls::version]] 1] %f version +::tcltest::testConstraint new_api [expr {$version >= 3.0}] +::tcltest::testConstraint old_api [expr {$version < 3.0}] + +# Load legacy provider +if {$version >= 3.0} { + tls::provider legacy +} Index: tests/keytest1.tcl ================================================================== --- tests/keytest1.tcl +++ tests/keytest1.tcl @@ -1,24 +1,25 @@ -#!/bin/sh -# The next line is executed by /bin/sh, but not tcl \ -exec tclsh "$0" ${1+"$@"} +#!/usr/bin/env tclsh set auto_path [linsert $auto_path 0 [file normalize [file join [file dirname [info script]] ..]]] package require tls proc creadable {s} { puts "LINE=[gets $s]" after 2000 + file delete -force $::keyfile + file delete -force $::certfile exit } proc myserv {s args} { fileevent $s readable [list creadable $s] } -close [file tempfile keyfile] -close [file tempfile certfile] +close [file tempfile keyfile keyfile] +close [file tempfile certfile certfile] + tls::misc req 1024 $keyfile $certfile [list C CCC ST STTT L LLLL O OOOO OU OUUUU CN CNNNN Email some@email.com days 730 serial 12] tls::socket -keyfile $keyfile -certfile $certfile -server myserv 12300 puts "Now run keytest2.tcl" ADDED tests/make_test_files.tcl Index: tests/make_test_files.tcl ================================================================== --- /dev/null +++ tests/make_test_files.tcl @@ -0,0 +1,123 @@ +# +# Name: Make Test Files From CSV Files +# Version: 0.2 +# Date: August 6, 2022 +# Author: Brian O'Hagan +# Email: brian199@comcast.net +# Legal Notice: (c) Copyright 2020 by Brian O'Hagan +# Released under the Apache v2.0 license. I would appreciate a copy of any modifications +# made to this package for possible incorporation in a future release. +# + +# +# Convert test case file into test files +# +proc process_config_file {filename} { + set prev "" + set test 0 + + # Open file with test case indo + set in [open $filename r] + array set cases [list] + + # Open output test file + set out [open [format %s.test [file rootname $filename]] w] + array set cases [list] + + # Add setup commands to test file + puts $out [format "# Auto generated test cases for %s" [file tail $filename]] + #puts $out [format "# Auto generated test cases for %s created on %s" [file tail $filename] [clock format [clock seconds]]] + + # Package requires + puts $out "\n# Load Tcl Test package" + puts $out [subst -nocommands {if {[lsearch [namespace children] ::tcltest] == -1} {\n\tpackage require tcltest\n\tnamespace import ::tcltest::*\n}\n}] + puts $out {set auto_path [concat [list [file dirname [file dirname [info script]]]] $auto_path]} + puts $out "" + + # Generate test cases and add to test file + while {[gets $in data] > -1} { + # Skip comments + set data [string trim $data] + if {[string match "#*" $data]} continue + + # Split comma separated fields with quotes + set list [list] + while {[string length $data] > 0} { + if {[string index $data 0] eq "\""} { + # Quoted + set end [string first "\"," $data] + if {$end == -1} {set end [expr {[string length $data]+1}]} + lappend list [string map [list {""} \"] [string range $data 1 [incr end -1]]] + set data [string range $data [incr end 3] end] + + } else { + # Not quoted, so no embedded NL, quotes, or commas + set index [string first "," $data] + if {$index == -1} {set index [expr {[string length $data]+1}]} + lappend list [string range $data 0 [incr index -1]] + set data [string range $data [incr index 2] end] + } + } + + # Get command or test case + foreach {group name constraints setup body cleanup match result output errorOutput returnCodes} $list { + if {$group eq "command"} { + # Pass-through command + puts $out $name + + } elseif {$group ne "" && $body ne ""} { + set group [string map [list " " "_"] $group] + if {$group ne $prev} { + incr test + set prev $group + puts $out "" + } + + # Test case + set buffer [format "\ntest %s-%d.%d {%s}" $group $test [incr cases($group)] $name] + foreach opt [list -constraints -setup -body -cleanup -match -result -output -errorOutput -returnCodes] { + set cmd [string trim [set [string trimleft $opt "-"]]] + if {$cmd ne ""} { + if {$opt in [list -setup -body -cleanup]} { + append buffer " " $opt " \{\n" + foreach line [split $cmd ";"] { + append buffer \t [string trim $line] \n + } + append buffer " \}" + } elseif {$opt in [list -output -errorOutput]} { + append buffer " " $opt " {" $cmd \n "}" + } elseif {$opt in [list -result]} { + if {[string index $cmd 0] in [list \[ \" \{]} { + append buffer " " $opt " " $cmd + } elseif {[string match {*[\\$]*} $cmd]} { + append buffer " " $opt " \"" [string map [list \\\\\" \\\"] [string map [list \" \\\" ] $cmd]] "\"" + } else { + append buffer " " $opt " {" $cmd "}" + } + } else { + append buffer " " $opt " {" $cmd "}" + } + } + } + puts $out $buffer + + } else { + # Empty line + } + break + } + } + + # Output clean-up commands + puts $out "\n# Cleanup\n::tcltest::cleanupTests\nreturn" + close $out + close $in +} + +# +# Call script +# +foreach file [glob *.csv] { + process_config_file $file +} +exit Index: tests/simpleClient.tcl ================================================================== --- tests/simpleClient.tcl +++ tests/simpleClient.tcl @@ -1,8 +1,6 @@ -#!/bin/sh -# The next line is executed by /bin/sh, but not tcl \ -exec tclsh8.3 "$0" ${1+"$@"} +#!/usr/bin/env tclsh package require tls set dir [file join [file dirname [info script]] ../tests/certs] set OPTS(-cafile) [file join $dir ca.pem] Index: tests/simpleServer.tcl ================================================================== --- tests/simpleServer.tcl +++ tests/simpleServer.tcl @@ -1,8 +1,6 @@ -#!/bin/sh -# The next line is executed by /bin/sh, but not tcl \ -exec tclsh8.3 "$0" ${1+"$@"} +#!/usr/bin/env tclsh package require tls set dir [file join [file dirname [info script]] ../tests/certs] set OPTS(-cafile) [file join $dir ca.pem] Index: tests/tlsIO.test ================================================================== --- tests/tlsIO.test +++ tests/tlsIO.test @@ -166,11 +166,11 @@ set remoteServerIP 127.0.0.1 set remoteFile [file join [pwd] remote.tcl] if {[catch {set remoteProcChan \ [open "|[list $::tcltest::tcltest $remoteFile \ -serverIsSilent -port $remoteServerPort \ - -address $remoteServerIP] 2> /dev/null" w+]} msg] == 0} { + -address $remoteServerIP]" w+]} msg] == 0} { after 1000 if {[catch {set commandSocket [tls::socket -cafile $caCert \ -certfile $clientCert -keyfile $clientKey \ $remoteServerIP $remoteServerPort]} msg] == 0} { fconfigure $commandSocket -translation crlf -buffering line @@ -320,11 +320,11 @@ after cancel $timer close $f puts $x } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f x if {[catch {tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8828} msg]} { set x $msg } else { @@ -362,11 +362,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f x global port if {[catch {tls::socket -myport $port \ -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8829} sock]} { @@ -402,11 +402,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f x if {[catch {tls::socket -myaddr 127.0.0.1 \ -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8830} sock]} { set x $sock @@ -426,11 +426,11 @@ puts $f [list set auto_path $auto_path] puts $f { package require tls set timer [after 2000 "set x done"] } - puts $f "set f \[tls::socket -server accept -certfile $serverCert -cafile $caCert -keyfile $serverKey -myaddr [info hostname] 8831 \]" + puts $f "set f \[tls::socket -server accept -certfile $serverCert -cafile $caCert -keyfile $serverKey -myaddr localhost 8831 \]" puts $f { proc accept {sock addr port} { global x puts "[gets $sock]" close $sock @@ -440,14 +440,14 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f x if {[catch {tls::socket -certfile $clientCert -cafile $caCert \ - -keyfile $clientKey [info hostname] 8831} sock]} { + -keyfile $clientKey localhost 8831} sock]} { set x $sock } else { puts $sock hello flush $sock lappend x [gets $f] @@ -477,11 +477,11 @@ vwait x after cancel $timer close $f } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f x if {[catch {tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8832} sock]} { set x $sock } else { @@ -533,11 +533,11 @@ after cancel $timer close $f puts done } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f set s [tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8834] fconfigure $s -buffering line -translation lf puts $s "hello abcdefghijklmnop" @@ -580,11 +580,11 @@ after cancel $timer close $f puts "done $i" } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f set s [tls::socket -certfile $clientCert -cafile $caCert \ -keyfile $clientKey 127.0.0.1 8835] fconfigure $s -buffering line catch { @@ -633,11 +633,11 @@ gets $s close $s set done 1 } set cs [tls::socket -certfile $clientCert -cafile $caCert \ - -keyfile $clientKey [info hostname] 8830] + -keyfile $clientKey localhost 8830] close $cs vwait done after cancel $timer set done @@ -705,11 +705,11 @@ after cancel $timer close $f puts $x } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f x if {[catch {tls::socket 127.0.0.1 8828} msg]} { set x $msg } else { lappend x [gets $f] @@ -732,11 +732,11 @@ puts ready gets stdin close $f } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] + set f [open "|[list $::tcltest::tcltest script]" r+] gets $f set x [list [catch {tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ -server accept 8828} msg] \ $msg] @@ -781,11 +781,11 @@ after cancel $t3 close $s puts $x } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] + set f [open "|[list $::tcltest::tcltest script]" r+] set x [gets $f] set s1 [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8828] fconfigure $s1 -buffering line @@ -832,15 +832,15 @@ close $s puts bye gets stdin } close $f - set p1 [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] + set p1 [open "|[list $::tcltest::tcltest script]" r+] fconfigure $p1 -buffering line - set p2 [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] + set p2 [open "|[list $::tcltest::tcltest script]" r+] fconfigure $p2 -buffering line - set p3 [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] + set p3 [open "|[list $::tcltest::tcltest script]" r+] fconfigure $p3 -buffering line proc accept {s a p} { fconfigure $s -buffering line fileevent $s readable [list echo $s] } @@ -930,11 +930,11 @@ package require tls gets stdin } puts $f [list tls::socket -cafile $caCert 127.0.0.1 8848] close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r+] + set f [open "|[list $::tcltest::tcltest script]" r+] proc bgerror args { global x set x $args } proc accept {s a p} {expr 10 / 0} @@ -968,11 +968,11 @@ set timer [after 10000 "set x timed_out"] vwait x after cancel $timer } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f set s [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8820] set p [fconfigure $s -peername] @@ -1001,11 +1001,11 @@ set timer [after 10000 "set x timed_out"] vwait x after cancel $timer } close $f - set f [open "|[list $::tcltest::tcltest script] 2> /dev/null" r] + set f [open "|[list $::tcltest::tcltest script]" r] gets $f set s [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ 127.0.0.1 8821] set p [fconfigure $s -sockname] @@ -1040,11 +1040,11 @@ set x [fconfigure $s -sockname] close $s } set s1 [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ - [info hostname] 8823] + localhost 8823] set timer [after 10000 "set x timed_out"] vwait x after cancel $timer close $s close $s1 @@ -1093,20 +1093,21 @@ after 500 close $s set x done } set s1 [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ - -async [info hostname] 8830] + -async localhost 8830] # when doing an in-process client/server test, both sides need # to be non-blocking for the TLS handshake Also make sure to # return the channel to line buffering mode (TLS sets it to 'none'). fconfigure $s1 -blocking 0 -buffering line vwait x # TLS handshaking needs one byte from the client... puts $s1 a # need update to complete TLS handshake in-process update + fconfigure $s1 -blocking 1 set z [gets $s1] close $s close $s1 set z } bye @@ -1137,11 +1138,11 @@ set s [tls::socket \ -certfile $serverCert -cafile $caCert -keyfile $serverKey \ -server accept 8831] set c [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ - [info hostname] 8831] + localhost 8831] # This differs from socket-9.1 in that both sides need to be # non-blocking because of TLS' required handshake fconfigure $c -blocking 0 puts -nonewline $c 01234567890123456789012345678901234567890123456789 close $c @@ -1184,11 +1185,11 @@ set s [tls::socket \ -certfile $serverCert -cafile $caCert -keyfile $serverKey \ -server accept 8832] set c [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ - [info hostname] 8832] + localhost 8832] fconfigure $c -blocking 0 -trans lf -buffering line set count 0 puts $c hello proc readit {s} { global count done @@ -1245,11 +1246,11 @@ set s [tls::socket \ -certfile $serverCert -cafile $caCert -keyfile $serverKey \ -server accept 8833] set c [tls::socket \ -certfile $clientCert -cafile $caCert -keyfile $clientKey \ - [info hostname] 8833] + localhost 8833] fconfigure $c -blocking 0 -buffering line -translation lf fileevent $c readable "count_to_eof $c" set timer [after 2000 timerproc] vwait done close $s @@ -2012,16 +2013,14 @@ proc accept {s a p} { fconfigure $s -blocking 0 fileevent $s readable [list do_handshake $s readable readlittle \ -buffering none] } - set s [tls::socket \ - -certfile $serverCert -cafile $caCert -keyfile $serverKey \ + set s [tls::socket -certfile $serverCert -cafile $caCert -keyfile $serverKey \ -server accept 8831] - set c [tls::socket \ - -certfile $clientCert -cafile $caCert -keyfile $clientKey \ - [info hostname] 8831] + set c [tls::socket -certfile $clientCert -cafile $caCert -keyfile $clientKey \ + localhost 8831] # only the client gets tls::import set res [tls::unimport $c] list $res [catch {close $c} err] $err \ [catch {close $s} err] $err } {{} 0 {} 0 {}} @@ -2041,24 +2040,23 @@ # NOTE: when doing an in-process client/server test, both sides need # to be non-blocking for the TLS handshake # Server - Only accept TLS 1.2 set s [tls::socket \ - -certfile $serverCert -cafile $caCert -keyfile $serverKey \ - -request 0 -require 0 -ssl2 0 -ssl3 0 -tls1 0 -tls1.1 0 -tls1.2 1 \ - -server Accept 8831] + -certfile $serverCert -cafile $caCert -keyfile $serverKey -request 0 \ + -require 0 -ssl2 0 -ssl3 0 -tls1 0 -tls1.1 0 -tls1.2 1 -tls1.3 0 \ + -server Accept 8831] # Client - Only propose TLS1.0 - set c [tls::socket -async \ - -cafile $caCert \ - -request 0 -require 0 -ssl2 0 -ssl3 0 -tls1 1 -tls1.1 0 -tls1.2 0 \ - [info hostname] 8831] + set c [tls::socket -async -cafile $caCert -request 0 -require 0 \ + -ssl2 0 -ssl3 0 -tls1 1 -tls1.1 0 -tls1.2 0 -tls1.3 0 localhost 8831] fconfigure $c -blocking 0 puts $c a ; flush $c after 5000 [list set ::done timeout] vwait ::done switch -exact -- $::done { - "handshake failed: wrong ssl version" { + "handshake failed: wrong ssl version" - + "handshake failed: unsupported protocol" { set ::done "handshake failed: wrong version number" } } set ::done } {handshake failed: wrong version number} Index: win/README.txt ================================================================== --- win/README.txt +++ win/README.txt @@ -1,49 +1,84 @@ Windows DLL Build instructions using nmake build system 2020-10-15 Harald.Oehlmann@elmicron.de - 2023-08-22 Kevin Walzer (kw@codebykevin.com) + 2023-04-23 Brian O'Hagan Properties: - 64 bit DLL -- VisualStudio 2019 -- WSL -- OpenSSL dynamically linked to TCLTLS DLL. We used a freely redistributable build of OpenSSL from https://www.firedaemon.com/firedaemon-openssl. Unzip and install OpenSSL in an accessible place (we used the lib subdirectory of our Tcl installation). - -1. Visual Studio x86 native prompt. Update environmental variables for building Tcltls. Customize the below entries for your setup. - -set PATH=%PATH%;C:\tcl-trunk\lib\openssl-3\x64\bin -set INCLUDE=%INCLUDE%;C:\tcl-trunk\tcl\lib\openssl-3\x64\include\openssl -set LIB=%LIB%;C:\tcl-trunk\tcl\lib\openssl-3\x64\bin - - -2) Build TCLTLS - --> Unzip distribution on your system. --> Start WSL. --> cd /mnt/c/path/to/tcltls - -./gen_dh_params > dh_params.h - -od -A n -v -t xC < 'tls.tcl' > tls.tcl.h.new.1 -sed 's@[^0-9A-Fa-f]@@g;s@..@0x&, @g' < tls.tcl.h.new.1 > tls.tcl.h +- VisualStudio 2015 +Note: Visual C++ 6 does not build OpenSSL (long long syntax error) +- Cygwin32 (temporary helper, please help to replace by tclsh) +- OpenSSL statically linked to TCLTLS DLL. +Note: Dynamic linking also works but results in a DLL dependency on OPENSSL DLL's + +----------------------------- + +1) Build OpenSSL static libraries: + +set SSLBUILD=\path\to\build\dir +set SSLINSTALL=\path\to\install\dir +set SSLCOMMON=\path\to\common\dir + +(1a) Get OpenSSL + + https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1t/openssl-1.1.1t.tar.gz + + Unpack OpenSSL source distribution to %SSLBUILD% + +(1b) Install Perl from https://strawberryperl.com/ + + https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-64bit.msi + Install to C:\Strawberry\perl + +(1c) Install NASM Assembler from https://www.nasm.us/ + + https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-installer-x64.exe + Install to: C:\Program Files\NASM + +(1d) Configure + + At Visual Studio x86 native prompt: + + set Path=%PATH%;C:\Program Files\NASM;C:\Strawberry\perl\bin + perl ..\Configure VC-WIN64A no-shared no-filenames threads no-ssl2 no-ssl3 --api=1.1.0 --prefix="%SSLINSTALL%" --openssldir="%SSLCOMMON%" -DOPENSSL_NO_DEPRECATED + # Not used options: no-asm no-zlib no-comp no-ui-console no-autoload-config + +(1e) Build OpenSSL + + nmake + nmake test + nmake install + +----------------------------- + +2) Build TclTLS + +set BUILDDIR=\path\to\build\dir +set TCLINSTALL=\path\to\tcl\dir + +2a) Unzip distribution to %BUILDDIR% + +2b) Start BASH shell (MinGW62 Git shell) + +cd %BUILDDIR% +od -A n -v -t xC < 'library/tls.tcl' > tls.tcl.h.new.1 +sed 's@[^0-9A-Fa-f]@@g;s@..@0x&, @g' < tls.tcl.h.new.1 > generic/tls.tcl.h rm -f tls.tcl.h.new.1 --> Visual Studio x86 native prompt. - -cd C:path\to\tcltls\win - -Run the following commands (modify the flags to your specific installations). - -nmake -f makefile.vc TCLDIR=c:\users\wordt\tcl INSTALLDIR=c:\tcl-trunk\tcl\lib SSL_INSTALL_FOLDER=C:\tcl-trunk\tcl\lib\openssl-3\x64 - -nmake -f makefile.vc TCLDIR=c:\users\wordt\tcl INSTALLDIR=c:\tcl-trunk\tcl\lib SSL_INSTALL_FOLDER=C:\tcl-trunk\tcl\lib\openssl-3\x64 install - -The resulting installation will include both the tcltls package and also have libcrypto.dll and libssl.dll copied into the same directory. +2c) Start Visual Studio shell + +cd %BUILDDIR%\win + +nmake -f makefile.vc TCLDIR=%TCLINSTALL% SSL_INSTALL_FOLDER=%SSLINSTALL% +nmake -f makefile.vc install TCLDIR=c:\test\tcl8610 INSTALLDIR=%TCLINSTALL% SSL_INSTALL_FOLDER=%SSLINSTALL% + +----------------------------- 3) Test -Start tclsh +Start tclsh or wish package require tls package require http http::register https 443 [list ::tls::socket -autoservername true] set tok [http::data [http::geturl https://www.tcl-lang.org]] +::http::cleanup $tok