Files
mars-matrixssl/apps/ssl/client.c
2016-05-03 23:31:53 -07:00

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 */
/******************************************************************************/