summaryrefslogtreecommitdiff
path: root/pjlib/src
diff options
context:
space:
mode:
authorNanang Izzuddin <nanang@teluu.com>2009-10-26 15:47:52 +0000
committerNanang Izzuddin <nanang@teluu.com>2009-10-26 15:47:52 +0000
commitc7b5a2411a316bff3e3bda9e5fdac72db66f6269 (patch)
tree21fd37a2f1f9d1750187213ecf2700d6215257ca /pjlib/src
parentdfe07c8f8d65d6045e1003c6c11dca75495f7b38 (diff)
Ticket #957:
- Added features in secure socket: handshake timeout timer, certificate info, renegotiation API. - Added unit test for secure socket, along with testing purpose certificate & private key. - Updated build configs for secure socket. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2970 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjlib/src')
-rw-r--r--pjlib/src/pj/ssl_sock_ossl.c473
-rw-r--r--pjlib/src/pj/ssl_sock_symbian.cpp8
-rw-r--r--pjlib/src/pjlib-test/ssl_sock.c820
-rw-r--r--pjlib/src/pjlib-test/test.c4
-rw-r--r--pjlib/src/pjlib-test/test.h3
5 files changed, 1191 insertions, 117 deletions
diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
index 9c6c80c8..eca60d2e 100644
--- a/pjlib/src/pj/ssl_sock_ossl.c
+++ b/pjlib/src/pj/ssl_sock_ossl.c
@@ -25,12 +25,14 @@
#include <pj/lock.h>
#include <pj/log.h>
#include <pj/math.h>
+#include <pj/os.h>
#include <pj/pool.h>
#include <pj/string.h>
+#include <pj/timer.h>
/* Only build when PJ_HAS_SSL_SOCK is enabled */
-//#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
+#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
#define THIS_FILE "ssl_sock_ossl.c"
@@ -129,10 +131,14 @@ struct pj_ssl_sock_t
pj_ssl_sock_t *parent;
pj_ssl_sock_param param;
pj_ssl_cert_t *cert;
+
+ pj_ssl_cert_info local_cert_info;
+ pj_ssl_cert_info remote_cert_info;
pj_bool_t is_server;
enum ssl_state ssl_state;
pj_ioqueue_op_key_t handshake_op_key;
+ pj_timer_entry handshake_timer;
pj_sock_t sock;
pj_activesock_t *asock;
@@ -179,56 +185,57 @@ static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock);
*******************************************************************
*/
-/* ssl_report_error() */
-static void ssl_report_error(const char *sender, int level,
- pj_status_t status,
- const char *format, ...)
-{
- va_list marker;
+/**
+ * Mapping from OpenSSL error codes to pjlib error space.
+ */
- va_start(marker, format);
+#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \
+ PJ_ERRNO_SPACE_SIZE*6)
-#if PJ_LOG_MAX_LEVEL > 0
- if (status != PJ_SUCCESS) {
- char err_format[PJ_ERR_MSG_SIZE + 512];
- int len;
+#define PJ_SSL_ERRNO_SPACE_SIZE 5000
- len = pj_ansi_snprintf(err_format, sizeof(err_format),
- "%s: ", format);
- pj_strerror(status, err_format+len, sizeof(err_format)-len);
-
- pj_log(sender, level, err_format, marker);
+#define PJ_STATUS_FROM_OSSL(ossl_err) (ossl_err == SSL_ERROR_NONE? \
+ PJ_SUCCESS : \
+ (PJ_SSL_ERRNO_START + ossl_err))
- } else {
- unsigned long ssl_err;
+#define PJ_STATUS_TO_OSSL(status) (status == PJ_SUCCESS? \
+ SSL_ERROR_NONE : \
+ (status - PJ_SSL_ERRNO_START))
- ssl_err = ERR_get_error();
- if (ssl_err == 0) {
- pj_log(sender, level, format, marker);
- } else {
- char err_format[512];
- int len;
-
- len = pj_ansi_snprintf(err_format, sizeof(err_format),
- "%s: ", format);
- ERR_error_string(ssl_err, err_format+len);
-
- pj_log(sender, level, err_format, marker);
- }
- }
-#endif
+/*
+ * Get error string of OpenSSL.
+ */
+static pj_str_t ssl_strerror(pj_status_t status,
+ char *buf, pj_size_t bufsize)
+{
+ pj_str_t errstr;
+ unsigned long ssl_err = PJ_STATUS_TO_OSSL(status);
- va_end(marker);
-}
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ ERR_error_string_n(ssl_err, buf, bufsize);
+ errstr = pj_str(buf);
+
+#else
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown OpenSSL error %d",
+ ssl_err);
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+ return errstr;
+}
/* OpenSSL library initialization counter */
static int openssl_init_count;
+static int openssl_reg_strerr;
/* OpenSSL available ciphers */
-static pj_ssl_cipher openssl_ciphers[64];
+static pj_ssl_cipher openssl_ciphers[100];
static unsigned openssl_cipher_num;
@@ -238,6 +245,18 @@ static pj_status_t init_openssl(void)
if (++openssl_init_count != 1)
return PJ_SUCCESS;
+ /* Register error subsystem */
+ if (!openssl_reg_strerr) {
+ pj_status_t status;
+
+ openssl_reg_strerr = 1;
+ status = pj_register_strerror(PJ_SSL_ERRNO_START,
+ PJ_SSL_ERRNO_SPACE_SIZE,
+ &ssl_strerror);
+ pj_assert(status == PJ_SUCCESS);
+ }
+
+ /* Init OpenSSL lib */
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
@@ -344,17 +363,14 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx)
ssl_method = (SSL_METHOD*)DTLSv1_method();
break;
default:
- ssl_report_error(THIS_FILE, 4, PJ_EINVAL,
- "Error creating SSL context");
return PJ_EINVAL;
}
/* Create SSL context for the listener */
ctx = SSL_CTX_new(ssl_method);
if (ctx == NULL) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error creating SSL context");
- return PJ_EINVAL;
+ PJ_LOG(1,(ssock->pool->obj_name, "Error creating OpenSSL context"));
+ return PJ_STATUS_FROM_OSSL(ERR_get_error());
}
/* Apply credentials */
@@ -365,15 +381,11 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx)
rc = SSL_CTX_load_verify_locations(ctx, cert->CA_file.ptr, NULL);
if (rc != 1) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error loading/verifying CA list file '%s'",
- cert->CA_file.ptr);
+ PJ_LOG(1,(ssock->pool->obj_name, "Error loading CA list file "
+ "'%s'", cert->CA_file.ptr));
SSL_CTX_free(ctx);
- return PJ_EINVAL;
+ return PJ_STATUS_FROM_OSSL(ERR_get_error());
}
-
- PJ_LOG(5,(THIS_FILE, "CA file successfully loaded from '%s'",
- cert->CA_file.ptr));
}
/* Set password callback */
@@ -390,15 +402,11 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx)
rc = SSL_CTX_use_certificate_chain_file(ctx, cert->cert_file.ptr);
if(rc != 1) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error loading certificate chain file '%s'",
- cert->cert_file.ptr);
+ PJ_LOG(1,(ssock->pool->obj_name, "Error loading certificate "
+ "chain file '%s'", cert->cert_file.ptr));
SSL_CTX_free(ctx);
- return PJ_EINVAL;
+ return PJ_STATUS_FROM_OSSL(ERR_get_error());
}
-
- PJ_LOG(5,(THIS_FILE, "TLS certificate successfully loaded from '%s'",
- cert->cert_file.ptr));
}
@@ -409,15 +417,11 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx)
SSL_FILETYPE_PEM);
if(rc != 1) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error adding private key from '%s'",
- cert->privkey_file.ptr);
+ PJ_LOG(1,(ssock->pool->obj_name, "Error adding private key "
+ "from '%s'", cert->privkey_file.ptr));
SSL_CTX_free(ctx);
- return PJ_EINVAL;
+ return PJ_STATUS_FROM_OSSL(ERR_get_error());
}
-
- PJ_LOG(5,(THIS_FILE, "Private key successfully loaded from '%s'",
- cert->privkey_file.ptr));
}
}
@@ -430,12 +434,10 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx)
}
if (ssock->is_server && ssock->param.require_client_cert)
- mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER;
SSL_CTX_set_verify(ctx, mode, NULL);
- PJ_LOG(5,(THIS_FILE, "Verification mode set to %d", mode));
-
*p_ctx = ctx;
return PJ_SUCCESS;
@@ -473,6 +475,10 @@ static void reset_ssl_sock_state(pj_ssl_sock_t *ssock)
ssock->asock = NULL;
ssock->sock = PJ_INVALID_SOCKET;
}
+ if (ssock->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(ssock->sock);
+ ssock->sock = PJ_INVALID_SOCKET;
+ }
}
@@ -528,16 +534,131 @@ static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock)
/* Finally, set chosen cipher list */
ret = SSL_set_cipher_list(ssock->ossl_ssl, buf);
- if (ret < 1) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error setting cipher list");
- return PJ_EINVAL;
- }
+ if (ret < 1)
+ return PJ_STATUS_FROM_OSSL(SSL_get_error(ssock->ossl_ssl, ret));
return PJ_SUCCESS;
}
+/* Parse OpenSSL ASN1_TIME to pj_time_val and GMT info */
+static pj_bool_t parse_ossl_asn1_time(pj_time_val *tv, pj_bool_t *gmt,
+ const ASN1_TIME *tm)
+{
+ unsigned long parts[7] = {0};
+ char *p, *end;
+ unsigned len;
+ pj_bool_t utc;
+ pj_parsed_time pt;
+ int i;
+
+ utc = tm->type == V_ASN1_UTCTIME;
+ p = (char*)tm->data;
+ len = tm->length;
+ end = p + len - 1;
+
+ /* GMT */
+ *gmt = (*end == 'Z');
+
+ /* parse parts */
+ for (i = 0; i < 7 && p < end; ++i) {
+ pj_str_t st;
+
+ if (i==0 && !utc) {
+ /* 4 digits year part for non-UTC time format */
+ st.slen = 4;
+ } else if (i==6) {
+ /* fraction of seconds */
+ if (*p == '.') ++p;
+ st.slen = end - p + 1;
+ } else {
+ /* other parts always 2 digits length */
+ st.slen = 2;
+ }
+ st.ptr = p;
+
+ parts[i] = pj_strtoul(&st);
+ p += st.slen;
+ }
+
+ /* encode parts to pj_time_val */
+ pt.year = parts[0];
+ if (utc)
+ pt.year += (pt.year < 50)? 2000:1900;
+ pt.mon = parts[1] - 1;
+ pt.day = parts[2];
+ pt.hour = parts[3];
+ pt.min = parts[4];
+ pt.sec = parts[5];
+ pt.msec = parts[6];
+
+ pj_time_encode(&pt, tv);
+
+ return PJ_TRUE;
+}
+
+
+/* Get certificate info from OpenSSL X509 */
+static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, X509 *x)
+{
+ pj_ssl_cert_info info;
+ char buf1[256];
+ char buf2[256];
+
+ pj_assert(pool && ci);
+
+ if (!x) {
+ pj_bzero(ci, sizeof(pj_ssl_cert_info));
+ return;
+ }
+
+ pj_bzero(&info, sizeof(info));
+
+ /* Populate cert info */
+ info.subject = pj_str(X509_NAME_oneline(X509_get_subject_name(x),buf1,
+ sizeof(buf1)));
+ info.issuer = pj_str(X509_NAME_oneline(X509_get_issuer_name(x), buf2,
+ sizeof(buf2)));
+ info.version = X509_get_version(x) + 1;
+ parse_ossl_asn1_time(&info.validity_start, &info.validity_use_gmt,
+ X509_get_notBefore(x));
+ parse_ossl_asn1_time(&info.validity_end, &info.validity_use_gmt,
+ X509_get_notAfter(x));
+
+ /* Update certificate info */
+ if (pj_strcmp(&ci->subject, &info.subject))
+ pj_strdup(pool, &ci->subject, &info.subject);
+ if (pj_strcmp(&ci->issuer, &info.issuer))
+ pj_strdup(pool, &ci->issuer, &info.issuer);
+ ci->version = info.version;
+ ci->validity_start = info.validity_start;
+ ci->validity_end = info.validity_end;
+ ci->validity_use_gmt = info.validity_use_gmt;
+}
+
+
+/* Update local & remote certificates info. This function should be
+ * called after handshake or renegotiation successfully completed.
+ */
+static void update_certs_info(pj_ssl_sock_t *ssock)
+{
+ X509 *x;
+
+ pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED);
+
+ /* Active local certificate */
+ x = SSL_get_certificate(ssock->ossl_ssl);
+ get_cert_info(ssock->pool, &ssock->local_cert_info, x);
+ /* Don't free local's X509! */
+
+ /* Active remote certificate */
+ x = SSL_get_peer_certificate(ssock->ossl_ssl);
+ get_cert_info(ssock->pool, &ssock->remote_cert_info, x);
+ /* Free peer's X509 */
+ X509_free(x);
+}
+
+
/* When handshake completed:
* - notify application
* - if handshake failed, reset SSL state
@@ -546,6 +667,14 @@ static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock)
static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
pj_status_t status)
{
+ /* Cancel handshake timer */
+ if (ssock->param.timer_heap)
+ pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->handshake_timer);
+
+ /* Update certificates info on successful handshake */
+ if (status == PJ_SUCCESS)
+ update_certs_info(ssock);
+
/* Accepting */
if (ssock->is_server) {
if (status != PJ_SUCCESS) {
@@ -707,6 +836,22 @@ static pj_status_t flush_write_bio(pj_ssl_sock_t *ssock,
return status;
}
+
+static void handshake_timeout_cb(pj_timer_heap_t *th,
+ struct pj_timer_entry *te)
+{
+ pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)te->user_data;
+
+ PJ_UNUSED_ARG(th);
+
+ PJ_LOG(1,(ssock->pool->obj_name, "SSL handshake timeout after %d.%ds",
+ ssock->param.timeout.sec, ssock->param.timeout.msec));
+
+ on_handshake_complete(ssock, PJ_ETIMEDOUT);
+}
+
+
+/* Asynchronouse handshake */
static pj_status_t do_handshake(pj_ssl_sock_t *ssock)
{
pj_status_t status;
@@ -721,10 +866,8 @@ static pj_status_t do_handshake(pj_ssl_sock_t *ssock)
if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ)
{
/* Handshake fails */
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "SSL_do_handshake()");
pj_lock_release(ssock->write_mutex);
- return PJ_ECANCELLED;
+ return PJ_STATUS_FROM_OSSL(err);
}
}
@@ -766,15 +909,13 @@ static pj_bool_t asock_on_data_read (pj_activesock_t *asock,
pj_size_t nwritten;
/* Socket error or closed */
- if (data == NULL || size < 0)
- goto on_error;
-
- /* Consume the whole data */
- nwritten = BIO_write(ssock->ossl_rbio, data, size);
- if (nwritten < size) {
- status = PJ_ENOMEM;
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, "BIO_write()");
- goto on_error;
+ if (data && size > 0) {
+ /* Consume the whole data */
+ nwritten = BIO_write(ssock->ossl_rbio, data, size);
+ if (nwritten < size) {
+ status = PJ_STATUS_FROM_OSSL(ERR_get_error());
+ goto on_error;
+ }
}
/* Check if SSL handshake hasn't finished yet */
@@ -802,15 +943,16 @@ static pj_bool_t asock_on_data_read (pj_activesock_t *asock,
* is on progress, so let's protect it with write mutex.
*/
pj_lock_acquire(ssock->write_mutex);
-
size_ = SSL_read(ssock->ossl_ssl, data_, size_);
- if (size_ > 0) {
- pj_lock_release(ssock->write_mutex);
+ pj_lock_release(ssock->write_mutex);
+
+ if (size_ > 0 || status != PJ_SUCCESS) {
if (ssock->param.cb.on_data_read) {
pj_bool_t ret;
pj_size_t remainder_ = 0;
- buf->len += size_;
+ if (size_ > 0)
+ buf->len += size_;
ret = (*ssock->param.cb.on_data_read)(ssock, buf->data,
buf->len, status,
@@ -825,7 +967,18 @@ static pj_bool_t asock_on_data_read (pj_activesock_t *asock,
*/
buf->len = remainder_;
}
+
+ /* Active socket signalled connection closed/error, this has
+ * been signalled to the application along with any remaining
+ * buffer. So, let's just reset SSL socket now.
+ */
+ if (status != PJ_SUCCESS) {
+ reset_ssl_sock_state(ssock);
+ return PJ_FALSE;
+ }
+
} else {
+
int err = SSL_get_error(ssock->ossl_ssl, size);
/* SSL might just return SSL_ERROR_WANT_READ in
@@ -833,26 +986,43 @@ static pj_bool_t asock_on_data_read (pj_activesock_t *asock,
*/
if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ)
{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(ssock->pool->obj_name, "SSL_read() failed: %s",
+ errmsg));
+
/* Reset SSL socket state, then return PJ_FALSE */
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, "SSL_read()");
- pj_lock_release(ssock->write_mutex);
reset_ssl_sock_state(ssock);
- return PJ_FALSE;
+ goto on_error;
}
- /* SSL may write something in case of re-negotiation */
- status = flush_write_bio(ssock, &ssock->handshake_op_key, 0, 0);
- pj_lock_release(ssock->write_mutex);
- if (status != PJ_SUCCESS && status != PJ_EPENDING)
- goto on_error;
+ status = do_handshake(ssock);
+ if (status == PJ_SUCCESS) {
+ /* Renegotiation completed */
- /* If re-negotiation has been completed, start flushing
- * delayed send.
- */
- if (!SSL_renegotiate_pending(ssock->ossl_ssl)) {
+ /* Update certificates */
+ update_certs_info(ssock);
+
+ pj_lock_acquire(ssock->write_mutex);
status = flush_delayed_send(ssock);
- if (status != PJ_SUCCESS && status != PJ_EPENDING)
+ pj_lock_release(ssock->write_mutex);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(ssock->pool->obj_name, "Failed to flush "
+ "delayed send: %s", errmsg));
goto on_error;
+ }
+ } else if (status != PJ_EPENDING) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(ssock->pool->obj_name, "Renegotiation failed: "
+ "%s", errmsg));
+ goto on_error;
}
break;
@@ -943,9 +1113,13 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
pj_activesock_cfg asock_cfg;
unsigned i;
pj_status_t status;
+ char buf[64];
PJ_UNUSED_ARG(src_addr_len);
+ PJ_LOG(4,(ssock_parent->pool->obj_name, "Incoming connection from %s",
+ pj_sockaddr_print(src_addr, buf, sizeof(buf), 3)));
+
/* Create new SSL socket instance */
status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->param,
&ssock);
@@ -981,9 +1155,8 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
/* Create SSL instance */
ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
if (ssock->ossl_ssl == NULL) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error creating SSL connection object");
- status = PJ_EINVAL;
+ PJ_LOG(1,(ssock->pool->obj_name, "Error creating SSL instance"));
+ status = PJ_STATUS_FROM_OSSL(ERR_get_error());
goto on_return;
}
@@ -1049,6 +1222,16 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
ssock->write_state.start = ssock->write_state.buf;
ssock->write_state.len = 0;
+ /* Start handshake timer */
+ if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 ||
+ ssock->param.timeout.msec != 0))
+ {
+ pj_timer_entry_init(&ssock->handshake_timer, 0, ssock,
+ &handshake_timeout_cb);
+ pj_timer_heap_schedule(ssock->param.timer_heap, &ssock->handshake_timer,
+ &ssock->param.timeout);
+ }
+
/* Start SSL handshake */
ssock->ssl_state = SSL_STATE_HANDSHAKING;
SSL_set_accept_state(ssock->ossl_ssl);
@@ -1088,9 +1271,8 @@ static pj_bool_t asock_on_connect_complete (pj_activesock_t *asock,
/* Create SSL instance */
ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
if (ssock->ossl_ssl == NULL) {
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS,
- "Error creating SSL connection object");
- status = PJ_EINVAL;
+ PJ_LOG(1,(ssock->pool->obj_name, "Error creating SSL instance"));
+ status = PJ_STATUS_FROM_OSSL(ERR_get_error());
goto on_return;
}
@@ -1134,9 +1316,37 @@ static pj_bool_t asock_on_connect_complete (pj_activesock_t *asock,
ssock->write_state.start = ssock->write_state.buf;
ssock->write_state.len = 0;
+ /* Start handshake timer */
+ if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 ||
+ ssock->param.timeout.msec != 0))
+ {
+ pj_timer_entry_init(&ssock->handshake_timer, 0, ssock,
+ &handshake_timeout_cb);
+ pj_timer_heap_schedule(ssock->param.timer_heap,
+ &ssock->handshake_timer,
+ &ssock->param.timeout);
+ }
+
+#ifdef SSL_set_tlsext_host_name
+ /* Set server name to connect */
+ if (ssock->param.server_name.slen) {
+ /* Server name is null terminated already */
+ if (!SSL_set_tlsext_host_name(ssock->ossl_ssl,
+ ssock->param.server_name.ptr))
+ {
+ char err_str[PJ_ERR_MSG_SIZE];
+
+ ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str));
+ PJ_LOG(3,(ssock->pool->obj_name, "SSL_set_tlsext_host_name() "
+ "failed: %s", err_str));
+ }
+ }
+#endif
+
/* Start SSL handshake */
ssock->ssl_state = SSL_STATE_HANDSHAKING;
SSL_set_connect_state(ssock->ossl_ssl);
+
status = do_handshake(ssock);
if (status != PJ_EPENDING)
goto on_return;
@@ -1185,13 +1395,18 @@ PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate(
pj_pool_t *pool,
const pj_ssl_cert_t *cert)
{
+ pj_ssl_cert_t *cert_;
+
PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL);
- ssock->cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
- pj_strdup_with_null(pool, &ssock->cert->CA_file, &cert->CA_file);
- pj_strdup_with_null(pool, &ssock->cert->cert_file, &cert->cert_file);
- pj_strdup_with_null(pool, &ssock->cert->privkey_file, &cert->privkey_file);
- pj_strdup_with_null(pool, &ssock->cert->privkey_pass, &cert->privkey_pass);
+ cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
+ pj_memcpy(cert_, cert, sizeof(cert));
+ pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file);
+ pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file);
+ pj_strdup_with_null(pool, &cert_->privkey_file, &cert->privkey_file);
+ pj_strdup_with_null(pool, &cert_->privkey_pass, &cert->privkey_pass);
+
+ ssock->cert = cert_;
return PJ_SUCCESS;
}
@@ -1262,8 +1477,10 @@ PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool,
for (i = 0; i < param->ciphers_num; ++i)
ssock->param.ciphers[i] = param->ciphers[i];
}
- pj_strdup_with_null(pool, &ssock->param.servername,
- &param->servername);
+
+ /* Server name must be null-terminated */
+ pj_strdup_with_null(pool, &ssock->param.server_name,
+ &param->server_name);
/* Finally */
*p_ssock = ssock;
@@ -1345,6 +1562,10 @@ PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock,
/* Remote address */
pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr);
+
+ /* Certificates info */
+ info->local_cert_info = ssock->local_cert_info;
+ info->remote_cert_info = ssock->remote_cert_info;
}
return PJ_SUCCESS;
@@ -1489,11 +1710,11 @@ static pj_status_t ssl_write(pj_ssl_sock_t *ssock,
/* Re-negotiation is on progress, flush re-negotiation data */
status = flush_write_bio(ssock, &ssock->handshake_op_key, 0, 0);
if (status == PJ_SUCCESS || status == PJ_EPENDING)
+ /* Just return PJ_EBUSY when re-negotiation is on progress */
status = PJ_EBUSY;
} else {
/* Some problem occured */
- ssl_report_error(THIS_FILE, 4, PJ_SUCCESS, "SSL_write()");
- status = PJ_ECANCELLED;
+ status = PJ_STATUS_FROM_OSSL(err);
}
} else {
/* nwritten < *size, shouldn't happen, unless write BIO cannot hold
@@ -1776,5 +1997,25 @@ on_error:
}
-//#endif /* PJ_HAS_SSL_SOCK */
+PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
+{
+ int ret;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(ssock->ssl_state == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP);
+
+ if (SSL_renegotiate_pending(ssock->ossl_ssl))
+ return PJ_EPENDING;
+
+ ret = SSL_renegotiate(ssock->ossl_ssl);
+ if (ret <= 0) {
+ status = PJ_STATUS_FROM_OSSL(SSL_get_error(ssock->ossl_ssl, ret));
+ } else {
+ status = do_handshake(ssock);
+ }
+
+ return status;
+}
+
+#endif /* PJ_HAS_SSL_SOCK */
diff --git a/pjlib/src/pj/ssl_sock_symbian.cpp b/pjlib/src/pj/ssl_sock_symbian.cpp
index 06165f37..0619fd98 100644
--- a/pjlib/src/pj/ssl_sock_symbian.cpp
+++ b/pjlib/src/pj/ssl_sock_symbian.cpp
@@ -499,7 +499,7 @@ PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool,
for (i = 0; i < param->ciphers_num; ++i)
ssock->ciphers[i] = param->ciphers[i];
}
- pj_strdup_with_null(pool, &ssock->servername, &param->servername);
+ pj_strdup_with_null(pool, &ssock->servername, &param->server_name);
/* Finally */
*p_ssock = ssock;
@@ -1085,3 +1085,9 @@ PJ_DEF(pj_status_t) pj_ssl_sock_start_connect (pj_ssl_sock_t *ssock,
return status;
}
+
+PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
+{
+ PJ_UNUSED_ARG(ssock);
+ return PJ_ENOTSUP;
+}
diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c
new file mode 100644
index 00000000..8fd42ef0
--- /dev/null
+++ b/pjlib/src/pjlib-test/ssl_sock.c
@@ -0,0 +1,820 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; 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 program is distributed in the hope that it will be useful,
+ * but 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
+ */
+#include "test.h"
+#include <pjlib.h>
+
+#define ECHO_SERVER_NAME "localhost"
+#define ECHO_SERVER_ADDR "localhost"
+#define ECHO_SERVER_PORT 12345
+
+#define CERT_DIR "..\\build\\"
+#define CERT_CA_FILE NULL
+#define CERT_FILE CERT_DIR "cacert.pem"
+#define CERT_PRIVKEY_FILE CERT_DIR "privkey.pem"
+#define CERT_PRIVKEY_PASS ""
+
+
+#if INCLUDE_SSLSOCK_TEST
+
+
+struct send_key {
+ pj_ioqueue_op_key_t op_key;
+};
+
+
+static int get_cipher_list(void) {
+ pj_status_t status;
+ pj_ssl_cipher ciphers[100];
+ unsigned cipher_num;
+ unsigned i;
+
+ cipher_num = PJ_ARRAY_SIZE(ciphers);
+ status = pj_ssl_cipher_get_availables(ciphers, &cipher_num);
+ if (status != PJ_SUCCESS) {
+ app_perror("...FAILED to get available ciphers", status);
+ return -10;
+ }
+
+ PJ_LOG(3, ("", "...Found %u ciphers:", cipher_num));
+ for (i = 0; i < cipher_num; ++i) {
+ const char* st;
+ st = pj_ssl_cipher_name(ciphers[i]);
+ if (st == NULL)
+ st = "[Unknown]";
+
+ PJ_LOG(3, ("", "...%3u: 0x%08x=%s", i+1, ciphers[i], st));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+struct test_state
+{
+ pj_pool_t *pool; /* pool */
+ pj_bool_t echo; /* echo received data */
+ pj_status_t err; /* error flag */
+ unsigned sent; /* bytes sent */
+ unsigned recv; /* bytes received */
+ pj_uint8_t read_buf[256]; /* read buffer */
+ pj_bool_t done; /* test done flag */
+ char *send_str; /* data to send once connected */
+ unsigned send_str_len; /* send data length */
+ pj_bool_t check_echo; /* flag to compare sent & echoed data */
+ const char *check_echo_ptr; /* pointer/cursor for comparing data */
+ struct send_key send_key; /* send op key */
+};
+
+static void dump_cert_info(const char *prefix, const pj_ssl_cert_info *ci)
+{
+ const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ pj_parsed_time pt1;
+ pj_parsed_time pt2;
+
+ pj_time_decode(&ci->validity_start, &pt1);
+ pj_time_decode(&ci->validity_end, &pt2);
+
+ PJ_LOG(3, ("", "%sSubject : %.*s", prefix, ci->subject.slen, ci->subject.ptr));
+ PJ_LOG(3, ("", "%sIssuer : %.*s", prefix, ci->issuer.slen, ci->issuer.ptr));
+ PJ_LOG(3, ("", "%sVersion : v%d", prefix, ci->version));
+ PJ_LOG(3, ("", "%sValid from : %s %4d-%02d-%02d %02d:%02d:%02d.%03d %s",
+ prefix, wdays[pt1.wday], pt1.year, pt1.mon+1, pt1.day,
+ pt1.hour, pt1.min, pt1.sec, pt1.msec,
+ (ci->validity_use_gmt? "GMT":"")));
+ PJ_LOG(3, ("", "%sValid to : %s %4d-%02d-%02d %02d:%02d:%02d.%03d %s",
+ prefix, wdays[pt2.wday], pt2.year, pt2.mon+1, pt2.day,
+ pt2.hour, pt2.min, pt2.sec, pt2.msec,
+ (ci->validity_use_gmt? "GMT":"")));
+}
+
+
+static pj_bool_t ssl_on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status)
+{
+ struct test_state *st = (struct test_state*)
+ pj_ssl_sock_get_user_data(ssock);
+ void *read_buf[1];
+ pj_ssl_sock_info info;
+ const char *tmp_st;
+ char buf[64];
+
+ if (status != PJ_SUCCESS) {
+ app_perror("...ERROR ssl_on_connect_complete()", status);
+ goto on_return;
+ }
+
+ status = pj_ssl_sock_get_info(ssock, &info);
+ if (status != PJ_SUCCESS) {
+ app_perror("...ERROR pj_ssl_sock_get_info()", status);
+ goto on_return;
+ }
+
+ pj_sockaddr_print((pj_sockaddr_t*)&info.remote_addr, buf, sizeof(buf), 1);
+ PJ_LOG(3, ("", "...Connected to %s!", buf));
+
+ /* Print cipher name */
+ tmp_st = pj_ssl_cipher_name(info.cipher);
+ if (tmp_st == NULL)
+ tmp_st = "[Unknown]";
+ PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
+
+ /* Print certificates info */
+ if (info.local_cert_info.subject.slen) {
+ PJ_LOG(3, ("", ".....Local certificate info:"));
+ dump_cert_info(".......", &info.local_cert_info);
+ }
+ if (info.remote_cert_info.subject.slen) {
+ PJ_LOG(3, ("", ".....Remote certificate info:"));
+ dump_cert_info(".......", &info.remote_cert_info);
+ }
+
+ /* Start sending data */
+ while (st->sent < st->send_str_len) {
+ pj_ssize_t size;
+
+ size = st->send_str_len - st->sent;
+ status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key,
+ st->send_str + st->sent, &size, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ app_perror("...ERROR pj_ssl_sock_send()", status);
+ goto on_return;
+ }
+
+ if (status == PJ_SUCCESS)
+ st->sent += size;
+ else
+ break;
+ }
+
+ /* Start reading data */
+ read_buf[0] = st->read_buf;
+ status = pj_ssl_sock_start_read2(ssock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ app_perror("...ERROR pj_ssl_sock_start_read2()", status);
+ goto on_return;
+ }
+
+on_return:
+ st->err = status;
+ return PJ_TRUE;
+}
+
+
+static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *newsock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct test_state *st = (struct test_state*)
+ pj_ssl_sock_get_user_data(ssock);
+ void *read_buf[1];
+ pj_ssl_sock_info info;
+ pj_status_t status;
+ const char *tmp_st;
+ char buf[64];
+
+ PJ_UNUSED_ARG(src_addr_len);
+
+ status = pj_ssl_sock_get_info(newsock, &info);
+ if (status != PJ_SUCCESS) {
+ app_perror("...ERROR pj_ssl_sock_get_info()", status);
+ goto on_return;
+ }
+
+ pj_sockaddr_print(src_addr, buf, sizeof(buf), 1);
+ PJ_LOG(3, ("", "...Accepted connection from %s", buf));
+
+ /* Print cipher name */
+ tmp_st = pj_ssl_cipher_name(info.cipher);
+ if (tmp_st == NULL)
+ tmp_st = "[Unknown]";
+ PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
+
+ /* Print certificates info */
+ if (info.local_cert_info.subject.slen) {
+ PJ_LOG(3, ("", ".....Local certificate info:"));
+ dump_cert_info(".......", &info.local_cert_info);
+ }
+ if (info.remote_cert_info.subject.slen) {
+ PJ_LOG(3, ("", ".....Remote certificate info:"));
+ dump_cert_info(".......", &info.remote_cert_info);
+ }
+
+ pj_ssl_sock_set_user_data(newsock, st);
+
+ /* Start sending data */
+ while (st->sent < st->send_str_len) {
+ pj_ssize_t size;
+
+ size = st->send_str_len - st->sent;
+ status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key,
+ st->send_str + st->sent, &size, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ app_perror("...ERROR pj_ssl_sock_send()", status);
+ goto on_return;
+ }
+
+ if (status == PJ_SUCCESS)
+ st->sent += size;
+ else
+ break;
+ }
+
+ /* Start reading data */
+ read_buf[0] = st->read_buf;
+ status = pj_ssl_sock_start_read2(newsock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ app_perror("...ERROR pj_ssl_sock_start_read2()", status);
+ goto on_return;
+ }
+
+on_return:
+ st->err = status;
+ return PJ_TRUE;
+}
+
+static pj_bool_t ssl_on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ struct test_state *st = (struct test_state*)
+ pj_ssl_sock_get_user_data(ssock);
+
+ PJ_UNUSED_ARG(remainder);
+ PJ_UNUSED_ARG(data);
+
+ if (size > 0) {
+ pj_size_t consumed;
+
+ /* Set random remainder */
+ *remainder = pj_rand() % 100;
+
+ /* Apply zero remainder if:
+ * - remainder is less than size, or
+ * - connection closed/error
+ * - echo/check_eco set
+ */
+ if (*remainder > size || status != PJ_SUCCESS || st->echo || st->check_echo)
+ *remainder = 0;
+
+ consumed = size - *remainder;
+ st->recv += consumed;
+
+ //printf("%.*s", consumed, (char*)data);
+
+ pj_memmove(data, (char*)data + consumed, *remainder);
+
+ /* Echo data when specified to */
+ if (st->echo) {
+ pj_ssize_t size_ = consumed;
+ status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, data, &size_, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ app_perror("...ERROR pj_ssl_sock_send()", status);
+ goto on_return;
+ }
+
+ if (status == PJ_SUCCESS)
+ st->sent += size_;
+ }
+
+ /* Verify echoed data when specified to */
+ if (st->check_echo) {
+ if (!st->check_echo_ptr)
+ st->check_echo_ptr = st->send_str;
+
+ if (pj_memcmp(st->check_echo_ptr, data, consumed)) {
+ status = PJ_EINVAL;
+ app_perror("...ERROR echoed data not exact", status);
+ goto on_return;
+ }
+ st->check_echo_ptr += consumed;
+
+ if (st->send_str_len == st->recv)
+ st->done = PJ_TRUE;
+ }
+ }
+
+ if (status != PJ_SUCCESS) {
+ if (status == PJ_EEOF) {
+ status = PJ_SUCCESS;
+ st->done = PJ_TRUE;
+ } else {
+ app_perror("...ERROR ssl_on_data_read()", status);
+ }
+ }
+
+on_return:
+ st->err = status;
+ return PJ_TRUE;
+}
+
+static pj_bool_t ssl_on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t sent)
+{
+ struct test_state *st = (struct test_state*)
+ pj_ssl_sock_get_user_data(ssock);
+ PJ_UNUSED_ARG(op_key);
+
+ if (sent < 1) {
+ st->err++;
+ } else {
+ st->sent += sent;
+
+ /* Send more if any */
+ while (st->sent < st->send_str_len) {
+ pj_ssize_t size;
+ pj_status_t status;
+
+ size = st->send_str_len - st->sent;
+ status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key,
+ st->send_str + st->sent, &size, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ app_perror("...ERROR pj_ssl_sock_send()", status);
+ st->err++;
+ break;
+ }
+
+ if (status == PJ_SUCCESS)
+ st->sent += size;
+ else
+ break;
+ }
+ }
+
+ return PJ_TRUE;
+}
+
+#define HTTP_REQ "GET / HTTP/1.0\r\n\r\n";
+#define HTTP_SERVER_ADDR "trac.pjsip.org"
+#define HTTP_SERVER_PORT 443
+
+static int https_client_test(void)
+{
+ pj_pool_t *pool = NULL;
+ pj_ioqueue_t *ioqueue = NULL;
+ pj_ssl_sock_t *ssock = NULL;
+ pj_ssl_sock_param param;
+ pj_status_t status;
+ struct test_state state = {0};
+ pj_sockaddr local_addr, rem_addr;
+ pj_str_t tmp_st;
+
+ pool = pj_pool_create(mem, "http_get", 256, 256, NULL);
+
+ status = pj_ioqueue_create(pool, 4, &ioqueue);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ state.pool = pool;
+ state.send_str = HTTP_REQ;
+ state.send_str_len = pj_ansi_strlen(state.send_str);
+
+ pj_ssl_sock_param_default(&param);
+ param.cb.on_connect_complete = &ssl_on_connect_complete;
+ param.cb.on_data_read = &ssl_on_data_read;
+ param.cb.on_data_sent = &ssl_on_data_sent;
+ param.ioqueue = ioqueue;
+ param.user_data = &state;
+ param.server_name = pj_str((char*)HTTP_SERVER_ADDR);
+
+ status = pj_ssl_sock_create(pool, &param, &ssock);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, "0.0.0.0"), 0);
+ pj_sockaddr_init(PJ_AF_INET, &rem_addr, pj_strset2(&tmp_st, HTTP_SERVER_ADDR), HTTP_SERVER_PORT);
+ status = pj_ssl_sock_start_connect(ssock, pool, &local_addr, &rem_addr, sizeof(rem_addr));
+ if (status == PJ_SUCCESS) {
+ ssl_on_connect_complete(ssock, PJ_SUCCESS);
+ } else if (status == PJ_EPENDING) {
+ status = PJ_SUCCESS;
+ } else {
+ goto on_return;
+ }
+
+ /* Wait until everything has been sent/received */
+ while (state.err == PJ_SUCCESS && !state.done) {
+#ifdef PJ_SYMBIAN
+ pj_symbianos_poll(-1, 1000);
+#else
+ pj_time_val delay = {0, 100};
+ pj_ioqueue_poll(ioqueue, &delay);
+#endif
+ }
+
+ if (state.err) {
+ status = state.err;
+ goto on_return;
+ }
+
+ PJ_LOG(3, ("", "...Done!"));
+ PJ_LOG(3, ("", ".....Sent/recv: %d/%d bytes", state.sent, state.recv));
+
+on_return:
+ if (ssock)
+ pj_ssl_sock_close(ssock);
+ if (ioqueue)
+ pj_ioqueue_destroy(ioqueue);
+ if (pool)
+ pj_pool_release(pool);
+
+ return status;
+}
+
+
+static int echo_test(pj_ssl_sock_proto proto, pj_ssl_cipher srv_cipher,
+ pj_ssl_cipher cli_cipher)
+{
+ pj_pool_t *pool = NULL;
+ pj_ioqueue_t *ioqueue = NULL;
+ pj_ssl_sock_t *ssock_serv = NULL;
+ pj_ssl_sock_t *ssock_cli = NULL;
+ pj_ssl_sock_param param;
+ struct test_state state_serv = { 0 };
+ struct test_state state_cli = { 0 };
+ pj_sockaddr local_addr, rem_addr;
+ pj_str_t tmp_st;
+ pj_ssl_cipher ciphers[1];
+ pj_ssl_cert_t *cert = NULL;
+ pj_status_t status;
+
+ pool = pj_pool_create(mem, "echo", 256, 256, NULL);
+
+ status = pj_ioqueue_create(pool, 4, &ioqueue);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* Set cert */
+ {
+ pj_str_t tmp1, tmp2, tmp3, tmp4;
+
+ status = pj_ssl_cert_load_from_files(pool,
+ pj_strset2(&tmp1, (char*)CERT_CA_FILE),
+ pj_strset2(&tmp2, (char*)CERT_FILE),
+ pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE),
+ pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS),
+ &cert);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+ }
+
+ pj_ssl_sock_param_default(&param);
+ param.proto = proto;
+ param.cb.on_accept_complete = &ssl_on_accept_complete;
+ param.cb.on_connect_complete = &ssl_on_connect_complete;
+ param.cb.on_data_read = &ssl_on_data_read;
+ param.cb.on_data_sent = &ssl_on_data_sent;
+ param.ioqueue = ioqueue;
+ param.ciphers_num = 1;
+ param.ciphers = ciphers;
+
+ /* SERVER */
+ param.user_data = &state_serv;
+ ciphers[0] = srv_cipher;
+
+ state_serv.pool = pool;
+ state_serv.echo = PJ_TRUE;
+
+ status = pj_ssl_sock_create(pool, &param, &ssock_serv);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, ECHO_SERVER_ADDR), ECHO_SERVER_PORT);
+ status = pj_ssl_sock_start_accept(ssock_serv, pool, &local_addr, sizeof(local_addr));
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* CLIENT */
+ param.user_data = &state_cli;
+ ciphers[0] = cli_cipher;
+
+ state_cli.pool = pool;
+ state_cli.check_echo = PJ_TRUE;
+
+ {
+ pj_time_val now;
+
+ pj_gettimeofday(&now);
+ pj_srand((pj_rand()%now.sec) * (pj_rand()%now.msec));
+ state_cli.send_str_len = (pj_rand() % 5 + 1) * 1024 + pj_rand() % 1024;
+ }
+ state_cli.send_str = pj_pool_alloc(pool, state_cli.send_str_len);
+ {
+ unsigned i;
+ for (i = 0; i < state_cli.send_str_len; ++i)
+ state_cli.send_str[i] = (char)(pj_rand() % 256);
+ }
+
+ status = pj_ssl_sock_create(pool, &param, &ssock_cli);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, "0.0.0.0"), 0);
+ pj_sockaddr_init(PJ_AF_INET, &rem_addr, pj_strset2(&tmp_st, ECHO_SERVER_ADDR), ECHO_SERVER_PORT);
+ status = pj_ssl_sock_start_connect(ssock_cli, pool, &local_addr, &rem_addr, sizeof(rem_addr));
+ if (status == PJ_SUCCESS) {
+ ssl_on_connect_complete(ssock_cli, PJ_SUCCESS);
+ } else if (status == PJ_EPENDING) {
+ status = PJ_SUCCESS;
+ } else {
+ goto on_return;
+ }
+
+ /* Wait until everything has been sent/received or error */
+ while (!state_serv.err && !state_cli.err && !state_serv.done && !state_cli.done)
+ {
+#ifdef PJ_SYMBIAN
+ pj_symbianos_poll(-1, 1000);
+#else
+ pj_time_val delay = {0, 100};
+ pj_ioqueue_poll(ioqueue, &delay);
+#endif
+ }
+
+ if (state_serv.err || state_cli.err) {
+ if (state_serv.err != PJ_SUCCESS)
+ status = state_serv.err;
+ else
+ status = state_cli.err;
+
+ goto on_return;
+ }
+
+ PJ_LOG(3, ("", "...Done!"));
+ PJ_LOG(3, ("", ".....Server sent/recv: %d/%d bytes", state_serv.sent, state_serv.recv));
+ PJ_LOG(3, ("", ".....Client sent/recv: %d/%d bytes", state_cli.sent, state_cli.recv));
+
+on_return:
+ if (ssock_serv)
+ pj_ssl_sock_close(ssock_serv);
+ if (ssock_cli)
+ pj_ssl_sock_close(ssock_cli);
+ if (ioqueue)
+ pj_ioqueue_destroy(ioqueue);
+ if (pool)
+ pj_pool_release(pool);
+
+ return status;
+}
+
+
+static pj_bool_t asock_on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ struct test_state *st = (struct test_state*)
+ pj_activesock_get_user_data(asock);
+
+ PJ_UNUSED_ARG(data);
+ PJ_UNUSED_ARG(size);
+ PJ_UNUSED_ARG(remainder);
+
+ if (status != PJ_SUCCESS) {
+ if (status == PJ_EEOF) {
+ status = PJ_SUCCESS;
+ st->done = PJ_TRUE;
+ } else {
+ app_perror("...ERROR asock_on_data_read()", status);
+ }
+ }
+
+ st->err = status;
+
+ return PJ_TRUE;
+}
+
+
+static pj_bool_t asock_on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status)
+{
+ struct test_state *st = (struct test_state*)
+ pj_activesock_get_user_data(asock);
+
+ if (status == PJ_SUCCESS) {
+ status = pj_activesock_start_read(asock, st->pool, 1, 0);
+ }
+
+ st->err = status;
+
+ return PJ_TRUE;
+}
+
+
+/* Set ms_timeout to 0 to disable timer */
+static int client_non_ssl(unsigned ms_timeout)
+{
+ pj_pool_t *pool = NULL;
+ pj_ioqueue_t *ioqueue = NULL;
+ pj_timer_heap_t *timer = NULL;
+ pj_ssl_sock_t *ssock_serv = NULL;
+ pj_activesock_t *asock_cli = NULL;
+ pj_activesock_cb asock_cb = { 0 };
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ pj_ssl_sock_param param;
+ struct test_state state_serv = { 0 };
+ struct test_state state_cli = { 0 };
+ pj_sockaddr listen_addr;
+ pj_str_t tmp_st;
+ pj_ssl_cert_t *cert = NULL;
+ pj_status_t status;
+
+ pool = pj_pool_create(mem, "echo", 256, 256, NULL);
+
+ status = pj_ioqueue_create(pool, 4, &ioqueue);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ status = pj_timer_heap_create(pool, 4, &timer);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* Set cert */
+ {
+ pj_str_t tmp1, tmp2, tmp3, tmp4;
+ status = pj_ssl_cert_load_from_files(pool,
+ pj_strset2(&tmp1, (char*)CERT_CA_FILE),
+ pj_strset2(&tmp2, (char*)CERT_FILE),
+ pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE),
+ pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS),
+ &cert);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+ }
+
+ pj_ssl_sock_param_default(&param);
+ param.cb.on_accept_complete = &ssl_on_accept_complete;
+ param.cb.on_data_read = &ssl_on_data_read;
+ param.cb.on_data_sent = &ssl_on_data_sent;
+ param.ioqueue = ioqueue;
+ param.timeout.sec = 0;
+ param.timeout.msec = ms_timeout;
+ param.timer_heap = timer;
+ pj_time_val_normalize(&param.timeout);
+
+ /* SERVER */
+ param.user_data = &state_serv;
+ state_serv.pool = pool;
+
+ status = pj_ssl_sock_create(pool, &param, &ssock_serv);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ pj_sockaddr_init(PJ_AF_INET, &listen_addr, pj_strset2(&tmp_st, ECHO_SERVER_ADDR), ECHO_SERVER_PORT);
+ status = pj_ssl_sock_start_accept(ssock_serv, pool, &listen_addr, sizeof(listen_addr));
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* CLIENT */
+ state_cli.pool = pool;
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ asock_cb.on_connect_complete = &asock_on_connect_complete;
+ asock_cb.on_data_read = &asock_on_data_read;
+ status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), NULL,
+ ioqueue, &asock_cb, &state_cli, &asock_cli);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ status = pj_activesock_start_connect(asock_cli, pool, (pj_sockaddr_t*)&listen_addr,
+ pj_sockaddr_get_len(&listen_addr));
+ if (status == PJ_SUCCESS) {
+ asock_on_connect_complete(asock_cli, PJ_SUCCESS);
+ } else if (status == PJ_EPENDING) {
+ status = PJ_SUCCESS;
+ } else {
+ goto on_return;
+ }
+
+ /* Wait until everything has been sent/received or error */
+ while (!state_serv.err && !state_cli.err && !state_serv.done && !state_cli.done)
+ {
+#ifdef PJ_SYMBIAN
+ pj_symbianos_poll(-1, 1000);
+#else
+ pj_time_val delay = {0, 100};
+ pj_ioqueue_poll(ioqueue, &delay);
+ pj_timer_heap_poll(timer, &delay);
+#endif
+ }
+
+ if (state_serv.err || state_cli.err) {
+ if (state_serv.err != PJ_SUCCESS)
+ status = state_serv.err;
+ else
+ status = state_cli.err;
+
+ goto on_return;
+ }
+
+ PJ_LOG(3, ("", "...Done!"));
+
+on_return:
+ if (ssock_serv)
+ pj_ssl_sock_close(ssock_serv);
+ if (asock_cli)
+ pj_activesock_close(asock_cli);
+ if (timer)
+ pj_timer_heap_destroy(timer);
+ if (ioqueue)
+ pj_ioqueue_destroy(ioqueue);
+ if (pool)
+ pj_pool_release(pool);
+
+ return status;
+}
+
+
+int ssl_sock_test(void)
+{
+ int ret;
+
+ PJ_LOG(3,("", "..get cipher list test"));
+ ret = get_cipher_list();
+ if (ret != 0)
+ return ret;
+
+#if 0
+ PJ_LOG(3,("", "..https client test"));
+ ret = https_client_test();
+ if (ret != 0)
+ return ret;
+#endif
+
+ PJ_LOG(3,("", "..echo test w/ TLSv1 and TLS_RSA_WITH_DES_CBC_SHA cipher"));
+ ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA);
+ if (ret != 0)
+ return ret;
+
+ PJ_LOG(3,("", "..echo test w/ SSLv23 and TLS_RSA_WITH_AES_256_CBC_SHA cipher"));
+ ret = echo_test(PJ_SSL_SOCK_PROTO_SSL23, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA);
+ if (ret != 0)
+ return ret;
+
+ PJ_LOG(3,("", "..echo test w/ incompatible ciphers"));
+ ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA);
+ if (ret == 0)
+ return -10;
+
+ PJ_LOG(3,("", "..client non-SSL timeout in 5 secs"));
+ ret = client_non_ssl(5000);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+#else /* INCLUDE_SSLSOCK_TEST */
+/* To prevent warning about "translation unit is empty"
+ * when this test is disabled.
+ */
+int dummy_ssl_sock_test;
+#endif /* INCLUDE_SSLSOCK_TEST */
+
diff --git a/pjlib/src/pjlib-test/test.c b/pjlib/src/pjlib-test/test.c
index b3e2951d..9d7c1ba4 100644
--- a/pjlib/src/pjlib-test/test.c
+++ b/pjlib/src/pjlib-test/test.c
@@ -167,6 +167,10 @@ int test_inner(void)
DO_TEST( file_test() );
#endif
+#if INCLUDE_SSLSOCK_TEST
+ DO_TEST( ssl_sock_test() );
+#endif
+
#if INCLUDE_ECHO_SERVER
//echo_server();
//echo_srv_sync();
diff --git a/pjlib/src/pjlib-test/test.h b/pjlib/src/pjlib-test/test.h
index 0f128b05..2d65d15d 100644
--- a/pjlib/src/pjlib-test/test.h
+++ b/pjlib/src/pjlib-test/test.h
@@ -54,6 +54,7 @@
#define INCLUDE_UDP_IOQUEUE_TEST GROUP_NETWORK
#define INCLUDE_TCP_IOQUEUE_TEST GROUP_NETWORK
#define INCLUDE_ACTIVESOCK_TEST GROUP_NETWORK
+#define INCLUDE_SSLSOCK_TEST (PJ_HAS_SSL_SOCK && GROUP_NETWORK)
#define INCLUDE_IOQUEUE_PERF_TEST (PJ_HAS_THREADS && GROUP_NETWORK)
#define INCLUDE_IOQUEUE_UNREG_TEST (PJ_HAS_THREADS && GROUP_NETWORK)
#define INCLUDE_FILE_TEST GROUP_FILE
@@ -96,6 +97,7 @@ extern int tcp_ioqueue_test(void);
extern int ioqueue_perf_test(void);
extern int activesock_test(void);
extern int file_test(void);
+extern int ssl_sock_test(void);
extern int echo_server(void);
extern int echo_client(int sock_type, const char *server, int port);
@@ -104,6 +106,7 @@ extern int echo_srv_sync(void);
extern int udp_echo_srv_ioqueue(void);
extern int echo_srv_common_loop(pj_atomic_t *bytes_counter);
+
extern pj_pool_factory *mem;
extern int test_main(void);