Files
mars-matrixssl/apps/ssl/client.c
Janne Johansson dbc2786c73 MatrixSSL 4.1.0
2019-04-17 13:34:22 +03:00

2614 lines
87 KiB
C

/**
* @file client.c
* @version $Format:%h%d$
*
* Simple MatrixSSL blocking client example.
*/
/*
* Copyright (c) 2013-2017 INSIDE Secure Corporation
* Copyright (c) PeerSec Networks, 2002-2011
* All Rights Reserved
*
* The latest version of this code is available at http://www.matrixssl.org
*
* This software is open source; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This General Public License does NOT permit incorporating this software
* into proprietary programs. If you are unable to comply with the GPL, a
* commercial license for this software may be purchased from INSIDE at
* http://www.insidesecure.com/
*
* This program is distributed in WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* http://www.gnu.org/copyleft/gpl.html
*/
/******************************************************************************/
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 200112L
#endif
#ifndef NEED_PS_TIME_CONCRETE
# define NEED_PS_TIME_CONCRETE
#endif
#ifndef USE_MULTITHREADING
# define USE_MULTITHREADING
#endif
/* This example uses osdep*.h to allow porting similar to MatrixSSL's libs. */
#include "osdep_stddef.h"
#include "osdep_time.h"
#include "osdep_string.h"
#include "osdep_stdio.h"
#include "osdep_ctype.h"
#include "osdep_sys_socket.h"
#include "osdep-types.h"
#include "app.h"
/* Command line arguments parsing: */
#ifndef WIN32
# define USE_GETOPT_LONG
# ifdef USE_GETOPT_LONG
# include <getopt.h>
# else
# include "osdep_unistd.h"
# endif
#else
# include "XGetopt.h"
#endif
/* The MatrixSSL's API. */
#include "matrixssl/matrixsslApi.h"
#include "core/coreApi.h"
#include "core/psUtil.h"
# include "../common/client_common.h"
# ifdef USE_TLS_1_3
# include "testkeys/PSK/tls13_psk.h"
# endif /* USE_TLS_1_3 */
#ifdef USE_CLIENT_SIDE_SSL
static int g_use_psk;
# ifndef MATRIX_TESTING_ENVIRONMENT /* Omit the message when testing. */
# define WARNING_MESSAGE "DO NOT USE THESE DEFAULT KEYS IN PRODUCTION ENVIRONMENTS."
# define WARNING_MESSAGE_DEFAULT_KEY
# include "pscompilerwarning.h"
# endif
# define ALLOW_ANON_CONNECTIONS 0
# define CRL_MAX_LENGTH 2097152 /* Maximum length for CRL: 2 megabytes. */
/* #define REHANDSHAKE_TEST */
# ifdef REHANDSHAKE_TEST
static int g_rehandshakeFlag = 0;
# endif
/********************************** Globals ***********************************/
static unsigned char g_httpRequestHdr[] = "GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"User-Agent: MatrixSSL/" MATRIXSSL_VERSION "\r\n"
"Accept: */*\r\n"
"Content-Length: 0\r\n"
"\r\n";
static const char g_strver[][8] =
{ "SSL 3.0", "TLS 1.0", "TLS 1.1", "TLS 1.2", "TLS 1.3" };
static psList_t *g_groupList;
static psSize_t g_num_key_shares;
static psList_t *g_sigAlgsList;
static psList_t *g_sigAlgsCertList;
static psList_t *g_supportedVersionsList;
static char g_early_data_file[256];
static long int g_tls13_block_size;
static long int g_min_dh_p_size;
static unsigned char g_matrixShutdownServer[] = "MATRIX_SHUTDOWN";
extern int opterr;
static char g_ip[16];
static char g_server_name[256];
static char g_path[256];
static int g_port, g_new, g_resumed, g_ciphers, g_version, g_closeServer;
static int g_min_version, g_max_version, g_version_range_set;
static int g_disableCertNameChk;
static int g_max_verify_depth;
static uint16_t g_cipher[16];
static int g_trace;
static int g_keepalive;
static int g_req_ocsp_stapling;
static int g_disable_peer_authentication;
static uint32_t g_bytes_requested;
static uint8_t g_send_closure_alert;
static int g_print_http_response;
# ifdef USE_EXT_CLIENT_CERT_KEY_LOADING
static const char *g_on_demand_cert_file = "testkeys/RSA/3072_RSA.pem";
static const char *g_on_demand_key_file = "testkeys/RSA/3072_RSA_KEY.pem";
# endif /* USE_EXT_CLIENT_CERT_KEY_LOADING */
struct g_sslstats
{
int rbytes; /* Bytes read */
int64 hstime;
int64 datatime;
};
/********************************** Defines ***********************************/
/****************************** Local Functions *******************************/
static int32 httpWriteRequest(ssl_t *ssl);
static int32 certCb(ssl_t *ssl, psX509Cert_t *cert, int32 alert);
static SOCKET lsocketConnect(char *ip, int32 port, int32 *err);
static void closeConn(ssl_t *ssl, SOCKET fd);
static int32_t extensionCb(ssl_t *ssl,
uint16_t extType, uint8_t extLen, void *e);
# ifdef USE_TLS_1_3
static int32_t sendEarlyData(ssl_t *ssl);
# endif
# ifdef USE_CRL
static int32 fetchCRL(psPool_t *pool, char *url, uint32_t urlLen,
unsigned char **crlBuf, uint32_t *crlBufLen);
static int32_t fetchParseAndAuthCRLfromCert(psPool_t *pool, psX509Cert_t *cert,
psX509Cert_t *potentialIssuers);
/* Enable the example on how to fetch CRLs mid-handshake. If disabled, the
example will show how to halt the handshake to go out and fetch and retry
the connection (command line option -n must be specified for multiple
connection attempts) */
#define MIDHANDSHAKE_CRL_FETCH
# ifndef MIDHANDSHAKE_CRL_FETCH
/* In the example where we stop the handhsake to go fetch the CRL files, we
need storage to hold the CRL URL distribution points since those are
coming from the server cert chain which we do not keep around */
# define CRL_MAX_SERVER_CERT_CHAIN 3
# define CRL_MAX_URL_LEN 256
static unsigned char g_crlDistURLs[CRL_MAX_SERVER_CERT_CHAIN][CRL_MAX_URL_LEN];
static int32_t fetchParseAndAuthCRLfromUrl(psPool_t *pool, unsigned char *url,
uint32_t urlLen, psX509Cert_t *potentialIssuers);
static void fetchSavedCRL(psX509Cert_t *potentialIssuers);
# endif
# endif /* USE_CRL */
# ifdef USE_EXT_CERTIFICATE_VERIFY_SIGNING
# endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */
static void sslstatsPrintTime(const struct g_sslstats* stats, int conn_count);
static void addTimeDiff(int64 *t, psTime_t t1, psTime_t t2);
/* Not a public function, but needed for command-line backwards
compatibility. */
extern int32_t psVerToFlag(psProtocolVersion_t ver);
/******************************************************************************/
/*
Make a secure HTTP request to a defined IP and port
Connection is made in blocking socket mode
The connection is considered successful if the SSL/TLS session is
negotiated successfully, a request is sent, and a HTTP response is received.
*/
static int g_alreadyopen = 0;
static int32 httpsClientConnection(sslKeys_t *keys, sslSessionId_t *sid,
struct g_sslstats *stats)
{
tlsExtension_t *extension;
int32 rc, transferred, len, extLen;
ssl_t *ssl = NULL;
unsigned char *buf, *ext;
httpConn_t cp;
SOCKET fd;
psTime_t t1, t2;
sslSessOpts_t options;
psList_t *sigAlg;
psList_t *supportedVersion;
# ifdef USE_TLS_1_3
psList_t *group;
uint16_t groups[TLS_1_3_MAX_GROUPS] = {0};
# endif
uint16_t sigAlgs[TLS_MAX_SIGNATURE_ALGORITHMS] = {0};
psProtocolVersion_t supportedVersions[TLS_MAX_SUPPORTED_VERSIONS] = {0};
psSize_t i;
# ifdef USE_ALPN
unsigned char *alpn[MAX_PROTO_EXT];
int32 alpnLen[MAX_PROTO_EXT];
# endif
Memset(&cp, 0x0, sizeof(httpConn_t));
if (g_alreadyopen == 0)
{
fd = lsocketConnect(g_ip, g_port, &rc);
if (g_keepalive == 1)
{
g_alreadyopen = fd;
}
}
else
{
fd = g_alreadyopen;
rc = PS_SUCCESS;
}
if (fd == INVALID_SOCKET || rc != PS_SUCCESS)
{
return PS_PLATFORM_FAIL;
}
Memset(&options, 0x0, sizeof(sslSessOpts_t));
# ifdef USE_OCSP_RESPONSE
if (g_req_ocsp_stapling)
{
options.OCSPstapling = 1;
}
# endif
# ifdef USE_DH
rc = matrixSslSessOptsSetMinDhBits(&options, g_min_dh_p_size);
if (rc != PS_SUCCESS)
{
return rc;
}
# endif
# ifdef USE_TLS_1_3
/* Determine which groups to use. */
if (g_groupList != 0)
{
group = g_groupList;
i = 0;
while (group)
{
groups[i] = psGetNamedGroupId((const char*)group->item);
group = group->next;
i++;
}
rc = matrixSslSessOptsSetKeyExGroups(&options,
groups,
i,
g_num_key_shares);
if (rc < 0)
{
Printf("matrixSslSessOptsSetKeyExGroups failed\n");
goto L_CLOSE_ERR;
}
}
if (g_sigAlgsCertList != 0)
{
sigAlg = g_sigAlgsCertList;
i = 0;
while (sigAlg)
{
sigAlgs[i] = psGetNamedSigAlgId((const char*)sigAlg->item);
sigAlg = sigAlg->next;
i++;
}
rc = matrixSslSessOptsSetSigAlgsCert(&options,
sigAlgs,
i);
if (rc < 0)
{
Printf("matrixSslSessOptsSetSigAlgsCert failed\n");
goto L_CLOSE_ERR;
}
}
# endif /* USE_TLS_1_3 */
if (g_sigAlgsList != 0)
{
sigAlg = g_sigAlgsList;
i = 0;
while (sigAlg)
{
sigAlgs[i] = psGetNamedSigAlgId((const char*)sigAlg->item);
sigAlg = sigAlg->next;
i++;
}
rc = matrixSslSessOptsSetSigAlgs(&options,
sigAlgs,
i);
if (rc < 0)
{
Printf("matrixSslSessOptsSetSigAlgs failed\n");
goto L_CLOSE_ERR;
}
}
/*
Get version information from (in order of priority):
- supported versions list (e.g. --tls-supported-versions 26,3)
- version range option (e.g. --tls-version-range 3,4)
- single version option (e.g. -V26)
*/
if (g_supportedVersionsList)
{
supportedVersion = g_supportedVersionsList;
i = 0;
while (supportedVersion)
{
supportedVersions[i] = atoi((char* )supportedVersion->item);
supportedVersions[i] = matrixSslVersionFromMinorDigit(
supportedVersions[i]);
supportedVersion = supportedVersion->next;
i++;
}
rc = matrixSslSessOptsSetClientTlsVersions(&options,
supportedVersions,
i);
if (rc < 0)
{
Printf("matrixSslSessOptsSetClientTlsVersions failed\n");
goto L_CLOSE_ERR;
}
}
else if (g_version_range_set)
{
rc = matrixSslSessOptsSetClientTlsVersionRange(&options,
g_min_version,
g_max_version);
if (rc < 0)
{
goto L_CLOSE_ERR;
}
}
else if (g_version != 0)
{
options.versionFlag = psVerToFlag(matrixSslVersionFromMinorDigit(
g_version));
}
options.userPtr = keys;
# ifdef USE_EXT_CERTIFICATE_VERIFY_SIGNING
# endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */
# ifdef TEST_KEEP_PEER_CERTS
options.keep_peer_certs = 1;
options.keep_peer_cert_der = 1;
# endif
if (g_max_verify_depth != 0)
options.validateCertsOpts.max_verify_depth = g_max_verify_depth;
matrixSslNewHelloExtension(&extension, NULL);
if (Strlen(g_server_name) == 0)
{
Memcpy(g_server_name, g_ip, Strlen(g_ip));
}
matrixSslCreateSNIext(NULL,
(unsigned char *) g_server_name, (uint32) Strlen(g_server_name),
&ext, &extLen);
matrixSslLoadHelloExtension(extension, ext, extLen, EXT_SNI);
psFree(ext, NULL);
# ifdef USE_ALPN
/* Application Layer Protocol Negotiation */
alpn[0] = (unsigned char *)psMalloc(NULL, Strlen("http/1.0"));
Memcpy(alpn[0], "http/1.0", Strlen("http/1.0"));
alpnLen[0] = Strlen("http/1.0");
alpn[1] = (unsigned char *)psMalloc(NULL, Strlen("http/1.1"));
Memcpy(alpn[1], "http/1.1", Strlen("http/1.1"));
alpnLen[1] = Strlen("http/1.1");
matrixSslCreateALPNext(NULL, 2, alpn, alpnLen, &ext, &extLen);
matrixSslLoadHelloExtension(extension, ext, extLen, EXT_ALPN);
psFree(alpn[0], NULL);
psFree(alpn[1], NULL);
# endif
/* We are passing the IP address of the server as the expected name */
/* To skip certificate subject name tests, pass NULL instead of
g_server_name */
if (g_disableCertNameChk == 0)
{
rc = matrixSslNewClientSession(&ssl, keys, sid, g_cipher, g_ciphers,
certCb, g_server_name, extension, extensionCb, &options);
}
else
{
rc = matrixSslNewClientSession(&ssl, keys, sid, g_cipher, g_ciphers,
certCb, NULL, extension, extensionCb, &options);
}
if (rc != MATRIXSSL_REQUEST_SEND)
{
psTraceInt("New Client Session Failed: %d. Exiting\n", rc);
rc = PS_ARG_FAIL;
goto L_CLOSE_ERR;
}
# ifdef USE_TLS_1_3
/* TLS 1.3 early data sending from file. Early data must be sent right
after the matrixSslNewClientSession call */
if (Strlen(g_early_data_file) > 0 &&
matrixSslGetMaxEarlyData(ssl) > 0)
{
if (sendEarlyData(ssl) < 0)
{
rc = PS_FAILURE;
goto L_CLOSE_ERR;
}
}
if (g_tls13_block_size != 0)
{
rc = matrixSslSetTls13BlockPadding(ssl, g_tls13_block_size);
if (rc != PS_SUCCESS)
{
rc = PS_FAILURE;
goto L_CLOSE_ERR;
}
}
# endif
matrixSslDeleteHelloExtension(extension);
WRITE_MORE:
while ((len = matrixSslGetOutdata(ssl, &buf)) > 0)
{
if (g_trace)
{
psTraceBytes("SEND", buf, len);
}
transferred = send(fd, buf, len, 0);
if (transferred <= 0)
{
Printf("Error sending\n");
goto L_CLOSE_ERR;
}
else
{
psTraceBytes("sent", buf, len);
/* Indicate that we've written > 0 bytes of data */
if ((rc = matrixSslSentData(ssl, transferred)) < 0)
{
goto L_CLOSE_ERR;
}
if (rc == MATRIXSSL_REQUEST_CLOSE)
{
closeConn(ssl, fd);
return MATRIXSSL_SUCCESS;
}
if (rc == MATRIXSSL_HANDSHAKE_COMPLETE)
{
Printf("TLS handshake complete.\n");
if ((matrixSslGetNegotiatedVersion(ssl) & v_tls_1_3_any)
&& sid != NULL && g_resumed > 0)
{
/* Try to receive the server's NewSessionTicket. */
goto READ_MORE;
}
/* If we sent the Finished SSL message, initiate the HTTP req */
/* (This occurs on a resumption handshake) */
if ((rc = httpWriteRequest(ssl)) < 0)
{
goto L_CLOSE_ERR;
}
if (rc == MATRIXSSL_REQUEST_SEND)
{
/* We have a HTTP request to send */
goto WRITE_MORE;
}
closeConn(ssl, fd);
return MATRIXSSL_SUCCESS;
}
/* MATRIXSSL_REQUEST_SEND is handled by loop logic */
}
}
READ_MORE:
if ((len = matrixSslGetReadbuf(ssl, &buf)) <= 0)
{
goto L_CLOSE_ERR;
}
if ((transferred = recv(fd, buf, len, 0)) < 0)
{
goto L_CLOSE_ERR;
}
if (g_trace)
{
//psTraceBytes("RECV", buf, transferred);
}
/* If EOF, remote socket closed. But we haven't received the HTTP response
so we consider it an error in the case of an HTTP client */
if (transferred == 0)
{
goto L_CLOSE_ERR;
}
psGetTime(&t1, NULL);
if ((rc = matrixSslReceivedData(ssl, (int32) transferred, &buf,
(uint32 *) &len)) < 0)
{
psGetTime(&t2, NULL);
# ifdef USE_EXT_CLIENT_CERT_KEY_LOADING
if (rc == PS_PENDING && matrixSslNeedClientCert(ssl))
{
sslKeys_t *newKeys;
psTrace("Loading client cert and key in response to " \
"CertificateRequest\n");
/* Clear previously set identities from the ssl->keys before
loading new ones.
On a typical application the 'identity' keys would not be
shared with initial NewClient call, and the ps-pending
driven/callback driven mechanisms */
matrixSslDeleteKeys(matrixSslGetKeys(ssl));
/* Load the "on-demand" keys. */
rc = matrixSslNewKeys(&newKeys, NULL);
if (rc < 0)
{
psTrace("matrixSslNewKeys failed\n");
exit(EXIT_FAILURE);
}
rc = matrixSslLoadKeys(newKeys,
g_on_demand_cert_file,
g_on_demand_key_file,
NULL, NULL, NULL);
if (rc < 0)
{
psTrace("matrixSslLoadKeys failed\n");
exit(EXIT_FAILURE);
}
matrixSslSetClientIdentity(ssl, newKeys);
(void)matrixSslClientCertUpdated(ssl);
/* Retry now that we have the cert and the priv key. */
rc = matrixSslReceivedData(ssl, (int32) transferred, &buf,
(uint32 *) &len);
if (rc < 0)
{
psTrace("Retry failed\n");
}
goto WRITE_MORE;
}
# endif
# ifdef USE_EXT_CERTIFICATE_VERIFY_SIGNING
# endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */
if (matrixSslHandshakeIsComplete(ssl))
{
addTimeDiff(&stats->datatime, t1, t2);
}
else
{
addTimeDiff(&stats->hstime, t1, t2);
}
goto L_CLOSE_ERR;
}
psGetTime(&t2, NULL);
if (matrixSslHandshakeIsComplete(ssl))
{
addTimeDiff(&stats->datatime, t1, t2);
}
else
{
addTimeDiff(&stats->hstime, t1, t2);
}
PROCESS_MORE:
switch (rc)
{
case MATRIXSSL_HANDSHAKE_COMPLETE:
# ifdef REHANDSHAKE_TEST
/*
Test rehandshake capabilities of server. A full re-handshake
is first tested. After that, a session resmption rehandshake
is attempted. In that case, this client will be last to
send handshake data and MATRIXSSL_HANDSHAKE_COMPLETE will hit on
the WRITE_MORE handler and httpWriteRequest will occur there.
NOTE: If the server doesn't support session resumption it is
possible to fall into an endless rehandshake loop
*/
if (g_rehandshakeFlag == 0)
{
/* Full rehandshake */
if (matrixSslEncodeRehandshake(ssl, NULL, NULL,
SSL_OPTION_FULL_HANDSHAKE, g_cipher, g_ciphers) < 0)
{
goto L_CLOSE_ERR;
}
g_rehandshakeFlag = 1;
}
else if (g_rehandshakeFlag == 1)
{
/* Resumed rehandshake */
if (matrixSslEncodeRehandshake(ssl, NULL, NULL, 0,
g_cipher, g_ciphers) < 0)
{
goto L_CLOSE_ERR;
}
g_rehandshakeFlag = 2;
}
else
{
if ((rc = httpWriteRequest(ssl)) < 0)
{
goto L_CLOSE_ERR;
}
if (rc != MATRIXSSL_REQUEST_SEND)
{
closeConn(ssl, fd);
return MATRIXSSL_SUCCESS;
}
}
goto WRITE_MORE;
# else
Printf("TLS handshake complete.\n");
/* We got the Finished SSL message, initiate the HTTP req */
if ((rc = httpWriteRequest(ssl)) < 0)
{
goto L_CLOSE_ERR;
}
if (rc == MATRIXSSL_REQUEST_SEND)
{
/* We have a HTTP request to send */
goto WRITE_MORE;
}
# ifdef TEST_KEEP_PEER_CERTS
if (ssl->sec.cert == NULL)
{
Printf("Error: peer cert not kept\n");
return MATRIXSSL_ERROR;
}
else
{
Printf("OK: peer cert still available\n");
}
if (ssl->sec.cert->unparsedBin == NULL ||
ssl->sec.cert->binLen <= 0)
{
Printf("Error: peer cert DER not kept\n");
return MATRIXSSL_ERROR;
}
else
{
Printf("OK: peer cert DER still available\n");
}
# endif
closeConn(ssl, fd);
return MATRIXSSL_SUCCESS;
# endif
case MATRIXSSL_APP_DATA:
case MATRIXSSL_APP_DATA_COMPRESSED:
if (g_trace)
{
psTraceBytes("Decrypted app data", buf, len);
}
if (cp.flags != HTTPS_COMPLETE)
{
rc = httpBasicParse(&cp, buf, len, g_trace);
if (rc < 0)
{
closeConn(ssl, fd);
if (cp.parsebuf)
{
Free(cp.parsebuf);
}
cp.parsebuf = NULL;
cp.parsebuflen = 0;
return MATRIXSSL_ERROR;
}
if (rc == HTTPS_COMPLETE)
{
cp.flags = HTTPS_COMPLETE;
}
}
cp.bytes_received += len;
stats->rbytes += len;
if (g_trace)
{
psTraceBytes("HTTP DATA", buf, len);
if (g_print_http_response)
{
char *resp_str = (char *)psMalloc(NULL, len+1);
if (resp_str != NULL)
{
psMem2Str(resp_str, buf, len);
resp_str[len] = '\0';
psTraceStr("%s", resp_str);
Free(resp_str);
}
else
{
/* Memory allocation failure. Skip trace. */
psTraceInt("HTTP RESPONSE: %d bytes of data.\n"
"(Printing omitted due to memory allocation "
"failure)\n", (int)len);
}
}
}
rc = matrixSslProcessedData(ssl, &buf, (uint32 *) &len);
if (rc < 0)
{
goto L_CLOSE_ERR;
}
if (g_bytes_requested > 0)
{
if (cp.bytes_received >= g_bytes_requested)
{
/* We've received all that was requested, so close */
closeConn(ssl, fd);
if (cp.parsebuf)
{
Free(cp.parsebuf);
}
cp.parsebuf = NULL;
cp.parsebuflen = 0;
return MATRIXSSL_SUCCESS;
}
if (rc == 0)
{
/* We processed a partial HTTP message */
goto READ_MORE;
}
}
goto PROCESS_MORE;
case MATRIXSSL_REQUEST_SEND:
goto WRITE_MORE;
case MATRIXSSL_REQUEST_RECV:
goto READ_MORE;
case MATRIXSSL_RECEIVED_ALERT:
/* The first byte of the buffer is the level */
/* The second byte is the description */
if (*buf == SSL_ALERT_LEVEL_FATAL)
{
psTraceInt("Fatal alert: %d, closing connection.\n",
*(buf + 1));
goto L_CLOSE_ERR;
}
/* Closure alert is normal (and best) way to close */
if (*(buf + 1) == SSL_ALERT_CLOSE_NOTIFY)
{
closeConn(ssl, fd);
if (cp.parsebuf)
{
Free(cp.parsebuf);
}
cp.parsebuf = NULL;
cp.parsebuflen = 0;
return MATRIXSSL_SUCCESS;
}
psTraceInt("Warning alert: %d\n", *(buf + 1));
if ((rc = matrixSslProcessedData(ssl, &buf, (uint32 *) &len)) == 0)
{
/* No more data in buffer. Might as well read for more. */
goto READ_MORE;
}
goto PROCESS_MORE;
default:
/* If rc <= 0 we fall here */
goto L_CLOSE_ERR;
}
L_CLOSE_ERR:
if (cp.flags != HTTPS_COMPLETE)
{
psTrace("FAIL: No HTTP Response\n");
}
matrixSslDeleteSession(ssl);
if (g_keepalive == 0)
{
close(fd);
}
if (cp.parsebuf)
{
Free(cp.parsebuf);
}
cp.parsebuf = NULL;
cp.parsebuflen = 0;
return rc;
}
/******************************************************************************/
/*
Create an HTTP request and encode it to the SSL buffer
*/
static int32 httpWriteRequest(ssl_t *ssl)
{
unsigned char *buf;
int32 available, requested;
/* If we don't have a path defined and are sending zero bytes, skip http */
if (g_bytes_requested == 0 && *g_path == '\0')
{
return PS_SUCCESS;
}
if (g_closeServer)
{
/* A value of 0 to the 'new' connections is the key to sending the
server a shutdown message */
requested = Strlen((char *) g_matrixShutdownServer) + 1;
if ((available = matrixSslGetWritebuf(ssl, &buf, requested)) < 0)
{
return PS_MEM_FAIL;
}
if (available < requested)
{
return PS_FAILURE;
}
Memset(buf, 0x0, requested); /* So strlen will work below */
Strncpy((char *) buf, (char *) g_matrixShutdownServer,
(uint32) Strlen((char *) g_matrixShutdownServer));
if (matrixSslEncodeWritebuf(ssl, (uint32) Strlen((char *) buf)) < 0)
{
return PS_MEM_FAIL;
}
return MATRIXSSL_REQUEST_SEND;
}
requested = Strlen((char *) g_httpRequestHdr) + Strlen(g_path) + Strlen(g_server_name) + 1;
if ((available = matrixSslGetWritebuf(ssl, &buf, requested)) < 0)
{
return PS_MEM_FAIL;
}
requested = PS_MIN(requested, available);
Snprintf((char *) buf, requested, (char *) g_httpRequestHdr, g_path, g_server_name);
if (g_trace)
{
//psTraceStr("SEND: [%s]\n", (char *) buf);
}
if (matrixSslEncodeWritebuf(ssl, Strlen((char *) buf)) < 0)
{
return PS_MEM_FAIL;
}
return MATRIXSSL_REQUEST_SEND;
}
static void usage(void)
{
Printf(
"\nusage: client { options }\n"
"\n"
"Options can be one or more of the following:\n"
"\n"
"-a - Disable sending closure alerts\n"
"--no-alerts\n"
"-b <numBytesPerRequest> - Client request size\n"
"--request-bytes <numBytesPerRequest>\n"
" Generates an HTTPS request after TLS negotiation\n"
" Uses URL path of '/bytes?<numBytesPerRequest>'\n"
" Mutually exclusive with '-u' flag\n"
"-c <cipherList> - Comma separated list of ciphers numbers\n"
"--ciphers <cipherList\n"
" - Example cipher numbers:\n"
" - '53' TLS_RSA_WITH_AES_256_CBC_SHA\n"
" - '47' TLS_RSA_WITH_AES_128_CBC_SHA\n"
" - '10' SSL_RSA_WITH_3DES_EDE_CBC_SHA\n"
" - '5' SSL_RSA_WITH_RC4_128_SHA\n"
" - '4' SSL_RSA_WITH_RC4_128_MD5\n"
"-C <caFile> - Path to certificate authority file\n"
"--ca <caFile>\n"
"-d - Disable server certicate name/addr chk\n"
"--no-name-check\n"
"-e <useExternalVerify> - Enable/disable external certificate verification\n"
"--external-verify <useExternalVerify\n"
" 0 (turn it OFF, default)\n"
" 1 (turn it ON)\n"
"--groups - (TLS 1.3 only) Set supported key exchange groups.\n"
" The argument must be a colon-separated list of TLS 1.3\n"
" NamedGroup names, e.g. --groups secp256r1:secp384r1:ffdhe2048\n"
"-h - Help, print usage and exit\n"
"--help\n"
"-k <keyLen> - RSA keyLen (if using client auth)\n"
"--rsa-key-len\n"
" - Must be one of 1024, 2048 or 4096\n"
"-K - Keepalive (Re-use socket after TLS session close)\n"
"--keep-alive\n"
"-n <numNewSessions> - Num of new (full handshake) sessions\n"
"--handshakes <numNewSessions>\n"
" - Default 1\n"
"-m <maxVerifyDepth> - Maximum depth for certificate verification\n"
"--depth <maxVerifyDepth>\n"
"--num-key-shares - (TLS 1.3 only) Number of key shares to include in ClientHello.\n"
"-p <serverPortNum> - Port number for SSL/TLS server\n"
"--port <serverPortNum>\n"
" - Default 4433 (HTTPS is 443)\n"
"-r <numResumedSessions> - Num of resumed SSL/TLS sesssions\n"
"--resumed <numResumedSessions>\n"
" - Default 0\n"
"-s <serverIpAddress> - IP address of server machine/interface\n"
"--server <serverIpAddress>\n"
" - Default 127.0.0.1 (localhost)\n"
"--server-name <name> - The server name to send in the SNI extension\n"
"-t - Enable printing of HTTP response\n"
"--response\n"
"-u <url path> - URL path, eg. '/index.html'\n"
"--url <url path>\n"
" Generates an HTTPS request after TLS negotiation\n"
" Mutually exclusive with '-b' flag\n"
"-V <tlsVersion> - SSL/TLS version to use\n"
"--tls <tlsVersion> - Selects a single TLS version to support\n"
" '0' SSL 3.0\n"
" '1' TLS 1.0\n"
" '2' TLS 1.1\n"
" '3' TLS 1.2\n"
" '4' TLS 1.3 (default)\n"
" '22' TLS 1.3 draft 22\n"
" '23' TLS 1.3 draft 23\n"
" '24' TLS 1.3 draft 24\n"
"--tls-version-range <minVersion>,<maxVersion>\n"
" - Set TLS version range, e.g.\n"
" 2,3 for TLS 1.1 - TLS 1.2\n"
" - Note: Only one of the version parameters should be supplied\n"
" (--tls, --tls-version-range or--tls-supported-versions)\n"
"--tls-supported-versions <version>,<version>,...\n"
" - Lists the supported versions in priority order, e.g.\n"
" 23,3,2 for TLS 1.3 draft 23, TLS 1.2 and TLS 1.1\n"
" - Priority order is used only when TLS1.3 is enabled. Otherwise\n"
" the latest version has the highest priority.\n"
" - Note: Only one of the version parameters should be supplied\n"
" (--tls, --tls-version-range or --tls-supported-versions)\n"
"--no-cert - Unset client certificate\n"
"--cert <certificateFile>\n"
" - Path to client certificate file\n"
"--key <privateKeyFile> - Path to client private key file\n"
"--keytype <loadKeyMethod>\n"
" - Specify format of client certificate:\n"
" any (detect key and signature type)\n"
" rsa (for RSA keys)\n"
" ec (for EC keys ECDSA signature)\n"
" ecrsa (for EC keys with RSA signature)\n"
"--psk \n"
" - Load test PSKs.\n"
"--groups <groups>\n"
" - Supported groups.\n"
" For example: secp256r1:secp384r1\n"
"--num-key-shares <numKeyShares>\n"
" - For how many groups (listed by --groups)\n"
" key share entries are generated in ClientHello.\n"
"--sig-algs <sigAlgs>\n"
" - Supported signature algorithms.\n"
" For example: ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256\n"
"--sig-algs-cert <sigAlgsCert>\n"
" - Supported signature algorithms in TLS1.3 certs.\n"
" For example: ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256\n"
"--early-data <file>\n"
" - Send supplied file using TLS1.3 early data mechanism.\n"
" - Enable also -r or --resumed\n"
" For example: --early-data file.dat -r1\n"
" - Maximum of 16384 bytes is sent from the file.\n"
"--tls13-block-size\n"
" - Block size to pad TLS 1.3 records to.\n"
"\n");
}
/* Returns number of cipher numbers found, or -1 if an error. */
# include "osdep_ctype.h"
static int32_t parse_cipher_list(char *cipherListString,
psCipher16_t cipher_array[], uint8_t size_of_cipher_array)
{
uint32 numCiphers, cipher;
char *endPtr;
/* Convert the cipherListString into an array of cipher numbers. */
numCiphers = 0;
while (cipherListString != NULL)
{
cipher = Strtol(cipherListString, &endPtr, 10);
if (endPtr == cipherListString)
{
Printf("The remaining cipherList has no cipher numbers - '%s'\n",
cipherListString);
return -1;
}
else if (size_of_cipher_array <= numCiphers)
{
Printf("Too many cipher numbers supplied. limit is %d\n",
size_of_cipher_array);
return -1;
}
cipher_array[numCiphers++] = cipher;
while (*endPtr != '\0' && !Isdigit(*endPtr))
{
endPtr++;
}
cipherListString = endPtr;
if (*endPtr == '\0')
{
break;
}
}
return numCiphers;
}
/* Return 0 on good set of cmd options, return -1 if a bad cmd option is
encountered OR a request for help is seen (i.e. '-h' option). */
static int32 process_cmd_options(int32 argc, char **argv)
{
int optionChar, key_len, version, numCiphers;
char *cipherListString;
psList_t *versionRangeList;
/* Set some default options: */
Memset(g_cipher, 0, sizeof(g_cipher));
Memset(g_ip, 0, sizeof(g_ip));
Memset(g_path, 0, sizeof(g_path));
Strcpy(g_ip, "127.0.0.1");
g_bytes_requested = 0;
g_send_closure_alert = 1;
g_ciphers = 0;
g_cipher[0] = 0;
g_disableCertNameChk = 0;
g_key_len = 2048;
g_new = 1;
g_port = 4433;
g_resumed = 0;
g_version = 0;
g_keepalive = 0;
opterr = 0;
const char *optstring = "ab:C:c:de:hk:Km:n:p:r:s:tu:V:";
#ifdef USE_GETOPT_LONG
#define ARG_NO_CERT 1
#define ARG_CERT 2
#define ARG_KEY 3
#define ARG_KEYTYPE 4
#define ARG_ON_DEMAND_CERT 5
#define ARG_ON_DEMAND_KEY 6
#define ARG_TLS_VERSION_RANGE 7
#define ARG_GROUPS 8
#define ARG_NUM_KEY_SHARES 9
#define ARG_SIG_ALGS 10
#define ARG_SIG_ALGS_CERT 11
#define ARG_TLS_SUPPORTED_VERSIONS 12
#define ARG_SERVER_NAME 13
#define ARG_PSK 14
#define ARG_EARLY_DATA 15
#define ARG_REQ_OCSP_STAPLING 16
#define ARG_DISABLE_PEER_AUTHENTICATION 17
#define ARG_TLS13_BLOCK_SIZE 18
#define ARG_MIN_DH_P_SIZE 19
static struct option long_options[] =
{
{"no-alerts", no_argument, NULL, 'a'},
{"request-bytes", required_argument, NULL, 'b'},
{"ciphers", required_argument, NULL, 'c'},
{"ca", required_argument, NULL, 'C'},
{"no-name-check", no_argument, NULL, 'd'},
{"external-verify", required_argument, NULL, 'e'},
{"help", no_argument, NULL, 'h'},
{"rsa-key-len", required_argument, NULL, 'k'},
{"keep-alive", no_argument, NULL, 'K'},
{"handshakes", required_argument, NULL, 'n'},
{"depth", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"resumed", required_argument, NULL, 'r'},
{"server", required_argument, NULL, 's'},
{"server-name", required_argument, NULL, ARG_SERVER_NAME},
{"response", no_argument, NULL, 't'},
{"url", required_argument, NULL, 'u'},
{"tls", required_argument, NULL, 'V'},
{"tls-version-range", required_argument, NULL, ARG_TLS_VERSION_RANGE},
{"tls-supported-versions", required_argument, NULL, ARG_TLS_SUPPORTED_VERSIONS},
{"no-cert", no_argument, NULL, ARG_NO_CERT},
{"cert", required_argument, NULL, ARG_CERT},
{"key", required_argument, NULL, ARG_KEY},
{"on-demand-cert", required_argument, NULL, ARG_ON_DEMAND_CERT},
{"on-demand-key", required_argument, NULL, ARG_ON_DEMAND_KEY},
{"keytype", required_argument, NULL, ARG_KEYTYPE},
{"groups", required_argument, NULL, ARG_GROUPS},
{"num-key-shares", required_argument, NULL, ARG_NUM_KEY_SHARES},
{"sig-algs", required_argument, NULL, ARG_SIG_ALGS},
{"sig-algs-cert", required_argument, NULL, ARG_SIG_ALGS_CERT},
{"psk", no_argument, NULL, ARG_PSK},
{"early-data", required_argument, NULL, ARG_EARLY_DATA},
{"tls13-block-size", required_argument, NULL, ARG_TLS13_BLOCK_SIZE},
{"req-ocsp-stapling", no_argument, NULL, ARG_REQ_OCSP_STAPLING},
{"disable-peer-authentication", no_argument, NULL, ARG_DISABLE_PEER_AUTHENTICATION},
{"min-dh-p-size", required_argument, NULL, ARG_MIN_DH_P_SIZE},
{0, 0, 0, 0}
};
int opt_index = 0;
while ((optionChar = getopt_long(argc, argv, optstring, long_options, &opt_index)) != -1)
#else
while ((optionChar = getopt(argc, argv, optstring)) != -1)
#endif
{
switch (optionChar)
{
case 'h':
return -1;
case 'a':
g_send_closure_alert = 0;
break;
case 'b':
if (*g_path)
{
Printf("-b and -u options cannot both be provided\n");
return -1;
}
g_bytes_requested = atoi(optarg);
Snprintf(g_path, sizeof(g_path), "/bytes?%u", g_bytes_requested);
break;
case 'C':
g_clientconfig.ca_file = optarg;
clientconfigUseFileKeys();
break;
case 'c':
/* Convert the cipherListString into an array of cipher numbers. */
cipherListString = optarg;
numCiphers = parse_cipher_list(cipherListString, g_cipher, 16);
if (numCiphers <= 0)
{
return -1;
}
g_ciphers = numCiphers;
break;
case 'd':
g_disableCertNameChk = 1;
break;
case 'e':
Printf("-e option only supported when USE_EXT_CERTIFICATE_VERIFY_SIGNING " \
"and USE_EXT_EXAMPLE_MODULE are defined\n");
return -1;
case 'k':
key_len = atoi(optarg);
if ((key_len != 1024) && (key_len != 2048)
&& (key_len != 3072) && (key_len != 4096))
{
Printf("-k option must be followed by a key_len whose value "
" must be 1024, 2048, 3072 or 4096\n");
return -1;
}
g_key_len = key_len;
break;
case 'K':
g_keepalive = 1;
break;
case 'm':
g_max_verify_depth = atoi(optarg);
break;
case 'n':
g_new = atoi(optarg);
break;
case 'p':
g_port = atoi(optarg);
break;
case 't':
g_print_http_response = 1;
break;
case 'r':
g_resumed = atoi(optarg);
break;
case 's':
Strncpy(g_ip, optarg, 15);
break;
case 'u':
if (*g_path)
{
Printf("-b and -u options cannot both be provided\n");
return -1;
}
Strncpy(g_path, optarg, sizeof(g_path) - 1);
g_bytes_requested = 0;
break;
case 'V':
/* Single version. */
version = atoi(optarg);
if (!matrixSslTlsVersionRangeSupported(
matrixSslVersionFromMinorDigit(version),
matrixSslVersionFromMinorDigit(version)))
{
Printf("Invalid version: %d\n", version);
return -1;
}
g_version = version;
break;
#ifdef USE_GETOPT_LONG
/* Additional options not supported through short arguments */
case ARG_CERT:
g_clientconfig.cert_file = optarg;
clientconfigUseFileKeys();
break;
case ARG_NO_CERT:
g_clientconfig.cert_file = NULL;
g_clientconfig.privkey_file = optarg;
clientconfigUseFileKeys();
break;
case ARG_KEY:
g_clientconfig.privkey_file = optarg;
clientconfigUseFileKeys();
break;
case ARG_KEYTYPE:
if (Strcmp("any", optarg) == 0) {
g_clientconfig.load_key = &loadKeysFromFile;
} else if (Strcmp("rsa", optarg) == 0) {
g_clientconfig.load_key = &loadRsaKeysFromFile;
} else if (Strcmp("ec", optarg) == 0) {
g_clientconfig.load_key = &loadECDH_ECDSAKeysFromFile;
} else if (Strcmp("ecrsa", optarg) == 0) {
g_clientconfig.load_key = &loadECDHRsaKeysFromFile;
} else {
Printf("Invalid option: %s\n", optarg);
return -1;
}
g_clientconfig.loadKeysFromMemory = 0;
break;
case ARG_TLS_VERSION_RANGE:
{
if (psParseList(NULL, optarg, ',', &versionRangeList) < 0)
{
Printf("Invalid version range string: %s\n",
optarg);
return -1;
}
g_min_version = matrixSslVersionFromMinorDigit(
atoi((char *)versionRangeList->item));
g_max_version = matrixSslVersionFromMinorDigit(
atoi((char *)versionRangeList->next->item));
psFreeList(versionRangeList, NULL);
if (!matrixSslTlsVersionRangeSupported(
g_min_version,
g_max_version))
{
Printf("Unsupported version range: %s\n",
optarg);
return -1;
}
g_version_range_set = 1;
}
break;
case ARG_ON_DEMAND_CERT:
# ifdef USE_EXT_CLIENT_CERT_KEY_LOADING
g_on_demand_cert_file = optarg;
# else
Printf("Please enable USE_EXT_CLIENT_CERT_KEY_LOADING " \
"in matrixsslConfig.h for --on-demand-cert\n");
# endif
break;
case ARG_ON_DEMAND_KEY:
# ifdef USE_EXT_CLIENT_CERT_KEY_LOADING
g_on_demand_key_file = optarg;
# else
Printf("Please enable USE_EXT_CLIENT_CERT_KEY_LOADING " \
"in matrixsslConfig.h for --on-demand-key\n");
# endif
break;
case ARG_GROUPS:
if (psParseList(NULL, optarg, ':', &g_groupList) < 0)
{
Printf("Invalid group list: %s\n", optarg);
}
break;
case ARG_NUM_KEY_SHARES:
g_num_key_shares = atoi(optarg);
if (g_num_key_shares > 10)
{
Printf("Invalid number of key shares: %hu\n", g_num_key_shares);
}
break;
case ARG_SIG_ALGS:
if (psParseList(NULL, optarg, ':', &g_sigAlgsList) < 0)
{
Printf("Invalid sig_alg list: %s\n", optarg);
}
break;
case ARG_SIG_ALGS_CERT:
if (psParseList(NULL, optarg, ':', &g_sigAlgsCertList) < 0)
{
Printf("Invalid sig_alg_cert list: %s\n", optarg);
}
break;
case ARG_TLS_SUPPORTED_VERSIONS:
if (psParseList(NULL, optarg, ',', &g_supportedVersionsList) < 0)
{
Printf("Invalid tls-supported-versions list: %s\n", optarg);
}
break;
case ARG_SERVER_NAME:
if (Strlen(optarg) > sizeof(g_server_name) - 1)
{
Printf("Server name argument too long\n");
exit(EXIT_FAILURE);
}
Strncpy(g_server_name, optarg, sizeof(g_server_name) - 1);
break;
case ARG_PSK:
g_clientconfig.loadPreSharedKeys = 1;
g_use_psk = 1;
break;
case ARG_EARLY_DATA:
Strcpy(g_early_data_file, optarg);
break;
case ARG_TLS13_BLOCK_SIZE:
{
char *end;
g_tls13_block_size = Strtol(optarg, &end, 10);
if (end == optarg
|| g_tls13_block_size < 0
|| g_tls13_block_size > TLS_1_3_MAX_INNER_PLAINTEXT_LEN)
{
Printf("Invalid tls13-block-size argument\n");
exit(EXIT_FAILURE);
}
Printf("Using TLS 1.3 block size %ld\n",
g_tls13_block_size);
}
break;
case ARG_REQ_OCSP_STAPLING:
g_req_ocsp_stapling = 1;
break;
case ARG_DISABLE_PEER_AUTHENTICATION:
g_disable_peer_authentication = 1;
break;
case ARG_MIN_DH_P_SIZE:
{
char *end;
g_min_dh_p_size = Strtol(optarg, &end, 10);
if (end == optarg
|| g_min_dh_p_size <= 0)
{
Printf("Invaling min-dh-p argument\n");
exit(EXIT_FAILURE);
}
if (g_min_dh_p_size < MIN_DH_BITS)
{
Printf("Invalid min-dh-p argument: " \
"must be <= MIN_DH_BITS (= %u in this build)\n",
(unsigned int)MIN_DH_BITS);
exit(EXIT_FAILURE);
}
}
break;
#endif /* USE_GETOPT_LONG */
}
}
return 0;
}
static void sslstatsPrintTime(const struct g_sslstats* stats, int conn_count)
{
# ifdef USE_HIGHRES_TIME
Printf("%d usec (%d avg usec/conn SSL handshake overhead)\n",
(int) stats->hstime, (int) (stats->hstime / conn_count));
Printf("%d usec (%d avg usec/conn SSL data overhead)\n",
(int) stats->datatime, (int) (stats->datatime / conn_count));
# else
Printf("%d msec (%d avg msec/conn SSL handshake overhead)\n",
(int) stats->hstime, (int) (stats->hstime / conn_count));
Printf("%d msec (%d avg msec/conn SSL data overhead)\n",
(int) stats->datatime, (int) (stats->datatime / conn_count));
# endif
}
static void addTimeDiff(int64 *t, psTime_t t1, psTime_t t2)
{
# ifdef USE_HIGHRES_TIME
*t += psDiffUsecs(t1, t2);
# else
*t += psDiffMsecs(t1, t2, NULL);
# endif
}
/******************************************************************************/
/*
Main routine. Initialize SSL keys and structures, and make two SSL
connections, the first with a blank session Id, and the second with
a session ID populated during the first connection to do a much faster
session resumption connection the second time.
*/
int32 main(int32 argc, char **argv)
{
int32 rc, i, exit_code;
sslKeys_t *keys;
sslSessionId_t *sid = NULL;
struct g_sslstats stats;
# ifdef WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(1, 1), &wsaData);
# endif
exit_code = 0;
clientconfigInitialize();
if ((rc = matrixSslOpen()) < 0)
{
psTrace("MatrixSSL library init failure. Exiting\n");
return EXIT_FAILURE;
}
if (0 != process_cmd_options(argc, argv))
{
usage();
clientconfigFree();
return 0;
}
if (matrixSslNewKeys(&keys, NULL) < 0)
{
psTrace("MatrixSSL library key init failure. Exiting\n");
return EXIT_FAILURE;
}
if (g_new <= 1 && g_resumed <= 1)
{
g_trace = 1;
}
else
{
g_trace = 0;
}
if (g_bytes_requested == 0 && *g_path == '\0')
{
Printf("client %s:%d "
"new:%d resumed:%d keylen:%d nciphers:%d version:%d\n",
g_ip, g_port, g_new, g_resumed, g_key_len,
g_ciphers, g_version);
}
else
{
Printf("client https://%s:%d%s "
"new:%d resumed:%d keylen:%d nciphers:%d version:%d\n",
g_ip, g_port, g_path, g_new, g_resumed, g_key_len,
g_ciphers, g_version);
}
if (!clientconfigLoadKeys(keys))
{
return EXIT_FAILURE;
}
# ifdef USE_TLS_1_3
if (g_use_psk)
{
/* Load the TLS 1.3 test PSK. */
rc = matrixSslLoadTls13Psk(keys,
g_tls13_test_psk_256,
sizeof(g_tls13_test_psk_256),
g_tls13_test_psk_id_sha256,
sizeof(g_tls13_test_psk_id_sha256),
NULL);
if (rc < 0)
{
psTrace("Unable to load PSK keys.\n");
return rc;
}
}
# endif
# ifdef USE_CRL
/* One initialization step that can be taken is to run through the CA
files and see if any CRL URL distribution points are present.
Fetch the CRL and load into the cache if found */
fetchParseAndAuthCRLfromCert(NULL,
sslKeysGetCACerts(keys),
sslKeysGetCACerts(keys));
# endif
Memset(&stats, 0x0, sizeof(struct g_sslstats));
Printf("=== %d new connections ===\n", g_new);
if (g_new == 0)
{
/* Special case where client is being used to remotely shut down
the server for automated tests */
g_closeServer = 1;
g_bytes_requested = 0; /* Disable data exchange in this case */
g_new = 1;
}
for (i = 0; i < g_new; i++)
{
matrixSslNewSessionId(&sid, NULL);
# ifdef USE_CRL
# ifndef MIDHANDSHAKE_CRL_FETCH
/* This is part of the example for an application that has chosen to
fail a handshake if the CRL was not available during the first
attempted connection. In this case, the CRL URL distribution points
have been saved aside in g_crlDistURLs and now we will go out and
fetch those CRLs and load them into the library cache so they
will be available on this next connection attempt. */
if (g_crlDistURLs[0][0] == 'h') /* assumption is "http" */
{
fetchSavedCRL(sslKeysGetCACerts(keys));
}
# endif
# endif
rc = httpsClientConnection(keys, sid, &stats);
if (rc < 0)
{
Printf("F %d/%d\n", i, g_new);
exit_code = EXIT_FAILURE;
goto out;
}
else
{
Printf("N"); Fflush(stdout);
}
/* Leave the final sessionID for resumed connections */
if (i + 1 < g_new)
{
matrixSslDeleteSessionId(sid);
}
}
Printf("\n");
if (g_bytes_requested > 0)
{
psAssert(g_bytes_requested * g_new == stats.rbytes);
}
Printf("%d bytes received\n", stats.rbytes);
sslstatsPrintTime(&stats, g_new);
Memset(&stats, 0x0, sizeof(struct g_sslstats));
Printf("=== %d resumed connections ===\n", g_resumed);
for (i = 0; i < g_resumed; i++)
{
rc = httpsClientConnection(keys, sid, &stats);
if (rc < 0)
{
Printf("f %d/%d\n", i, g_resumed);
exit_code = EXIT_FAILURE;
goto out;
}
else
{
Printf("Resumed session.\n");
Printf("R"); Fflush(stdout);
}
}
if (g_keepalive)
{
Printf("Closing socket\n");
close(g_keepalive);
g_keepalive = 0;
}
if (g_resumed)
{
if (g_bytes_requested > 0)
{
psAssert(g_bytes_requested * g_resumed == stats.rbytes);
}
Printf("\n%d bytes received\n", stats.rbytes);
sslstatsPrintTime(&stats, g_resumed);
}
out:
matrixSslDeleteSessionId(sid);
matrixSslDeleteKeys(keys);
matrixSslClose();
clientconfigFree();
psFreeList(g_sigAlgsCertList, NULL);
psFreeList(g_sigAlgsList, NULL);
psFreeList(g_supportedVersionsList, NULL);
psFreeList(g_groupList, NULL);
# ifdef WIN32
psTrace("Press any key to close");
getchar();
# endif
return exit_code;
}
/******************************************************************************/
/*
Close a socket and free associated SSL context and buffers
An attempt is made to send a closure alert
*/
static void closeConn(ssl_t *ssl, SOCKET fd)
{
unsigned char *buf;
int32 len, rc;
if (g_send_closure_alert)
{
# if 1
/* Set the socket to non-blocking to flush remaining data */
# ifdef POSIX
rc = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
psAssert(rc >= 0);
# endif
# ifdef WIN32
len = 1; /* 1 for non-block, 0 for block */
rc = ioctlsocket(fd, FIONBIO, &len);
psAssert(rc);
# endif
/* Quick attempt to send a closure alert, don't worry about failure */
if (matrixSslEncodeClosureAlert(ssl) >= 0)
{
if ((len = matrixSslGetOutdata(ssl, &buf)) > 0)
{
if ((len = send(fd, buf, len, MSG_DONTWAIT)) > 0)
{
matrixSslSentData(ssl, len);
}
}
}
# endif
}
matrixSslDeleteSession(ssl);
if (fd != INVALID_SOCKET && g_keepalive == 0)
{
close(fd);
}
}
static int32_t extensionCb(ssl_t *ssl,
uint16_t extType, uint8_t extLen, void *e)
{
unsigned char *c;
short len;
char proto[128];
c = (unsigned char *) e;
if (extType == EXT_ALPN)
{
Memset(proto, 0x0, 128);
/* two byte proto list len, one byte proto len, then proto */
c += 2; /* Skip proto list len */
len = *c; c++;
Memcpy(proto, c, len);
Printf("Server agreed to use %s\n", proto);
}
return PS_SUCCESS;
}
/******************************************************************************/
/*
Example callback to show possible outcomes of certificate validation.
If this callback is not registered in matrixSslNewClientSession
the connection will be accepted or closed based on the alert value.
*/
static int32 certCb(ssl_t *ssl, psX509Cert_t *cert, int32 alert)
{
# ifndef USE_ONLY_PSK_CIPHER_SUITE
psX509Cert_t *next;
if (g_disable_peer_authentication)
{
psTraceStr("Allowing anonymous connection for: %s.\n",
cert->subject.commonName);
return SSL_ALLOW_ANON_CONNECTION;
}
/* An immediate SSL_ALERT_UNKNOWN_CA alert means we could not find the
CA to authenticate the server's certificate */
if (alert == SSL_ALERT_UNKNOWN_CA)
{
/*
SSL_ALERT_UNKNOWN_CA can also result from breach of the
max_verify_depth limit we specified in the session options.
*/
if (g_max_verify_depth > 0) {
for (next = cert; next != NULL; next = next->next)
{
if ((next->authStatus & PS_CERT_AUTH_FAIL_PATH_LEN) &&
(next->authFailFlags &
PS_CERT_AUTH_FAIL_VERIFY_DEPTH_FLAG))
{
psTrace("Maximum cert chain verify depth exceeded\n");
return SSL_ALERT_UNKNOWN_CA;
}
}
}
/* Example to allow anonymous connections based on a define */
else if (ALLOW_ANON_CONNECTIONS)
{
if (g_trace)
{
psTraceStr("Allowing anonymous connection for: %s.\n",
cert->subject.commonName);
}
return SSL_ALLOW_ANON_CONNECTION;
}
psTrace("ERROR: No matching CA found. Terminating connection\n");
}
/* Check for "major" authentication problems within the server certificate
chain.
The "alert" is the translation of the very first authentication problem
found. So if we are dealing with a certificate chain we should walk to
the parent-most cert to confirm there are no authentication problems
that indicate the chain itself did not validate or this client did
not find a CA to authenticate the server.
Some certificate callback implemenations might choose to ignore some
alerts that are considered minor to the use case, so this is an example
of how to make sure a minor alert (such as expired date) is not
overriding a more serious authentication problem
*/
for (next = cert; next != NULL; next = next->next)
{
if (next->authStatus == PS_CERT_AUTH_FAIL_SIG)
{
psTrace("Public key signature failure in server cert chain\n");
/* This should result in a BAD_CERTIFICATE alert */
alert = SSL_ALERT_BAD_CERTIFICATE;
break;
}
if (next->authStatus == PS_CERT_AUTH_FAIL_DN)
{
/* A CA file was never located to support this chain */
psTrace("No CA file was found to support server's certificate\n");
/* This should result in a SSL_ALERT_UNKNOWN_CA alert */
alert = SSL_ALERT_UNKNOWN_CA;
break;
}
if (next->authStatus == PS_CERT_AUTH_FAIL_AUTHKEY)
{
/* Subject and Issuer Key Id extension */
psTrace("Subject and Issuer Key Id mismatch error\n");
/* This should be a BAD_CERTIFICATE alert */
alert = SSL_ALERT_BAD_CERTIFICATE;
break;
}
}
/*
If the expectedName passed to matrixSslNewClientSession does not
match any of the server subject name or subjAltNames, we will have
the alert below.
For security, the expected name (typically a domain name) _must_
match one of the certificate subject names, or the connection
should not continue.
The default MatrixSSL certificates use localhost and 127.0.0.1 as
the subjects, so unless the server IP matches one of those, this
alert will happen.
To temporarily disable the subject name validation, NULL can be passed
as expectedName to matrixNewClientSession.
*/
if (alert == SSL_ALERT_CERTIFICATE_UNKNOWN)
{
psTraceStr("ERROR: %s not found in cert subject names\n",
matrixSslGetExpectedName(ssl));
}
if (alert == SSL_ALERT_CERTIFICATE_EXPIRED)
{
# ifdef POSIX
psTrace("ERROR: A cert did not fall within the notBefore/notAfter window\n");
# else
psTrace("WARNING: Certificate date window validation not implemented\n");
alert = 0;
# endif
}
if (alert == SSL_ALERT_ILLEGAL_PARAMETER)
{
psTrace("ERROR: Found correct CA but X.509 extension details are wrong\n");
}
/* Key usage related problems on chain */
for (next = cert; next != NULL; next = next->next)
{
if (next->authStatus == PS_CERT_AUTH_FAIL_EXTENSION)
{
if (next->authFailFlags & PS_CERT_AUTH_FAIL_KEY_USAGE_FLAG)
{
psTrace("CA keyUsage extension doesn't allow cert signing\n");
}
if (next->authFailFlags & PS_CERT_AUTH_FAIL_EKU_FLAG)
{
psTrace("Cert extendedKeyUsage extension doesn't allow TLS\n");
}
}
}
if (alert == SSL_ALERT_BAD_CERTIFICATE)
{
/* Should never let a connection happen if this is set. There was
either a problem in the presented chain or in the final CA test */
psTrace("ERROR: Problem in certificate validation. Exiting.\n");
}
# ifdef USE_CRL
/* Examples on how to look at the CRL status for the cert chain and fetch
CRLs if they have not been loaded */
{
psX509Crl_t *expired;
# ifdef MIDHANDSHAKE_CRL_FETCH
/* Will pause this handshake to go out and fetch a CRL via HTTP GET,
re-check for revocation and continue on if it all checks out */
int retryOnce = 0;
RETRY_CRL_TEST_ONCE:
# else
/* Perhaps the more standard way is to stop the handshake if the CRLs have
not been fetched. Go fetch them and retry the SSL connection from
scratch. The connection reattempt will only occur if the -n command
line option has been set to something greater than 1 */
int count = 0;
uint32_t urlLen;
unsigned char *url;
# endif
/* Loop to look at the CRL status of each cert in the chain */
for (next = cert; next != NULL; next = next->next)
{
switch (next->revokedStatus)
{
case CRL_CHECK_CRL_EXPIRED:
psTrace("Have CRL but it is expired. Fetching new one\n");
/* Remove the CRL from the table */
expired = psCRL_GetCRLForCert(next);
if (expired)
{
psAssert(expired->expired);
psCRL_Delete(expired);
}
else
{
psTrace("Unexpected combo of expired but no CRL found\n");
}
/* MOVING INTO CRL_CHECK_EXPECTED ON PURPOSE TO REFETCH */
case CRL_CHECK_EXPECTED:
/* There was a CRL distribution point in this cert but we didn't
have the CRL to test against */
# ifdef MIDHANDSHAKE_CRL_FETCH
/* It is an application choice to go out and fetch CRLs in the
middle of the handshake like this. It's probably not advised
to do this but here is an example if you'd like to do so */
/* Only attempt this once so we don't get stuck in a loop */
if (retryOnce)
{
psTrace("Cert was not able to be tested against a CRL\n");
alert = SSL_ALERT_CERTIFICATE_UNKNOWN;
break;
}
psTrace("Cert expects CRL. Mid-handshake attempt being made\n");
/* This fetchParseAndAuthCRLfromCert will work on "next" as a chain
so it is correct that the server cert will look for the first
instance of CHECK_EXPECTED and pass that as the start of
chain to work upon */
fetchParseAndAuthCRLfromCert(NULL,
next,
sslKeysGetCACerts(matrixSslGetKeys(ssl)));
/* If all went well, every cert in the server chain will have an
updated status */
retryOnce++;
goto RETRY_CRL_TEST_ONCE;
# else /* MIDHANSHAKE_CRL_FETCH */
psTrace("Cert expects CRL. Failing handshake to go fetch it\n");
/* A more typical case if CRL testing is expected to be done is
to halt the handshake now, go out and fetch the CRLs and
try the connection again */
/* Not clear which alert should be associated with this
application level decision. Let's call it UNKNOWN */
alert = SSL_ALERT_CERTIFICATE_UNKNOWN;
/* Save aside the CRL URL distribution points to fetch after
control is given back when this handshake is done. Correct
to pick up from the current cert and work up as far as
possible */
if (count < CRL_MAX_SERVER_CERT_CHAIN)
{
Memset(g_crlDistURLs[count], 0, CRL_MAX_URL_LEN);
psX509GetCRLdistURL(next, (char **) &url, &urlLen);
if (urlLen > CRL_MAX_URL_LEN)
{
psTraceInt("CLR URL distribution point longer than %d\n",
CRL_MAX_URL_LEN);
}
else
{
Memcpy(g_crlDistURLs[count], url, urlLen);
count++;
}
}
else
{
psTraceInt("Server cert chain was longer than %d\n",
CRL_MAX_SERVER_CERT_CHAIN);
}
break;
# endif /* MIDHANDSHAKE_CRL_FETCH */
case CRL_CHECK_NOT_EXPECTED:
psTrace("Cert didn't specify a CRL distribution point\n");
break;
case CRL_CHECK_PASSED_AND_AUTHENTICATED:
psTrace("Cert passed CRL test and CRL was authenticated\n");
break;
case CRL_CHECK_PASSED_BUT_NOT_AUTHENTICATED:
psTrace("Cert passed CRL test but CRL was not authenticated\n");
break;
case CRL_CHECK_REVOKED_AND_AUTHENTICATED:
psTrace("Cert was revoked by an authenticated CRL\n");
alert = SSL_ALERT_CERTIFICATE_REVOKED;
break;
case CRL_CHECK_REVOKED_BUT_NOT_AUTHENTICATED:
psTrace("Cert was revoked but the CRL wasn't authenticated\n");
alert = SSL_ALERT_CERTIFICATE_REVOKED;
break;
default:
break;
}
}
} /* End CRL local code block */
# endif
if (g_trace && alert == 0 && cert)
{
psTraceStr("SUCCESS: Validated cert for: %s.\n", cert->subject.commonName);
}
# endif /* !USE_ONLY_PSK_CIPHER_SUITE */
return alert;
} /* end certificate callback */
/******************************************************************************/
# ifdef USE_CRL
# ifndef MIDHANDSHAKE_CRL_FETCH
/* Part of example for halting handshake because no CRL was available. Now
we have closed that handshake and are fetching the CRLs that were set
aside during the certificate callback. We use our list of CA files as
potential issuers here so we can attempt a round of CRL authentications.
This authentication round will save time during the next handshake */
static void fetchSavedCRL(psX509Cert_t *potentialIssuers)
{
int i;
/* Test for 'h' is assuming URL to begin with "http" */
for (i = 0; i < CRL_MAX_SERVER_CERT_CHAIN && g_crlDistURLs[i][0] == 'h';
i++)
{
fetchParseAndAuthCRLfromUrl(NULL, g_crlDistURLs[i],
Strlen((char *) g_crlDistURLs[i]), potentialIssuers);
Memset(g_crlDistURLs[i], 0, CRL_MAX_URL_LEN);
}
}
/* Fetch a CRL give an URL. Once you have it, check to see if it can be
authenticated by any of the "potentialIssuers"
Regardless of authentication status, add any CRL that is found
to the global cache for access inside the library during internal
authentications */
static int32_t fetchParseAndAuthCRLfromUrl(psPool_t *pool, unsigned char *url,
uint32_t urlLen, psX509Cert_t *potentialIssuers)
{
unsigned char *crlBuf;
uint32_t crlBufLen;
psX509Crl_t *crl;
psX509Cert_t *ic;
/* url need not be freed. It points into cert structure */
if (fetchCRL(NULL, (char *) url, urlLen, &crlBuf, &crlBufLen) < 0)
{
psTrace("Unable to fetch CRL\n");
return -1;
}
/* Convert the CRL stream into our structure */
if (psX509ParseCRL(pool, &crl, crlBuf, crlBufLen) < 0)
{
psTrace("Unable to parse CRL\n");
psFree(crlBuf, pool);
return -1;
}
psFree(crlBuf, pool);
/* Adding the CRL to the global cache. This local crl is now the
same memory as the entry in the global cache and is managed
there. Freeing will now be done with psCRL_DeleteAll at
application closure */
psCRL_Update(crl, 1); /* The 1 will delete old CRLs */
/* Important to separate the concept of the CRL authentication
from the cert authentication. Here, we run through the
list of potential issuers the caller thinks could work */
for (ic = potentialIssuers; ic != NULL; ic = ic->next)
{
if (psX509AuthenticateCRL(ic, crl, NULL) >= 0)
{
psTrace("NOTE: Able to authenticate CRL\n");
break; /* Stop looking */
}
}
return PS_SUCCESS;
}
# endif /* ifndef MIDHANDSHAKE_CRL_FETCH */
/* Take the CRL Distribution URL from the "cert" (may be a chain) and go fetch
the CRL. Once you have it, check to see if it can be authenticated by any
of the "potentialIssuers" OR by the "cert" chain itself.
Regardless of authentication status, add any CRL that is found
to the global cache for access inside the library during internal
authentications */
static int32_t fetchParseAndAuthCRLfromCert(psPool_t *pool, psX509Cert_t *cert,
psX509Cert_t *potentialIssuers)
{
char *url;
unsigned char *crlBuf;
uint32_t urlLen, crlBufLen;
psX509Crl_t *crl;
psX509Cert_t *sc, *ic;
int32 numLoaded = 0;
sc = cert;
while (sc)
{
if (psX509GetCRLdistURL(sc, &url, &urlLen) > 0)
{
/* url need not be freed. It points into cert structure */
if (fetchCRL(NULL, url, urlLen, &crlBuf, &crlBufLen) < 0)
{
psTrace("Unable to fetch CRL\n");
sc = sc->next;
continue;
}
/* Convert the CRL stream into our structure */
if (psX509ParseCRL(pool, &crl, crlBuf, crlBufLen) < 0)
{
psTrace("Unable to parse CRL\n");
psFree(crlBuf, pool);
sc = sc->next;
continue;
}
psFree(crlBuf, pool);
/* Adding the CRL to the global cache. This local crl is now the
same memory as the entry in the global cache and is managed
there. Freeing will now be done with psCRL_DeleteAll at
application closure */
psCRL_Update(crl, 1); /* The 1 will delete old CRLs */
++numLoaded;
/* Important to separate the concept of the CRL authentication
from the cert authentication. Here, we run through the
list of potential issuers the caller thinks could work */
for (ic = potentialIssuers; ic != NULL; ic = ic->next)
{
if (psX509AuthenticateCRL(ic, crl, NULL) >= 0)
{
psTrace("NOTE: Able to authenticate CRL\n");
break; /* Stop looking */
}
}
if (crl->authenticated == 0)
{
/* If none of our potential issuers were able to authenticate,
let's just run through our own "cert" chain as well.
The reason we are doing this is to allow this function
to handle cases where the "cert" is part of a server
cert chain where the issuer is included as part of that
CERTIFICATE message. That is what we are testing here.
The potentialIssuers will typically be the loaded CA
files and that will catch the cases where the parent-most
certificate of the server chain will need to be
authenticated */
for (ic = cert; ic != NULL; ic = ic->next)
{
if (psX509AuthenticateCRL(ic, crl, NULL) >= 0)
{
psTrace("NOTE: Able to authenticate CRL\n");
break; /* Stop looking */
}
}
}
/* Regardless of whether or not we could authenticate the CRL,
run the function that recalculates the revokedStatus of
the certificate itself. REQUIRES g_CRL */
psCRL_determineRevokedStatus(sc);
}
sc = sc->next;
}
psTraceInt("CRLs loaded: %d\n", numLoaded);
return PS_SUCCESS;
}
/**
Return the number of bytes difference between 'end' and 'start'.
@param[in] end A pointer to valid memory, greater or equal to 'start'
@param[in] start A pointer to valid memory, less than or equal to 'start'
@param sanity The maximum expected difference in bytes
@return Number of bytes that 'end' is greater than 'start'. Range is 0 <= return <= sanity.
If end < start, 0 is returned. If 'end' - 'start' > 'sanity', 'sanity' is returned.
*/
__inline static size_t ptrdiff_safe(const void *end, const void *start, size_t sanity)
{
ptrdiff_t d;
if (end < start)
{
return 0;
}
d = (const unsigned char *)end - (const unsigned char *)start;
if (d > sanity)
{
return sanity;
}
return (size_t) d;
}
/**
Example function to retrieve a CRL using HTTP GET over POSIX sockets.
@security This API does not fully validate all input. It should only be used to fetch
a CRL froa trusted source with validly generated CRL data. The HTTP response of the
trusted server should also be tested, as the HTTP parsing in this API is not flexible.
@param [out] crlBuf is allocated by this routine and must be freed via psFree
@return < 0 Error loading CRL. 0 on Success
*/
int32 fetchCRL(psPool_t *pool, char *url, uint32_t urlLen,
unsigned char **crlBuf, uint32_t *crlBufLen)
{
static unsigned char crl_getHdr[] = "GET ";
# define GET_OH_LEN 4
static unsigned char crl_httpHdr[] = " HTTP/1.0\r\n";
# define HTTP_OH_LEN 11
static unsigned char crl_hostHdr[] = "Host: ";
# define HOST_OH_LEN 6
static unsigned char crl_acceptHdr[] = "\r\nAccept: */*\r\n\r\n";
# define ACCEPT_OH_LEN 17
# define HOST_ADDR_LEN 64 /* max to hold 'www.something.com' */
# define GET_REQ_LEN 128 /* max to hold http GET request */
# define HTTP_REPLY_CHUNK_SIZE 2048
SOCKET fd;
struct hostent *ip;
struct in_addr intaddr;
char *pageStart, *ipAddr;
const char *replyPtr;
char hostAddr[HOST_ADDR_LEN], getReq[GET_REQ_LEN];
int hostAddrLen, getReqLen, pageLen;
ssize_t transferred;
int32_t grown = 0;
int32_t sawOK, sawContentLength, err, httpUriLen, port, offset;
unsigned char crlChunk[HTTP_REPLY_CHUNK_SIZE + 1];
unsigned char *crlBin; /* allocated */
uint32_t crlBinLen;
/* Is URI in expected URL form? */
if (Strstr(url, "http://") == NULL)
{
if (Strstr(url, "https://") == NULL)
{
psTraceStr("fetchCRL: Unsupported CRL URI: %s\n", url);
return -1;
}
httpUriLen = 8;
port = 80; /* No example yet of using SSL to fetch CRL */
}
else
{
httpUriLen = 7;
port = 80;
}
/* Parsing host and page and setting up IP address and GET request */
if ((pageStart = Strchr(url + httpUriLen, '/')) == NULL)
{
psTrace("fetchCRL: No host/page divider found\n");
return -1;
}
if ((hostAddrLen = (int) (pageStart - url) - httpUriLen) > HOST_ADDR_LEN)
{
psTrace("fetchCRL: HOST_ADDR_LEN needs to be increased\n");
return -1; /* ipAddr too small to hold */
}
Memset(hostAddr, 0, HOST_ADDR_LEN);
Memcpy(hostAddr, url + httpUriLen, hostAddrLen);
if ((ip = gethostbyname(hostAddr)) == NULL)
{
psTrace("fetchCRL: gethostbyname failed\n");
return -1;
}
Memcpy((char *) &intaddr, (char *) ip->h_addr_list[0],
(size_t) ip->h_length);
if ((ipAddr = inet_ntoa(intaddr)) == NULL)
{
psTrace("fetchCRL: inet_ntoa failed\n");
return -1;
}
pageLen = (urlLen - hostAddrLen - httpUriLen);
getReqLen = pageLen + hostAddrLen + GET_OH_LEN + HTTP_OH_LEN +
HOST_OH_LEN + ACCEPT_OH_LEN;
if (getReqLen > GET_REQ_LEN)
{
psTrace("fetchCRL: GET_REQ_LEN needs to be increased\n");
return -1;
}
/* Build the request: */
/* */
/* GET /page.crl HTTP/1.0 */
/* Host: www.host.com */
/* Accept: * / * */
/* */
Memset(getReq, 0, GET_REQ_LEN);
Memcpy(getReq, crl_getHdr, GET_OH_LEN);
offset = GET_OH_LEN;
Memcpy(getReq + offset, pageStart, pageLen);
offset += pageLen;
Memcpy(getReq + offset, crl_httpHdr, HTTP_OH_LEN);
offset += HTTP_OH_LEN;
Memcpy(getReq + offset, crl_hostHdr, HOST_OH_LEN);
offset += HOST_OH_LEN;
Memcpy(getReq + offset, hostAddr, hostAddrLen);
offset += hostAddrLen;
Memcpy(getReq + offset, crl_acceptHdr, ACCEPT_OH_LEN);
/* Connect and send */
fd = lsocketConnect(ipAddr, port, &err);
if (fd == INVALID_SOCKET || err != PS_SUCCESS)
{
psTraceInt("fetchCRL: socketConnect failed: %d\n", err);
return PS_PLATFORM_FAIL;
}
/* Send request and receive response */
offset = 0;
while (getReqLen)
{
if ((transferred = send(fd, getReq + offset, getReqLen, 0)) < 0)
{
psTraceInt("fetchCRL: socket send failed: %d\n", errno);
close(fd);
return PS_PLATFORM_FAIL;
}
getReqLen -= transferred;
offset += transferred;
}
/* Get a chunk at a time so we can peek at the size on the first chunk
and allocate the correct CRL size */
crlBin = NULL;
crlBinLen = 0;
*crlBuf = NULL;
*crlBufLen = 0;
sawOK = sawContentLength = 0;
/* This recv loop is not 100%. The parse is looking for a few specific
strings in the HTTP header to get initial status, content length,
and \r\n\r\n for beginning of CRL data. If a recv happens to fall right
on the boundary of any of these patterns, the behavior is undefined.
There are some asserts sprinked around to notify if this happens.
It SHOULD be sufficient to keep a decent size HTTP_REPLY_CHUNK_SIZE
that you can be pretty sure will hold the entire HTTP header but
the "recv" call itself is also a factor in how many bytes will be
recevied in the first call */
while ((transferred = recv(fd, crlChunk, HTTP_REPLY_CHUNK_SIZE, 0)) > 0)
{
crlChunk[transferred] = 0; /* Ensure zero termination for Strstr(). */
if (crlBin == NULL)
{
/* Still getting the details of the HTTP response */
/* Did we get an OK response? */
if (sawOK == 0)
{
if (Strstr((const char *) crlChunk, "200 OK") == NULL)
{
/* First chunk. Should be plenty large enough to hold */
psTrace("fetchCRL: server reply was not '200 OK'\n");
close(fd);
return -1;
}
sawOK++;
}
/* Length parse */
if (sawContentLength == 0)
{
if ((replyPtr = Strstr((const char *) crlChunk,
"Content-Length: ")) == NULL)
{
/* Apparently Content-Length is not always going to be
there. See if we have the end of the header instead */
if ((replyPtr = Strstr((const char *) crlChunk, "\r\n\r\n"))
== NULL)
{
continue; /* saw neither. keep trying */
}
/* Saw \r\n\r\n but no Content-Length: can't allocate full
CRL buffer at once so work in chunks */
crlBinLen = HTTP_REPLY_CHUNK_SIZE;
}
else
{
/* Got the Content-Length: as expected */
sawContentLength++;
/* Possible cut off right at Content-Length here which
would be a pain. This assert is seeing if there are
at least 8 more bytes in the chunk to read the
integer out of. If you hit this, some partial
parsing will need to be instrumented... or change
the chunk size if this is truly a chunk boundary */
psAssert((replyPtr + 16) <
(char *) &(crlChunk[HTTP_REPLY_CHUNK_SIZE - 24]));
/* Magic 16 is length of "Content-Length: " */
crlBinLen = (int) atoi(replyPtr + 16);
}
}
/* Data begins after CRLF CRLF */
if ((replyPtr = Strstr((const char *) crlChunk, "\r\n\r\n"))
== NULL)
{
continue;
}
/* Possible cut off right at data start here which
would be a pain. This assert is seeing if there are
4 more bytes in the chunk to advance past. If you hit this,
some partial parsing will need to be instrumented... or change
the chunk size if this is truly a chunk boundary */
psAssert((replyPtr + 4) < (char *) &(crlChunk[HTTP_REPLY_CHUNK_SIZE]));
replyPtr += 4; /* Move past that "\r\n\r\n" to get to start */
/* Check buffer length appears acceptable */
if (crlBinLen < 1 || crlBinLen > CRL_MAX_LENGTH)
{
psTrace("fetchCRL: Unacceptable size for CRL\n");
/* Note: If this fails you may need to check CRL_MAX_LENGTH,
as you possibly need to allow larger CRL. */
close(fd);
return -1;
}
/* Allocate the CRL buffer. Will be full size if sawContentLength */
if ((crlBin = (unsigned char *)psMalloc(pool, crlBinLen)) == NULL)
{
psTrace("fetchCRL: Memory allocation error for CRL buffer\n");
close(fd);
return -1;
}
/* So how much do we actually have to copy our of first chunk? */
transferred -= ptrdiff_safe(replyPtr, crlChunk, HTTP_REPLY_CHUNK_SIZE);
if (sawContentLength)
{
/* Will march crlBin forward so just assign output crlBuf now */
*crlBuf = crlBin;
*crlBufLen = crlBinLen;
Memcpy(crlBin, replyPtr, transferred);
crlBin += transferred;
psAssert((crlBin - *crlBuf) <= crlBinLen);
}
else
{
grown = 1;
/* Keep track of index to monitor size */
crlBinLen = transferred;
Memcpy(crlBin, replyPtr, transferred);
}
}
else
{
/* subsequent recv calls */
if (sawContentLength)
{
Memcpy(crlBin, crlChunk, transferred);
crlBin += transferred;
psAssert((crlBin - *crlBuf) <= crlBinLen);
}
else
{
if (transferred + crlBinLen > (HTTP_REPLY_CHUNK_SIZE * grown))
{
/* not enough room. psRealloc */
grown++;
crlBin = (unsigned char*)psRealloc(
crlBin, HTTP_REPLY_CHUNK_SIZE * grown, pool);
}
Memcpy(crlBin + crlBinLen, crlChunk, transferred);
crlBinLen += transferred;
}
}
}
close(fd);
if (sawContentLength == 0)
{
/* These have been changing as we grow */
*crlBuf = crlBin;
*crlBufLen = crlBinLen;
}
else
{
psAssert(crlBinLen == (crlBin - *crlBuf));
}
return 0;
}
# endif /* USE_CRL */
/******************************************************************************/
/*
Open an outgoing blocking socket connection to a remote ip and port.
Caller should always check *err value, even if a valid socket is returned
*/
static SOCKET lsocketConnect(char *ip, int32 port, int32 *err)
{
struct sockaddr_in addr;
SOCKET fd;
int32 rc;
/* By default, this will produce a blocking socket */
if ((fd = Socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
perror("Socket()");
psTrace("Error creating socket\n");
*err = SOCKET_ERRNO;
return INVALID_SOCKET;
}
# ifdef POSIX
rc = fcntl(fd, F_SETFD, FD_CLOEXEC);
psAssert(rc >= 0);
# endif
# if 0
{
struct linger lin;
rc = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &rc, sizeof(rc));
rc = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &rc, sizeof(rc));
lin.l_onoff = 0;
lin.l_linger = 0; /* Seconds */
setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &lin, sizeof(struct linger));
}
{
uint32 len;
getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rc, &len);
Printf("SO_RCVBUF: %d\n", rc);
}
# endif
# ifdef __APPLE__ /* MAC OS X */
rc = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *) &rc, sizeof(rc));
# endif
Memset((char *) &addr, 0x0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((short) port);
addr.sin_addr.s_addr = inet_addr(ip);
rc = Connect(fd, (struct sockaddr *) &addr, sizeof(addr));
if (rc < 0)
{
close(fd);
perror("Connect()");
*err = SOCKET_ERRNO;
}
else
{
*err = 0;
}
return fd;
}
# ifdef USE_TLS_1_3
static int32_t sendEarlyData(ssl_t *ssl)
{
FILE *fp = NULL;
unsigned char *earlyDataBuf;
int32_t earlyDataBufLen, earlyDataLen, maxEarlyData, encodedEarlyDataLen;
fp = Fopen(g_early_data_file, "r");
if (!fp)
{
psTrace("Failed to open early data file\n");
return PS_ARG_FAIL;
}
/* It is the caller's resposibility to keep track that not too much
early data is being sent. Data can be sent in multiple records. */
maxEarlyData = matrixSslGetMaxEarlyData(ssl);
/* Get writebuf for maximum early data length */
earlyDataBufLen = matrixSslGetWritebuf(ssl, &earlyDataBuf, maxEarlyData);
if (earlyDataBufLen <= 0)
{
Fclose(fp);
return PS_FAILURE;
}
/* Send only as many bytes as the received write buffer can fit. It might
be less than requested size in case the fragment size is small */
maxEarlyData = earlyDataBufLen;
earlyDataLen = Fread(earlyDataBuf, 1, maxEarlyData, fp);
if (earlyDataLen <= 0)
{
Fclose(fp);
return PS_FAILURE;
}
Fclose(fp);
encodedEarlyDataLen = matrixSslEncodeWritebuf(ssl, earlyDataLen);
if (encodedEarlyDataLen <= 0)
{
return PS_FAILURE;
}
if (matrixSslGetEarlyDataStatus(ssl) != MATRIXSSL_EARLY_DATA_SENT)
{
psTrace("Unexpected early data status. Exiting\n");
return PS_FAILURE;
}
return MATRIXSSL_SUCCESS;
}
# endif /* USE_TLS_1_3 */
#else
/******************************************************************************/
/*
Stub main for compiling without client enabled
*/
int32 main(int32 argc, char **argv)
{
Printf("USE_CLIENT_SIDE_SSL must be enabled in matrixsslConfig.h at build" \
" time to run this application\n");
return EXIT_FAILURE;
}
#endif /* USE_CLIENT_SIDE_SSL */
/******************************************************************************/