Index: doc/tls.html ================================================================== --- doc/tls.html +++ doc/tls.html @@ -19,11 +19,11 @@ for socket and I/O channel communications.
SYNOPSIS
-
package require Tcl ?8.5?
+
package require Tcl ?8.5?
package require tls
 
tls::init ?options?
tls::socket ?options? host port
tls::socket ?-server command? ?options? port
@@ -51,11 +51,11 @@

tls - binding to OpenSSL library for socket and I/O channel communications.

SYNOPSIS

-

package require Tcl 8.5
+

package require Tcl ?8.5?
package require tls

tls::init ?options?
tls::socket ?options? host port
tls::socket ?-server command? ?options? port
@@ -69,16 +69,17 @@ tls::version

DESCRIPTION

-

This extension provides a generic binding to OpenSSL, utilizing the -Tcl_StackChannel -API for Tcl 8.4 and higher. The sockets behave exactly the same -as channels created using Tcl's built-in socket -command with additional options for controlling the SSL session. +

This extension provides TCL script access to secure socket communications +using the Transport Layer Security (TLS) protocol. It provides a generic +binding to OpenSSL, utilizing the +Tcl_StackChannel API in Tcl 8.4 and higher. +These sockets behave exactly the same as channels created using the built-in +socket command, along with additional options for controlling +the SSL session.

COMMANDS

Typically one would use the tls::socket command @@ -459,26 +460,29 @@

- error channel message + error channelId message
- The message argument contains an error message generated - by the OpenSSL function ERR_reason_error_string(). + This form of callback is invoked whenever an error occurs during the + initial connection, handshake, or I/O operations. The message + argument can be from the Tcl_ErrnoMsg, OpenSSL function + ERR_reason_error_string(), or a custom message.

- info channel major minor message type + info channelId major minor message type
This form of callback is invoked by the OpenSSL function - SSL_CTX_set_info_callback() during connection setup - and use. + SSL_set_info_callback() during the initial connection + and handshake operations. The type argument is new for + TLS 1.8. The arguments are:
  • Possible values for major are: handshake, alert, connect, accept.
  • Possible values for minor are: @@ -491,32 +495,37 @@ info is used.
- message channel direction version content_type data + message channelId direction version content_type message
This form of callback is invoked by the OpenSSL function SSL_set_msg_callback() whenever a message is sent or - received. It is only available when - OpenSSL is complied with the enable-ssl-trace option. - Where direction is Sent or Received, version is the - protocol version, content_type is the message content type, - and data is more info on the message from the SSL_trace API. + received during the initial connection, handshake, or I/O operations. + It is only available when OpenSSL is complied with the + enable-ssl-trace option. Arguments are: direction + is Sent or Received, version is the protocol + version, content_type is the message content type, and + message is more info from the SSL_trace API. + This callback is new for TLS 1.8.

- session channel session_id ticket lifetime + session channelId session_id ticket lifetime
This form of callback is invoked by the OpenSSL function - SSL_CTX_sess_set_new_cb(). - Where session_id is the current session identifier, - ticket is the session ticket info, and lifetime - is the the ticket lifetime in seconds. + SSL_CTX_sess_set_new_cb() whenever a new session id is + sent by the server during the initial connection and handshake, but + can also be received later if the -post_handshake option is + used. Arguments are: session_id is the current + session identifier, ticket is the session ticket info, and + lifetime is the the ticket lifetime in seconds. + This callback is new for TLS 1.8.

@@ -540,10 +549,11 @@ Invoked when loading or storing a PEM certificate with encryption. Where rwflag is 0 for reading/decryption or 1 for writing/encryption (can prompt user to confirm) and size is the max password length in bytes. The callback should return the password as a string. + Both arguments are new for TLS 1.8.
@@ -550,79 +560,82 @@
-validatecommand callback
Invokes the specified callback script during handshake in order to validate the provided value(s). See below for the possible - arguments passed to the callback script. - To reject the value and abort connection, the callback should return 0. + 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.

- alpn channel protocol match -
-
- For servers, this form of callback is invoked when the client ALPN - extension is received. If match is true, protocol - is the first -alpn specified protocol common to the both the - client and server. If not, the first client specified protocol is - used. Called after hello and ALPN callbacks. -
- -
- -
- hello channel servername -
-
- For servers, this form of callback is invoked during client hello - message processing. It is used to select an appropriate certificate to - present, and make other configuration adjustments relevant to that - server name and its configuration. Called before SNI and ALPN callbacks. -
- -
- -
- sni channel servername -
-
- For servers, this form of callback is invoked when the SNI extension - from the client is received. Where servername is the client - provided server name from the -servername option. This is - used when a server supports multiple names, so the right certificate - can be used. Called after hello callback but before ALPN callback. -
- -
- -
- verify channel depth cert status error + alpn channelId protocol match +
+
+ For servers, this form of callback is invoked when the client ALPN + extension is received. If match is true, protocol + is the first -alpn 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. +
+ +
+ +
+ hello channelId servername +
+
+ 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. +
+ +
+ +
+ sni channelId servername +
+
+ For servers, this form of callback is invoked when the Server Name + Indication (SNI) extension is received. The servername + argument is the client provided server name in the -servername + 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. +
+ +
+ +
+ verify channelId depth cert status error
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.
    -
  • The depth argument is an integer representing the - current depth on the certificate chain, with - 0 as the peer certificate and higher values going - up to the Certificate Authority (CA).
  • +
  • The depth 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).
  • The cert argument is a list of key-value pairs similar to those returned by tls::status.
  • -
  • The status argument is an boolean representing the - validity of the current certificate. - A value of 0 means the certificate is deemed invalid. - A value of 1 means the certificate is deemed valid.
  • -
  • The error argument supplies the message, if any, generated +
  • The status argument is the boolean validity of the + current certificate where 0 is invalid and 1 is valid.
  • +
  • The error argument is the error message, if any, generated by X509_STORE_CTX_get_error().

@@ -668,32 +681,127 @@

The tls::debug 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 tls::callback to accept the -certificate, even when it is invalid. +certificate, even when it is invalid if the tls::validate_command +callback is used for the -validatecommand option.

The use of the variable tls::debug is not recommended. It may be removed from future releases.

+ +

Debug Examples

+ +

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.

+
+Example #1: Use HTTP package + + +

+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
+
+ +Example #2: Use raw socket +

+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
+
+

HTTPS EXAMPLE

-

This example uses a sample server.pem provided with the TLS release, -courtesy of the OpenSSL project.

+

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.

+ +Example #1: Get web page + +

+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
+
+ +Example #2: Download file

 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]
 
-set tok [http::geturl https://www.tcl.tk/]
+# Get file
+set ch [open $filename wb]
+set token [::http::geturl $url -blocksize 65536 -channel $ch]
+
+# Cleanup
+close $ch
+::http::cleanup $token
 

SPECIAL CONSIDERATIONS

The capabilities of this package can vary enormously based upon how the @@ -702,16 +810,17 @@ Use the tls::protocols commands to obtain the supported protocol versions.

SEE ALSO

-

socket, fileevent, OpenSSL

+

socket, fileevent, http, +OpenSSL


 Copyright © 1999 Matt Newman.
 Copyright © 2004 Starfish Systems.
+Copyright © 2023 Brian O'Hagan.
 
Index: generic/tls.c ================================================================== --- generic/tls.c +++ generic/tls.c @@ -50,11 +50,11 @@ (((key) == NULL) ? (char *) NULL : \ Tcl_TranslateFileName(interp, (key), (dsp))) 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, + 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 @@ -190,11 +190,11 @@ else if (where & SSL_CB_LOOP) minor = "loop"; else if (where & SSL_CB_EXIT) minor = "exit"; else minor = "unknown"; } - /* Create command to eval */ + /* 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)); @@ -316,11 +316,11 @@ buffer[n] = 0; (void)BIO_flush(bio); BIO_free(bio); } - /* Create command to eval */ + /* 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)); @@ -394,11 +394,11 @@ return 0; } dprintf("VerifyCallback: eval callback"); - /* Create command to eval */ + /* 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)); @@ -444,11 +444,11 @@ dprintf("Called"); if (statePtr->callback == (Tcl_Obj*)NULL) return; - /* Create command to eval */ + /* Create command to eval with fn, chan, and message args */ cmdPtr = Tcl_DuplicateObj(statePtr->callback); Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj("error", -1)); Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewStringObj(Tcl_GetChannelName(statePtr->self), -1)); if (msg != NULL) { @@ -534,11 +534,11 @@ } else { return -1; } } - /* Create command to eval */ + /* 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)); @@ -613,11 +613,11 @@ return SSL_TLSEXT_ERR_OK; } else if (ssl == NULL) { return SSL_TLSEXT_ERR_NOACK; } - /* Create command to eval */ + /* 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)); @@ -690,11 +690,11 @@ if (statePtr->vcmd == (Tcl_Obj*)NULL) { return res; } - /* Create command to eval */ + /* 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)); @@ -803,11 +803,11 @@ if (statePtr->vcmd == (Tcl_Obj*)NULL) { return SSL_TLSEXT_ERR_OK; } - /* Create command to eval */ + /* 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)); @@ -905,11 +905,11 @@ return SSL_CLIENT_HELLO_ERROR; } remaining = len; servername = (const char *)p; - /* Create command to eval */ + /* 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)); @@ -987,19 +987,23 @@ 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) { @@ -1046,34 +1050,34 @@ */ static int 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 *cmdObj = NULL; - Tcl_Obj *passwdObj = NULL; - Tcl_Obj *vcmd = NULL; + SSL_CTX *ctx = NULL; + Tcl_Obj *cmdObj = NULL; + Tcl_Obj *passwdObj = NULL; + Tcl_Obj *vcmd = NULL; Tcl_DString upperChannelTranslation, upperChannelBlocking, upperChannelEncoding, upperChannelEOFChar; int idx; Tcl_Size fn, 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 *ciphersuites = NULL; - char *CAfile = NULL; - char *CAdir = NULL; - char *DHparams = NULL; - char *model = NULL; - char *servername = NULL; /* hostname for Server Name Indication */ + 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_Size sess_len = 0; + Tcl_Size sess_len = 0; Tcl_Obj *alpnObj = NULL; int ssl2 = 0, ssl3 = 0; int tls1 = 1, tls1_1 = 1, tls1_2 = 1, tls1_3 = 1; int proto = 0, level = -1; int verify = 0, require = 0, request = 1, post_handshake = 0; @@ -1123,11 +1127,11 @@ switch(fn) { case _opt_alpn: alpnObj = objv[idx]; break; case _opt_cadir: - GET_OPT_STRING(objv[idx], CAdir, NULL); + GET_OPT_STRING(objv[idx], CApath, NULL); break; case _opt_cafile: GET_OPT_STRING(objv[idx], CAfile, NULL); break; case _opt_cert: @@ -1225,11 +1229,11 @@ 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)); @@ -1287,11 +1291,11 @@ return TCL_ERROR; } ctx = ((State *)Tcl_GetChannelInstanceData(chan))->ctx; } else { if ((ctx = CTX_Init(statePtr, server, proto, keyfile, certfile, key, cert, (int) key_len, - (int) cert_len, CAdir, CAfile, ciphers, ciphersuites, level, DHparams)) == NULL) { + (int) cert_len, CApath, CAfile, ciphers, ciphersuites, level, DHparams)) == NULL) { Tls_Free((char *) statePtr); return TCL_ERROR; } } @@ -1327,10 +1331,14 @@ 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); @@ -1570,17 +1578,16 @@ * *------------------------------------------------------------------- */ static SSL_CTX * 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 *CAdir, + 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"); @@ -1756,11 +1763,10 @@ #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); SSL_CTX_free(ctx); @@ -1792,34 +1798,33 @@ /* 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) { Tcl_DStringFree(&ds); Tcl_AppendResult(interp, "unable to set certificate file ", certfile, ": ", GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); 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: ", GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); return NULL; } + } else { certfile = (char*)X509_get_default_cert_file(); 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, ": ", GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); return NULL; #endif @@ -1849,11 +1854,10 @@ } 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: ", GET_ERR_REASON(), (char *) NULL); SSL_CTX_free(ctx); return NULL; @@ -1867,42 +1871,61 @@ SSL_CTX_free(ctx); return NULL; } } - /* Set verification CAs */ - Tcl_DStringInit(&ds); - Tcl_DStringInit(&ds1); - /* There is one default directory, one default file, and one default store. - The default CA certificates directory (and default store) is in the OpenSSL - certs directory. It can be overridden by the SSL_CERT_DIR env var. The - default CA certificates file is called cert.pem in the default OpenSSL - directory. It can be overridden by the SSL_CERT_FILE env var. */ - /* int SSL_CTX_set_default_verify_dir(SSL_CTX *ctx) and int SSL_CTX_set_default_verify_file(SSL_CTX *ctx) */ - 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: ", GET_ERR_REASON(), (char *) NULL); - SSL_CTX_free(ctx); - 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; } /* *------------------------------------------------------------------- Index: generic/tlsIO.c ================================================================== --- generic/tlsIO.c +++ generic/tlsIO.c @@ -163,11 +163,11 @@ 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", GET_ERR_REASON()); + 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) { @@ -187,10 +187,11 @@ 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; } @@ -226,15 +227,15 @@ 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(Tcl_GetErrno())); + Tls_Error(statePtr, (char *) Tcl_ErrnoMsg(*errorCodePtr)); } else { dprintf("I/O error occurred (backingError = %lu)", backingError); - *errorCodePtr = backingError; + *errorCodePtr = Tcl_GetErrno(); if (*errorCodePtr == ECONNRESET) { *errorCodePtr = ECONNABORTED; } Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); } @@ -243,16 +244,16 @@ 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)); } - 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))); - } statePtr->flags |= TLS_TCL_HANDSHAKE_FAILED; *errorCodePtr = ECONNABORTED; return(-1); case SSL_ERROR_WANT_READ: @@ -365,10 +366,14 @@ 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 @@ -393,15 +398,15 @@ } 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(Tcl_GetErrno())); + Tls_Error(statePtr, (char *) Tcl_ErrnoMsg(*errorCodePtr)); } else { dprintf("I/O error occurred (backingError = %lu)", backingError); - *errorCodePtr = backingError; + *errorCodePtr = Tcl_GetErrno(); bytesRead = -1; Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); } break; @@ -421,16 +426,14 @@ default: dprintf("Unknown error (err = %i), mapping to EOF", err); *errorCodePtr = 0; bytesRead = 0; + Tls_Error(statePtr, "Unknown error"); break; } - if (*errorCodePtr < 0) { - Tls_Error(statePtr, strerror(*errorCodePtr)); - } dprintf("Input(%d) -> %d [%d]", bufSize, bytesRead, *errorCodePtr); return(bytesRead); } /* @@ -488,10 +491,11 @@ 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); } @@ -560,15 +564,15 @@ } 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(Tcl_GetErrno())); + Tls_Error(statePtr, (char *) Tcl_ErrnoMsg(*errorCodePtr)); } else { dprintf("I/O error occurred (backingError = %lu)", backingError); - *errorCodePtr = backingError; + *errorCodePtr = Tcl_GetErrno(); written = -1; Tls_Error(statePtr, (char *) ERR_reason_error_string(backingError)); } break; @@ -575,23 +579,25 @@ 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; } - if (*errorCodePtr < 0) { - Tls_Error(statePtr, strerror(*errorCodePtr)); - } dprintf("Output(%d) -> %d", toWrite, written); return(written); } /* Index: tests/README.txt ================================================================== --- tests/README.txt +++ tests/README.txt @@ -1,17 +1,23 @@ Create Test Cases -1. Create test case *.csv file. You can use multiple files. Generally it's a good idea to group like functions in the same file. +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. Each column defines the equivalent input the tcltest tool expects. +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 common.tcl or in *.csv file. +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. The results will be output to the stdoutlog.txt file. - On Windows you can also use the run_all_tests.bat file. +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. -6. Review stdoutlog.txt for the count of test cases executed successfully and view details of those that failed. +SSL_CERT_DIR = Path to directory with CA files. Index: tests/badssl.csv ================================================================== --- tests/badssl.csv +++ tests/badssl.csv @@ -1,70 +1,78 @@ # Group,Name,Constraints,Setup,Body,Cleanup,Match,Result,Output,Error Output,Return Codes command,package require tls,,,,,,,,, ,,,,,,,,,, -command,# Find default CA certificates directory,,,,,,,,, -command,if {[info exists ::env(SSL_CERT_FILE)]} {set ::cafile $::env(SSL_CERT_FILE)} else {set ::cafile [file normalize {C:\Users\Brian\Documents\Source\Build\SSL-1.1\certs\cacert.pem}]},,,,,,,,, -,,,,,,,,,, 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 ch [tls::socket -autoservername 1 -require 1 -cafile $::cafile $url $port];if {[catch {tls::handshake $ch} err]} {close $ch;return -code error $err} else {close $ch}}",,,,,,,,, +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,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,,,badssl captive-portal.badssl.com,,,handshake failed: certificate verify failed due to: Hostname mismatch,,,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,,,badssl dh-composite.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,,,badssl dh480.badssl.com,,,handshake failed: dh key too small,,,1 +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,,,badssl dh1024.badssl.com,,,,,, +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,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,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,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,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,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,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,,,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,,,badssl sha1-2017.badssl.com,,,handshake failed: certificate verify failed due to: certificate has expired,,,1 -BadSSL,sha1-intermediate,,,badssl sha1-intermediate.badssl.com,,,handshake failed: certificate verify failed due to: unable to get local issuer certificate,,,1 +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,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,,,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,,badssl tls-v1-0.badssl.com:1010,,,,,, -BadSSL,tls-v1-1:1011,tls1.1,,badssl tls-v1-1.badssl.com:1011,,,,,, +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,,,badssl untrusted-root.badssl.com,,,handshake failed: certificate verify failed due to: self signed certificate in certificate chain,,,1 +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,,,badssl wrong.host.badssl.com,,,handshake failed: certificate verify failed due to: Hostname mismatch,,,1 +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,,,,,, Index: tests/badssl.test ================================================================== --- tests/badssl.test +++ tests/badssl.test @@ -8,247 +8,288 @@ set auto_path [concat [list [file dirname [file dirname [info script]]]] $auto_path] package require tls -# Find default CA certificates directory -if {[info exists ::env(SSL_CERT_FILE)]} {set ::cafile $::env(SSL_CERT_FILE)} else {set ::cafile [file normalize {C:\Users\Brian\Documents\Source\Build\SSL-1.1\certs\cacert.pem}]} - # 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 ch [tls::socket -autoservername 1 -require 1 -cafile $::cafile $url $port];if {[catch {tls::handshake $ch} err]} {close $ch;return -code error $err} else {close $ch}} +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} + } -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} -body { +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} + } -result {handshake failed: certificate verify failed due to "hostname mismatch"} -returnCodes {1} -test BadSSL-1.5 {cbc} -body { +test BadSSL-1.6 {cbc} -body { badssl cbc.badssl.com } -test BadSSL-1.6 {client-cert-missing} -body { +test BadSSL-1.7 {client-cert-missing} -body { badssl client-cert-missing.badssl.com } -test BadSSL-1.7 {client} -body { +test BadSSL-1.8 {client} -body { badssl client.badssl.com } -test BadSSL-1.8 {dh-composite} -body { +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.9 {dh-small-subgroup} -body { +test BadSSL-1.11 {dh-small-subgroup} -body { badssl dh-small-subgroup.badssl.com } -test BadSSL-1.10 {dh480} -body { +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.11 {dh512} -body { +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.12 {dh1024} -body { +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.13 {dh2048} -body { +test BadSSL-1.17 {dh2048} -body { badssl dh2048.badssl.com } -test BadSSL-1.14 {dsdtestprovider} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.15 {ecc256} -body { +test BadSSL-1.19 {ecc256} -body { badssl ecc256.badssl.com } -test BadSSL-1.16 {ecc384} -body { +test BadSSL-1.20 {ecc384} -body { badssl ecc384.badssl.com } -test BadSSL-1.17 {edellroot} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.18 {expired} -body { +test BadSSL-1.22 {expired} -body { badssl expired.badssl.com - } -result {handshake failed: certificate verify failed due to: certificate has expired} -returnCodes {1} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.19 {extended-validation} -body { +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} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.20 {hsts} -body { +test BadSSL-1.24 {hsts} -body { badssl hsts.badssl.com } -test BadSSL-1.21 {https-everywhere} -body { +test BadSSL-1.25 {https-everywhere} -body { badssl https-everywhere.badssl.com } -test BadSSL-1.22 {incomplete-chain} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.23 {invalid-expected-sct} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.24 {long-extended-subdomain-name-containing-many-letters-and-dashes} -body { +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.25 {longextendedsubdomainnamewithoutdashesinordertotestwordwrapping} -body { +test BadSSL-1.29 {longextendedsubdomainnamewithoutdashesinordertotestwordwrapping} -body { badssl longextendedsubdomainnamewithoutdashesinordertotestwordwrapping.badssl.com } -test BadSSL-1.26 {mitm-software} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.27 {no-common-name} -body { +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} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.28 {no-sct} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.29 {no-subject} -body { +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} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.30 {null} -body { +test BadSSL-1.34 {null} -body { badssl null.badssl.com } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} -test BadSSL-1.31 {pinning-test} -body { +test BadSSL-1.35 {pinning-test} -body { badssl pinning-test.badssl.com } -test BadSSL-1.32 {preact-cli} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.33 {preloaded-hsts} -body { +test BadSSL-1.37 {preloaded-hsts} -body { badssl preloaded-hsts.badssl.com } -test BadSSL-1.34 {rc4-md5} -body { +test BadSSL-1.38 {rc4-md5} -body { badssl rc4-md5.badssl.com } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} -test BadSSL-1.35 {rc4} -body { +test BadSSL-1.39 {rc4} -body { badssl rc4.badssl.com } -result {handshake failed: sslv3 alert handshake failure} -returnCodes {1} -test BadSSL-1.36 {revoked} -body { +test BadSSL-1.40 {revoked} -body { badssl revoked.badssl.com - } -result {handshake failed: certificate verify failed due to: certificate has expired} -returnCodes {1} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.37 {rsa2048} -body { +test BadSSL-1.41 {rsa2048} -body { badssl rsa2048.badssl.com } -test BadSSL-1.38 {rsa4096} -body { +test BadSSL-1.42 {rsa4096} -body { badssl rsa4096.badssl.com } -test BadSSL-1.39 {rsa8192} -body { +test BadSSL-1.43 {rsa8192} -body { badssl rsa8192.badssl.com } -test BadSSL-1.40 {self-signed} -body { +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} + } -result {handshake failed: certificate verify failed due to "self-signed certificate"} -returnCodes {1} -test BadSSL-1.41 {sha1-2016} -body { +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} + } -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.42 {sha1-2017} -body { +test BadSSL-1.48 {sha1-2017} -constraints {new_api} -body { badssl sha1-2017.badssl.com - } -result {handshake failed: certificate verify failed due to: certificate has expired} -returnCodes {1} + } -result {handshake failed: certificate verify failed due to "CA signature digest algorithm too weak"} -returnCodes {1} -test BadSSL-1.43 {sha1-intermediate} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.44 {sha256} -body { +test BadSSL-1.50 {sha256} -body { badssl sha256.badssl.com } -test BadSSL-1.45 {sha384} -body { +test BadSSL-1.51 {sha384} -body { badssl sha384.badssl.com - } -result {handshake failed: certificate verify failed due to: certificate has expired} -returnCodes {1} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.46 {sha512} -body { +test BadSSL-1.52 {sha512} -body { badssl sha512.badssl.com - } -result {handshake failed: certificate verify failed due to: certificate has expired} -returnCodes {1} + } -result {handshake failed: certificate verify failed due to "certificate has expired"} -returnCodes {1} -test BadSSL-1.47 {static-rsa} -body { +test BadSSL-1.53 {static-rsa} -body { badssl static-rsa.badssl.com } -test BadSSL-1.48 {subdomain.preloaded-hsts} -body { +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} + } -result {handshake failed: certificate verify failed due to "hostname mismatch"} -returnCodes {1} -test BadSSL-1.49 {superfish} -body { +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} + } -result {handshake failed: certificate verify failed due to "unable to get local issuer certificate"} -returnCodes {1} -test BadSSL-1.50 {tls-v1-0:1010} -constraints {tls1} -body { +test BadSSL-1.57 {tls-v1-0:1010} -constraints {tls1 old_api} -body { badssl tls-v1-0.badssl.com:1010 } -test BadSSL-1.51 {tls-v1-1:1011} -constraints {tls1.1} -body { +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.52 {tls-v1-2:1012} -constraints {tls1.2} -body { +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.53 {untrusted-root} -body { +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} + } -result {handshake failed: certificate verify failed due to "self-signed certificate in certificate chain"} -returnCodes {1} -test BadSSL-1.54 {upgrade} -body { +test BadSSL-1.64 {upgrade} -body { badssl upgrade.badssl.com } -test BadSSL-1.55 {webpack-dev-server} -body { +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} + } -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.56 {wrong.host} -body { +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} + } -result {handshake failed: certificate verify failed due to "hostname mismatch"} -returnCodes {1} -test BadSSL-1.57 {mozilla-modern} -body { +test BadSSL-1.68 {mozilla-modern} -body { badssl mozilla-modern.badssl.com } # Cleanup ::tcltest::cleanupTests Index: tests/common.tcl ================================================================== --- tests/common.tcl +++ tests/common.tcl @@ -18,5 +18,11 @@ ::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/digest.csv ================================================================== --- tests/digest.csv +++ tests/digest.csv @@ -1,10 +1,11 @@ # Group,Name,Constraints,Setup,Body,Cleanup,Match,Result,Output,Error Output,Return Codes command,package require tls,,,,,,,,, ,,,,,,,,,, command,# Constraints,,,,,,,,, -command,::tcltest::testConstraint md4 [expr {"md4" in [::tls::digests]}],,,,,,,,, +command,"::tcltest::testConstraint md4 [expr {""md4"" in [::tls::digests]}]",,,,,,,,, +command,catch {tls::provider legacy},,,,,,,,, ,,,,,,,,,, command,# Helper functions - See common.tcl,,,,,,,,, command,proc digest_read_chan {cmd filename args} {;set ch [open $filename rb];set bsize [fconfigure $ch -buffersize];set new [$cmd {*}$args -chan $ch];while {![eof $new]} {set md [read $new $bsize]};close $new;return $md},,,,,,,,, command,proc digest_write_chan {cmd filename data args} {;set ch [open $filename wb];set new [$cmd {*}$args -chan $ch];puts -nonewline $new $data;flush $new;close $new;set ch [open $filename rb];set md [read $ch];close $ch;return $md},,,,,,,,, command,proc digest_accumulate {string args} {;set cmd [{*}$args -command dcmd]; $cmd update [string range $string 0 20];$cmd update [string range $string 21 end];return [$cmd finalize]},$cmd update [string range $string 0 20];$cmd update [string range $string 21 end];return [$cmd finalize]},,,,,,,, Index: tests/digest.test ================================================================== --- tests/digest.test +++ tests/digest.test @@ -10,10 +10,11 @@ package require tls # Constraints ::tcltest::testConstraint md4 [expr {"md4" in [::tls::digests]}] +catch {tls::provider legacy} # Helper functions - See common.tcl proc digest_read_chan {cmd filename args} {;set ch [open $filename rb];set bsize [fconfigure $ch -buffersize];set new [$cmd {*}$args -chan $ch];while {![eof $new]} {set md [read $new $bsize]};close $new;return $md} proc digest_write_chan {cmd filename data args} {;set ch [open $filename wb];set new [$cmd {*}$args -chan $ch];puts -nonewline $new $data;flush $new;close $new;set ch [open $filename rb];set md [read $ch];close $ch;return $md} proc digest_accumulate {string args} {;set cmd [{*}$args -command dcmd]; $cmd update [string range $string 0 20];$cmd update [string range $string 21 end];return [$cmd finalize]}