From c7b5a2411a316bff3e3bda9e5fdac72db66f6269 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 26 Oct 2009 15:47:52 +0000 Subject: 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 --- pjlib/src/pj/ssl_sock_ossl.c | 473 ++++++++++++++++------ pjlib/src/pj/ssl_sock_symbian.cpp | 8 +- pjlib/src/pjlib-test/ssl_sock.c | 820 ++++++++++++++++++++++++++++++++++++++ pjlib/src/pjlib-test/test.c | 4 + pjlib/src/pjlib-test/test.h | 3 + 5 files changed, 1191 insertions(+), 117 deletions(-) create mode 100644 pjlib/src/pjlib-test/ssl_sock.c (limited to 'pjlib/src') 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 #include #include +#include #include #include +#include /* 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, - ¶m->servername); + + /* Server name must be null-terminated */ + pj_strdup_with_null(pool, &ssock->param.server_name, + ¶m->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, ¶m->servername); + pj_strdup_with_null(pool, &ssock->servername, ¶m->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 + * + * 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 + +#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(¶m); + 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, ¶m, &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(¶m); + 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, ¶m, &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, ¶m, &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(¶m); + 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(¶m.timeout); + + /* SERVER */ + param.user_data = &state_serv; + state_serv.pool = pool; + + status = pj_ssl_sock_create(pool, ¶m, &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); -- cgit v1.2.3