1572 lines
45 KiB
C
1572 lines
45 KiB
C
/**
|
|
* @file client.c
|
|
* @version $Format:%h%d$
|
|
*
|
|
* Simple MatrixSSL blocking client example.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013-2016 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
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include "app.h"
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#else
|
|
#include "XGetopt.h"
|
|
#endif
|
|
#include "matrixssl/matrixsslApi.h"
|
|
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
|
|
#ifdef WIN32
|
|
#pragma message("DO NOT USE THESE DEFAULT KEYS IN PRODUCTION ENVIRONMENTS.")
|
|
#else
|
|
#warning "DO NOT USE THESE DEFAULT KEYS IN PRODUCTION ENVIRONMENTS."
|
|
#endif
|
|
|
|
/*
|
|
If supporting client authentication, pick ONE identity to auto select a
|
|
certificate and private key that support desired algorithms.
|
|
*/
|
|
//#define ID_RSA /* RSA Certificate and Key */
|
|
//#define ID_ECDH_ECDSA /* EC Certificate and Key */
|
|
//#define ID_ECDH_RSA /* EC Key with RSA signed certificate */
|
|
|
|
#define USE_HEADER_KEYS
|
|
#define ALLOW_ANON_CONNECTIONS 1
|
|
|
|
/* If the algorithm type is supported, load a CA for it */
|
|
#ifdef USE_HEADER_KEYS
|
|
/* CAs */
|
|
#ifdef USE_RSA_CIPHER_SUITE
|
|
#include "testkeys/RSA/ALL_RSA_CAS.h"
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
#include "testkeys/ECDH_RSA/ALL_ECDH-RSA_CAS.h"
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
|
|
#if defined(USE_SECP192R1) && defined(USE_SECP224R1) && defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS.h"
|
|
#endif /* USE_SECP192R1 && USE_SECP224R1 && USE_SECP521R1 */
|
|
|
|
#if !defined(USE_SECP192R1) && defined(USE_SECP224R1) && defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P192.h"
|
|
#endif /* !USE_SECP192R1 && USE_SECP224R1 && USE_SECP521R1 */
|
|
|
|
#if defined(USE_SECP192R1) && !defined(USE_SECP224R1) && defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P224.h"
|
|
#endif /* USE_SECP192R1 && !USE_SECP224R1 && USE_SECP521R1 */
|
|
|
|
#if defined(USE_SECP192R1) && defined(USE_SECP224R1) && !defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P521.h"
|
|
#endif /* USE_SECP192R1 && USE_SECP224R1 && !USE_SECP521R1 */
|
|
|
|
#if !defined(USE_SECP192R1) && !defined(USE_SECP224R1) && defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P192_AND_P224.h"
|
|
#endif /* !USE_SECP192R1 && !USE_SECP224R1 && USE_SECP521R1 */
|
|
|
|
#if !defined(USE_SECP192R1) && defined(USE_SECP224R1) && !defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P192_AND_P521.h"
|
|
#endif /* !USE_SECP192R1 && USE_SECP224R1 && !USE_SECP521R1 */
|
|
|
|
#if defined(USE_SECP192R1) && !defined(USE_SECP224R1) && !defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P224_AND_P521.h"
|
|
#endif /* USE_SECP192R1 && USE_SECP224R1 && !USE_SECP521R1 */
|
|
|
|
#if !defined(USE_SECP192R1) && !defined(USE_SECP224R1) && !defined(USE_SECP521R1)
|
|
#include "testkeys/EC/ALL_EC_CAS_EXCEPT_P192_P224_AND_P521.h"
|
|
#endif /* !USE_SECP192R1 && USE_SECP224R1 && !USE_SECP521R1 */
|
|
|
|
#endif /* USE_ECC_CIPHER_SUITE */
|
|
|
|
/* Identity Certs and Keys for use with Client Authentication */
|
|
#ifdef ID_RSA
|
|
#define EXAMPLE_RSA_KEYS
|
|
#include "testkeys/RSA/1024_RSA.h"
|
|
#include "testkeys/RSA/1024_RSA_KEY.h"
|
|
#include "testkeys/RSA/2048_RSA.h"
|
|
#include "testkeys/RSA/2048_RSA_KEY.h"
|
|
#include "testkeys/RSA/4096_RSA.h"
|
|
#include "testkeys/RSA/4096_RSA_KEY.h"
|
|
#endif
|
|
|
|
#ifdef ID_ECDH_ECDSA
|
|
#define EXAMPLE_EC_KEYS
|
|
#include "testkeys/EC/384_EC.h"
|
|
#include "testkeys/EC/384_EC_KEY.h"
|
|
#endif
|
|
|
|
#ifdef ID_ECDH_RSA
|
|
#define EXAMPLE_ECDH_RSA_KEYS
|
|
#include "testkeys/ECDH_RSA/521_ECDH-RSA.h"
|
|
#include "testkeys/ECDH_RSA/521_ECDH-RSA_KEY.h"
|
|
#endif
|
|
|
|
/* File-based keys */
|
|
#else
|
|
/* CAs */
|
|
#ifdef USE_RSA_CIPHER_SUITE
|
|
static char rsaCAFile[] = "../../testkeys/RSA/ALL_RSA_CAS.pem";
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
static char ecdhRsaCAFile[] = "../../testkeys/ECDH_RSA/ALL_ECDH-RSA_CAS.pem";
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
#if defined(USE_SECP192R1) && defined(USE_SECP521R1)
|
|
static char ecCAFile[] = "../../testkeys/EC/ALL_EC_CAS.pem";
|
|
#endif /* USE_SECP192R1 && USE_SECP521R1 */
|
|
|
|
#if !defined(USE_SECP192R1) && defined(USE_SECP521R1)
|
|
static char ecCAFile[] = "../../testkeys/EC/ALL_EC_CAS_EXCEPT_P192.pem";
|
|
#endif /* USE_SECP192R1 && USE_SECP521R1 */
|
|
|
|
#if defined(USE_SECP192R1) && !defined(USE_SECP521R1)
|
|
static char ecCAFile[] = "../../testkeys/EC/ALL_EC_CAS_EXCEPT_P521.pem";
|
|
#endif /* USE_SECP192R1 && USE_SECP521R1 */
|
|
|
|
#if !defined(USE_SECP192R1) && !defined(USE_SECP521R1)
|
|
static char ecCAFile[] = "../../testkeys/EC/ALL_EC_CAS_EXCEPT_P192_AND_P521.pem";
|
|
#endif /* USE_SECP192R1 && USE_SECP521R1 */
|
|
|
|
#endif /* USE_ECC_CIPHER_SUITE */
|
|
|
|
/* Identity Certs and Keys for use with Client Authentication */
|
|
#ifdef ID_RSA
|
|
#define EXAMPLE_RSA_KEYS
|
|
static char rsaCertFile[] = "../../testkeys/RSA/2048_RSA.pem";
|
|
static char rsaPrivkeyFile[] = "../../testkeys/RSA/2048_RSA_KEY.pem";
|
|
#endif
|
|
|
|
#ifdef ID_ECDH_ECDSA
|
|
#define EXAMPLE_EC_KEYS
|
|
static char ecCertFile[] = "../../testkeys/EC/521_EC.pem";
|
|
static char ecPrivkeyFile[] = "../../testkeys/EC/521_EC_KEY.pem";
|
|
#endif
|
|
|
|
#ifdef ID_ECDH_RSA
|
|
#define EXAMPLE_ECDH_RSA_KEYS
|
|
static char ecdhRsaCertFile[] = "../../testkeys/ECDH_RSA/521_ECDH-RSA.pem";
|
|
static char ecdhRsaPrivkeyFile[] = "../../testkeys/ECDH_RSA/521_ECDH-RSA_KEY.pem";
|
|
#endif
|
|
|
|
#endif /* USE_HEADER_KEYS */
|
|
|
|
#ifdef USE_PSK_CIPHER_SUITE
|
|
/* Defines PSK_HEADER_TABLE and PSK_HEADER_TABLE_COUNT */
|
|
#include "../../testkeys/PSK/psk.h"
|
|
#endif
|
|
|
|
//#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"
|
|
"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" };
|
|
|
|
static unsigned char g_matrixShutdownServer[] = "MATRIX_SHUTDOWN";
|
|
|
|
extern int opterr;
|
|
static char g_ip[16];
|
|
static char g_path[256];
|
|
static int g_port, g_new, g_resumed, g_ciphers, g_version, g_closeServer;
|
|
static int g_key_len, g_disableCertNameChk;
|
|
static uint16_t g_cipher[16];
|
|
static int g_trace;
|
|
static int g_keepalive;
|
|
|
|
static uint32_t g_bytes_requested;
|
|
static uint8_t g_send_closure_alert;
|
|
|
|
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_CRL
|
|
static int32 crlCb(psPool_t *pool, psX509Cert_t *CA, int append,
|
|
char *url, uint32 urlLen);
|
|
#endif
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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, sessionFlag, extLen;
|
|
ssl_t *ssl;
|
|
unsigned char *buf, *ext;
|
|
httpConn_t cp;
|
|
SOCKET fd;
|
|
psTime_t t1, t2;
|
|
sslSessOpts_t options;
|
|
#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;
|
|
}
|
|
|
|
#ifdef SSL_FLAGS_SSLV3
|
|
/* Corresponds to version 3.g_version */
|
|
switch (g_version) {
|
|
case 0:
|
|
sessionFlag = SSL_FLAGS_SSLV3;
|
|
break;
|
|
case 1:
|
|
sessionFlag = SSL_FLAGS_TLS_1_0;
|
|
break;
|
|
case 2:
|
|
sessionFlag = SSL_FLAGS_TLS_1_1;
|
|
break;
|
|
case 3:
|
|
sessionFlag = SSL_FLAGS_TLS_1_2;
|
|
break;
|
|
default:
|
|
sessionFlag = SSL_FLAGS_TLS_1_0;
|
|
break;
|
|
}
|
|
#else
|
|
/* MatrixSSL <= 3.4.2 don't support setting version on request */
|
|
sessionFlag = 0;
|
|
#endif
|
|
|
|
memset(&options, 0x0, sizeof(sslSessOpts_t));
|
|
options.versionFlag = sessionFlag;
|
|
options.userPtr = keys;
|
|
//options.maxFragLen = 512;
|
|
//options.truncHmac = PS_TRUE;
|
|
//options.ticketResumption = PS_TRUE;
|
|
//options.ecFlags |= SSL_OPT_SECP521R1;
|
|
//options.ecFlags |= SSL_OPT_SECP384R1;
|
|
//options.ecFlags |= SSL_OPT_SECP256R1;
|
|
//options.ecFlags |= SSL_OPT_SECP224R1;
|
|
//options.ecFlags |= SSL_OPT_SECP192R1;
|
|
|
|
matrixSslNewHelloExtension(&extension, NULL);
|
|
matrixSslCreateSNIext(NULL, (unsigned char*)g_ip, (uint32)strlen(g_ip),
|
|
&ext, &extLen);
|
|
matrixSslLoadHelloExtension(extension, ext, extLen, EXT_SNI);
|
|
psFree(ext, NULL);
|
|
|
|
#ifdef USE_ALPN
|
|
/* Application Layer Protocol Negotiation */
|
|
alpn[0] = psMalloc(NULL, strlen("http/1.0"));
|
|
memcpy(alpn[0], "http/1.0", strlen("http/1.0"));
|
|
alpnLen[0] = strlen("http/1.0");
|
|
|
|
alpn[1] = 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_ip */
|
|
if (g_disableCertNameChk == 0) {
|
|
rc = matrixSslNewClientSession(&ssl, keys, sid, g_cipher, g_ciphers,
|
|
certCb, g_ip, extension, extensionCb, &options);
|
|
|
|
|
|
} else {
|
|
rc = matrixSslNewClientSession(&ssl, keys, sid, g_cipher, g_ciphers,
|
|
certCb, NULL, extension, extensionCb, &options);
|
|
}
|
|
|
|
matrixSslDeleteHelloExtension(extension);
|
|
if (rc != MATRIXSSL_REQUEST_SEND) {
|
|
_psTraceInt("New Client Session Failed: %d. Exiting\n", rc);
|
|
close(fd);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
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 {
|
|
/* 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) {
|
|
/* 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);
|
|
if (ssl->hsState == SSL_HS_DONE) {
|
|
#ifdef USE_HIGHRES_TIME
|
|
stats->datatime += psDiffUsecs(t1, t2);
|
|
#else
|
|
stats->datatime += psDiffMsecs(t1, t2, NULL);
|
|
#endif
|
|
} else {
|
|
#ifdef USE_HIGHRES_TIME
|
|
stats->hstime += psDiffUsecs(t1, t2);
|
|
#else
|
|
stats->hstime += psDiffMsecs(t1, t2, NULL);
|
|
#endif
|
|
}
|
|
goto L_CLOSE_ERR;
|
|
}
|
|
psGetTime(&t2, NULL);
|
|
if (ssl->hsState == SSL_HS_DONE) {
|
|
#ifdef USE_HIGHRES_TIME
|
|
stats->datatime += psDiffUsecs(t1, t2);
|
|
#else
|
|
stats->datatime += psDiffMsecs(t1, t2, NULL);
|
|
#endif
|
|
} else {
|
|
#ifdef USE_HIGHRES_TIME
|
|
stats->hstime += psDiffUsecs(t1, t2);
|
|
#else
|
|
stats->hstime += psDiffMsecs(t1, t2, NULL);
|
|
#endif
|
|
}
|
|
|
|
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
|
|
/* 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;
|
|
}
|
|
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);
|
|
}
|
|
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) {
|
|
psTraceIntInfo("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;
|
|
}
|
|
psTraceIntInfo("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");
|
|
} else {
|
|
/*
|
|
printf("Received %d bytes %d usecs, state %d\n",
|
|
stats->rbytes, (int)stats->hstime, (int)stats->datatime,
|
|
ssl->hsState);
|
|
*/
|
|
}
|
|
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) + 1;
|
|
if ((available = matrixSslGetWritebuf(ssl, &buf, requested)) < 0) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
requested = min(requested, available);
|
|
snprintf((char *)buf, requested, (char *)g_httpRequestHdr, g_path);
|
|
|
|
|
|
if (g_trace) _psTraceStr("SEND: [%s]\n", (char*)buf);
|
|
if (matrixSslEncodeWritebuf(ssl, strlen((char *)buf)) < 0) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
}
|
|
|
|
#ifdef ID_RSA
|
|
#ifdef USE_HEADER_KEYS
|
|
static int32 loadRsaKeys(uint32 key_len, sslKeys_t *keys,
|
|
unsigned char *CAstream, int32 CAstreamLen)
|
|
{
|
|
int32 rc;
|
|
|
|
if (key_len == 1024) {
|
|
_psTrace("Using 1024 bit RSA private key\n");
|
|
rc = matrixSslLoadRsaKeysMem(keys, RSA1024, sizeof(RSA1024),
|
|
RSA1024KEY, sizeof(RSA1024KEY), CAstream, CAstreamLen);
|
|
} else if (key_len == 2048) {
|
|
_psTrace("Using 2048 bit RSA private key\n");
|
|
rc = matrixSslLoadRsaKeysMem(keys, RSA2048, sizeof(RSA2048),
|
|
RSA2048KEY, sizeof(RSA2048KEY), CAstream, CAstreamLen);
|
|
} else if (key_len == 4096) {
|
|
_psTrace("Using 4096 bit RSA private key\n");
|
|
rc = matrixSslLoadRsaKeysMem(keys, RSA4096, sizeof(RSA4096),
|
|
RSA4096KEY, sizeof(RSA4096KEY), CAstream, CAstreamLen);
|
|
} else {
|
|
rc = -1;
|
|
psAssert((key_len == 1024) || (key_len == 2048) || (key_len == 4096));
|
|
}
|
|
|
|
if (rc < 0) {
|
|
_psTrace("No certificate material loaded. Exiting\n");
|
|
if (CAstream) {
|
|
psFree(CAstream, NULL);
|
|
}
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
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"
|
|
"-b <numBytesPerRequest> - Client request size\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"
|
|
" - 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"
|
|
"-d - Disable server certicate name/addr chk\n"
|
|
"-h - Help, print usage and exit\n"
|
|
"-k <keyLen> - RSA keyLen\n"
|
|
" - Must be one of 1024, 2048 or 4096\n"
|
|
"-K - Keepalive (Re-use socket after TLS session close)\n"
|
|
"-n <numNewSessions> - Num of new (full handshake) sessions\n"
|
|
" - Default 1\n"
|
|
"-p <serverPortNum> - Port number for SSL/TLS server\n"
|
|
" - Default 4433 (HTTPS is 443)\n"
|
|
"-r <numResumedSessions> - Num of resumed SSL/TLS sesssions\n"
|
|
" - Default 0\n"
|
|
"-s <serverIpAddress> - IP address of server machine/interface\n"
|
|
" - Default 127.0.0.1 (localhost)\n"
|
|
"-u <url path> - URL path, eg. '/index.html'\n"
|
|
" Generates an HTTPS request after TLS negotiation\n"
|
|
" Mutually exclusive with '-b' flag\n"
|
|
"-V <tlsVersion> - SSL/TLS version to use\n"
|
|
" - '0' SSL 3.0\n"
|
|
" - '1' TLS 1.0\n"
|
|
" - '2' TLS 1.1\n"
|
|
" - '3' TLS 1.2 (default)\n"
|
|
"\n");
|
|
}
|
|
|
|
/* Returns number of cipher numbers found, or -1 if an error. */
|
|
#include <ctype.h>
|
|
static int32_t parse_cipher_list(char *cipherListString,
|
|
uint16_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;
|
|
|
|
// 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 = 1;
|
|
g_cipher[0] = 47;
|
|
g_disableCertNameChk = 0;
|
|
g_key_len = 1024;
|
|
g_new = 1;
|
|
g_port = 4433;
|
|
g_resumed = 0;
|
|
g_version = 3;
|
|
g_keepalive = 0;
|
|
|
|
opterr = 0;
|
|
while ((optionChar = getopt(argc, argv, "ab:c:dhk:Kn:p:r:s:u:V:")) != -1)
|
|
{
|
|
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':
|
|
// 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 'k':
|
|
key_len = atoi(optarg);
|
|
if ((key_len != 1024) && (key_len != 2048) && (key_len != 4096)) {
|
|
printf("-k option must be followed by a key_len whose value "
|
|
" must be 1024, 2048 or 4096\n");
|
|
return -1;
|
|
}
|
|
g_key_len = key_len;
|
|
break;
|
|
|
|
case 'K':
|
|
g_keepalive = 1;
|
|
break;
|
|
|
|
case 'n':
|
|
g_new = atoi(optarg);
|
|
break;
|
|
|
|
case 'p':
|
|
g_port = atoi(optarg);
|
|
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':
|
|
version = atoi(optarg);
|
|
if (version < 0 || version > 3) {
|
|
printf("Invalid version: %d\n", version);
|
|
return -1;
|
|
}
|
|
g_version = version;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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, CAstreamLen, i;
|
|
sslKeys_t *keys;
|
|
sslSessionId_t *sid;
|
|
struct g_sslstats stats;
|
|
unsigned char *CAstream;
|
|
#ifdef USE_CRL
|
|
int32 numLoaded;
|
|
#endif
|
|
#ifdef WIN32
|
|
WSADATA wsaData;
|
|
WSAStartup(MAKEWORD(1, 1), &wsaData);
|
|
#endif
|
|
|
|
if ((rc = matrixSslOpen()) < 0) {
|
|
_psTrace("MatrixSSL library init failure. Exiting\n");
|
|
return rc;
|
|
}
|
|
|
|
if (matrixSslNewKeys(&keys, NULL) < 0) {
|
|
_psTrace("MatrixSSL library key init failure. Exiting\n");
|
|
return -1;
|
|
}
|
|
|
|
if (0 != process_cmd_options(argc, argv)) {
|
|
usage();
|
|
return 0;
|
|
}
|
|
|
|
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:%s\n",
|
|
g_ip, g_port, g_new, g_resumed, g_key_len,
|
|
g_ciphers, g_strver[g_version]);
|
|
} else {
|
|
printf("client https://%s:%d%s "
|
|
"new:%d resumed:%d keylen:%d nciphers:%d version:%s\n",
|
|
g_ip, g_port, g_path, g_new, g_resumed, g_key_len,
|
|
g_ciphers, g_strver[g_version]);
|
|
}
|
|
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
#ifdef USE_HEADER_KEYS
|
|
/*
|
|
In-memory based keys
|
|
Build the CA list first for potential client auth usage
|
|
*/
|
|
CAstreamLen = 0;
|
|
#ifdef USE_RSA_CIPHER_SUITE
|
|
CAstreamLen += sizeof(RSACAS);
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
CAstreamLen += sizeof(ECDHRSACAS);
|
|
#endif
|
|
#endif
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
CAstreamLen += sizeof(ECCAS);
|
|
#endif
|
|
|
|
#if defined(USE_RSA_CIPHER_SUITE) || defined(USE_ECC_CIPHER_SUITE)
|
|
CAstream = psMalloc(NULL, CAstreamLen);
|
|
#else
|
|
CAstream = NULL;
|
|
#endif
|
|
|
|
CAstreamLen = 0;
|
|
#ifdef USE_RSA_CIPHER_SUITE
|
|
memcpy(CAstream, RSACAS, sizeof(RSACAS));
|
|
CAstreamLen += sizeof(RSACAS);
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
memcpy(CAstream + CAstreamLen, ECDHRSACAS, sizeof(ECDHRSACAS));
|
|
CAstreamLen += sizeof(ECDHRSACAS);
|
|
#endif
|
|
#endif
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
memcpy(CAstream + CAstreamLen, ECCAS, sizeof(ECCAS));
|
|
CAstreamLen += sizeof(ECCAS);
|
|
#endif
|
|
|
|
#ifdef ID_RSA
|
|
rc = loadRsaKeys(g_key_len, keys, CAstream, CAstreamLen);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ID_ECDH_RSA
|
|
if ((rc = matrixSslLoadEcKeysMem(keys, ECDHRSA521, sizeof(ECDHRSA521),
|
|
ECDHRSA521KEY, sizeof(ECDHRSA521KEY), (unsigned char*)CAstream,
|
|
CAstreamLen)) < 0) {
|
|
_psTrace("No certificate material loaded. Exiting\n");
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ID_ECDH_ECDSA
|
|
if ((rc = matrixSslLoadEcKeysMem(keys, EC384, sizeof(EC384),
|
|
EC384KEY, sizeof(EC384KEY), (unsigned char*)CAstream,
|
|
CAstreamLen)) < 0) {
|
|
_psTrace("No certificate material loaded. Exiting\n");
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
|
|
#else
|
|
/*
|
|
File based keys
|
|
*/
|
|
CAstreamLen = 0;
|
|
#ifdef USE_RSA_CIPHER_SUITE
|
|
CAstreamLen += (int32)strlen(rsaCAFile) + 1;
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
CAstreamLen += (int32)strlen(ecdhRsaCAFile) + 1;
|
|
#endif
|
|
#endif
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
CAstreamLen += (int32)strlen(ecCAFile) + 1;
|
|
#endif
|
|
if (CAstreamLen > 0) {
|
|
CAstream = psMalloc(NULL, CAstreamLen);
|
|
memset(CAstream, 0x0, CAstreamLen);
|
|
} else {
|
|
CAstream = NULL;
|
|
}
|
|
|
|
CAstreamLen = 0;
|
|
#ifdef USE_RSA_CIPHER_SUITE
|
|
memcpy(CAstream, rsaCAFile, strlen(rsaCAFile));
|
|
CAstreamLen += strlen(rsaCAFile);
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
memcpy(CAstream + CAstreamLen, ";", 1); CAstreamLen++;
|
|
memcpy(CAstream + CAstreamLen, ecdhRsaCAFile, strlen(ecdhRsaCAFile));
|
|
CAstreamLen += strlen(ecdhRsaCAFile);
|
|
#endif
|
|
#endif
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
if (CAstreamLen > 0) {
|
|
memcpy(CAstream + CAstreamLen, ";", 1); CAstreamLen++;
|
|
}
|
|
memcpy(CAstream + CAstreamLen, ecCAFile, strlen(ecCAFile));
|
|
#endif
|
|
|
|
/* Load Identiy */
|
|
#ifdef EXAMPLE_RSA_KEYS
|
|
if ((rc = matrixSslLoadRsaKeys(keys, rsaCertFile, rsaPrivkeyFile, NULL,
|
|
(char*)CAstream)) < 0) {
|
|
_psTrace("No certificate material loaded. Exiting\n");
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
#ifdef EXAMPLE_ECDH_RSA_KEYS
|
|
if ((rc = matrixSslLoadEcKeys(keys, ecdhRsaCertFile, ecdhRsaPrivkeyFile,
|
|
NULL, (char*)CAstream)) < 0) {
|
|
_psTrace("No certificate material loaded. Exiting\n");
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
#ifdef EXAMPLE_EC_KEYS
|
|
if ((rc = matrixSslLoadEcKeys(keys, ecCertFile, ecPrivkeyFile, NULL,
|
|
(char*)CAstream)) < 0) {
|
|
_psTrace("No certificate material loaded. Exiting\n");
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
if (CAstream) psFree(CAstream, NULL);
|
|
#endif /* USE_HEADER_KEYS */
|
|
#endif /* USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
#ifdef USE_PSK_CIPHER_SUITE
|
|
for (rc = 0; rc < PSK_HEADER_TABLE_COUNT; rc++) {
|
|
matrixSslLoadPsk(keys,
|
|
PSK_HEADER_TABLE[rc].key, sizeof(PSK_HEADER_TABLE[rc].key),
|
|
PSK_HEADER_TABLE[rc].id, sizeof(PSK_HEADER_TABLE[rc].id));
|
|
}
|
|
#endif /* USE_PSK_CIPHER_SUITE */
|
|
|
|
#ifdef USE_CRL
|
|
if (matrixSslGetCRL(keys, crlCb, &numLoaded) < 0) {
|
|
_psTrace("WARNING: A CRL failed to load\n");
|
|
}
|
|
_psTraceInt("CRLs loaded: %d\n", numLoaded);
|
|
#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++;
|
|
}
|
|
|
|
for (i = 0; i < g_new; i++) {
|
|
matrixSslNewSessionId(&sid, NULL);
|
|
rc = httpsClientConnection(keys, sid, &stats);
|
|
if (rc < 0) {
|
|
printf("F %d/%d\n", i, g_new);
|
|
return 0;
|
|
} else {
|
|
printf("N"); fflush(stdout);
|
|
}
|
|
/* Leave the final sessionID for resumed connections */
|
|
if (i + 1 < g_new) matrixSslDeleteSessionId(sid);
|
|
}
|
|
if (g_new) printf("\n");
|
|
if (g_bytes_requested > 0) {
|
|
psAssert(g_bytes_requested * g_new == stats.rbytes);
|
|
}
|
|
printf("%d bytes received\n", stats.rbytes);
|
|
#ifdef USE_HIGHRES_TIME
|
|
printf("%d usec (%d avg usec/conn SSL handshake overhead)\n",
|
|
(int)stats.hstime, (int)(stats.hstime/ g_new));
|
|
printf("%d usec (%d avg usec/conn SSL data overhead)\n",
|
|
(int)stats.datatime, (int)(stats.datatime/ g_new));
|
|
#else
|
|
printf("%d msec (%d avg msec/conn SSL handshake overhead)\n",
|
|
(int)stats.hstime, (int)(stats.hstime/ g_new));
|
|
printf("%d msec (%d avg msec/conn SSL data overhead)\n",
|
|
(int)stats.datatime, (int)(stats.datatime/ g_new));
|
|
#endif
|
|
|
|
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);
|
|
} else {
|
|
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);
|
|
#ifdef USE_HIGHRES_TIME
|
|
printf("%d usec (%d avg usec/conn SSL handshake overhead)\n",
|
|
(int)stats.hstime, (int)(stats.hstime/ g_resumed));
|
|
printf("%d usec (%d avg usec/conn SSL data overhead)\n",
|
|
(int)stats.datatime, (int)(stats.datatime/ g_resumed));
|
|
#else
|
|
printf("%d msec (%d avg msec/conn SSL handshake overhead)\n",
|
|
(int)stats.hstime, (int)(stats.hstime/ g_resumed));
|
|
printf("%d msec (%d avg msec/conn SSL data overhead)\n",
|
|
(int)stats.datatime, (int)(stats.datatime/ g_resumed));
|
|
#endif
|
|
}
|
|
|
|
matrixSslDeleteSessionId(sid);
|
|
|
|
matrixSslDeleteKeys(keys);
|
|
matrixSslClose();
|
|
|
|
#ifdef WIN32
|
|
_psTrace("Press any key to close");
|
|
getchar();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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) {
|
|
//printf("Closing socket from closeConn\n");
|
|
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 possiblie 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;
|
|
|
|
/* 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) {
|
|
/* Example to allow anonymous connections based on a define */
|
|
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 subjet 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",
|
|
ssl->expectedName);
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
|
|
if (g_trace && alert == 0) _psTraceStr("SUCCESS: Validated cert for: %s.\n",
|
|
cert->subject.commonName);
|
|
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
return alert;
|
|
}
|
|
|
|
#ifdef USE_CRL
|
|
/* Basic example of matrixSslGetCRL callback for downloading a CRL from a given
|
|
URL and passing the CRL contents to matrixSslLoadCRL
|
|
|
|
< 0 - Error loading CRL
|
|
> 0 - Success
|
|
*/
|
|
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 CRL_BUF_SIZE 4096 /* max size of incoming CRL */
|
|
|
|
int32 crlCb(psPool_t *pool, psX509Cert_t *CA, int append, char *url,
|
|
uint32 urlLen)
|
|
{
|
|
SOCKET fd;
|
|
struct hostent *ip;
|
|
struct in_addr intaddr;
|
|
char *pageStart, *replyPtr, *ipAddr;
|
|
char hostAddr[HOST_ADDR_LEN], getReq[GET_REQ_LEN];
|
|
char crlBuf[CRL_BUF_SIZE];
|
|
int hostAddrLen, getReqLen, pageLen;
|
|
int32 transferred;
|
|
int32 err, httpUriLen, port, offset;
|
|
uint32 crlBinLen;
|
|
|
|
/* Is URI in expected URL form? */
|
|
if (strstr(url, "http://") == NULL) {
|
|
if (strstr(url, "https://") == NULL) {
|
|
_psTraceStr("crlCb: 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("crlCb: No host/page divider found\n");
|
|
return -1;
|
|
}
|
|
if ((hostAddrLen = (int)(pageStart - url) - httpUriLen) > HOST_ADDR_LEN) {
|
|
_psTrace("crlCb: 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("crlCb: 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("crlCb: 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("crlCb: 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("crlCb: 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("crlCb: socket send failed: %d\n", errno);
|
|
close(fd);
|
|
return PS_PLATFORM_FAIL;
|
|
}
|
|
getReqLen -= transferred;
|
|
offset += transferred;
|
|
}
|
|
|
|
/* Not a good full recv */
|
|
if ((transferred = recv(fd, crlBuf, CRL_BUF_SIZE, 0)) <= 0) {
|
|
_psTrace("crlCb: socket recv closed or failed\n");
|
|
close(fd);
|
|
return PS_PLATFORM_FAIL;
|
|
}
|
|
if (transferred == CRL_BUF_SIZE) {
|
|
/* CRL larger than max */
|
|
_psTrace("crlCb: CRL_BUF_SIZE needs to be increased\n");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
|
|
/* Did we get an OK response? */
|
|
if (strstr(crlBuf, "200 OK") == NULL) {
|
|
_psTrace("crlCb: server reply was not '200 OK'\n");
|
|
return -1;
|
|
}
|
|
/* Length parse */
|
|
if ((replyPtr = strstr(crlBuf, "Content-Length: ")) == NULL) {
|
|
return -1;
|
|
}
|
|
crlBinLen = (int)atoi(replyPtr + 16);
|
|
|
|
/* Data begins after CRLF CRLF */
|
|
if ((replyPtr = strstr(crlBuf, "\r\n\r\n")) == NULL) {
|
|
return -1;
|
|
}
|
|
/* A sanity test that the length matches the remainder */
|
|
if ((transferred - (replyPtr - crlBuf) - 4) != crlBinLen) {
|
|
return -1;
|
|
}
|
|
|
|
/* Lastly, pass the CRL to matrixSslLoadCRL to parse, perform signature
|
|
validation, and cache the revoked certificates for this CA */
|
|
return matrixSslLoadCRL(pool, CA, append, replyPtr + 4, crlBinLen, NULL);
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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 POSIX
|
|
// rc = 1;
|
|
// setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&rc, sizeof(rc));
|
|
// fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
|
|
#elif defined(WIN32)
|
|
// rc = 1; /* 1 for non-block, 0 for block */
|
|
// ioctlsocket(fd, FIONBIO, &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;
|
|
}
|
|
|
|
#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 -1;
|
|
}
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
|
|
/******************************************************************************/
|
|
|