From bb2fc905eb58b9ebdf66e89330599be996821db7 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Wed, 24 Feb 2010 05:43:34 +0000 Subject: Ticket #1032: - Initial version of server domain name verification: - Updated SSL certificate info, especially identities info - Updated verification mechanism as in the specifications in ticket desc. - Added server domain name info in pjsip_tx_data. - Added alternative API for acquiring transport and creating transport of transport factory to include pjsip_tx_data param. - Server identity match criteria: - full host name match - wild card not accepted - if identity is URI, it must be SIP/SIPS URI - Initial version of transport state notifications: - Added new API to set transport state callback in PJSIP and PJSUA. - Defined states: connected/disconnected, accepted/rejected, verification errors. - Minors: - Updated SSL socket test: dump verification result, test of requiring client cert, and few minors. - Updated test cert to include subjectAltName extensions. - Added SSL certificate dump function. - Updated max number of socket async operations in Symbian sample apps (RSocketServ::Connect()) to 32 (was default 8). git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3106 74dad513-b988-da41-8d7b-12977e46ad98 --- build.symbian/pjlib.mmp | 3 +- build.symbian/pjlib_test.mmp | 4 +- build.symbian/symbian_ua.mmp | 3 +- pjlib/build/Makefile | 3 +- pjlib/build/cacert.pem | 31 +- pjlib/build/pjlib.dsp | 4 + pjlib/build/pjlib.vcproj | 4 + pjlib/build/privkey.pem | 38 +- pjlib/include/pj/ssl_sock.h | 177 ++++++++- pjlib/src/pj/ssl_sock_common.c | 95 +++++ pjlib/src/pj/ssl_sock_dump.c | 147 ++++++++ pjlib/src/pj/ssl_sock_ossl.c | 414 ++++++++++++++++----- pjlib/src/pj/ssl_sock_symbian.cpp | 152 +++++++- pjlib/src/pjlib-test/ssl_sock.c | 213 ++++++----- pjsip-apps/src/pjsua/pjsua_app.c | 80 ++++ pjsip-apps/src/symbian_ua/ua.cpp | 60 ++- .../src/symbian_ua_gui/group/symbian_ua_gui.mmp | 1 + pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp | 2 +- pjsip/include/pjsip/sip_endpoint.h | 29 ++ pjsip/include/pjsip/sip_transport.h | 143 ++++++- pjsip/include/pjsip/sip_transport_tls.h | 64 +++- pjsip/include/pjsua-lib/pjsua.h | 19 + pjsip/src/pjsip/sip_endpoint.c | 16 + pjsip/src/pjsip/sip_transport.c | 94 ++++- pjsip/src/pjsip/sip_transport_tcp.c | 87 +++-- pjsip/src/pjsip/sip_transport_tls.c | 336 +++++++++++++---- pjsip/src/pjsip/sip_util.c | 20 +- pjsip/src/pjsua-lib/pjsua_core.c | 8 +- 28 files changed, 1837 insertions(+), 410 deletions(-) create mode 100644 pjlib/src/pj/ssl_sock_dump.c diff --git a/build.symbian/pjlib.mmp b/build.symbian/pjlib.mmp index 65ffcc5a..3765e0f5 100644 --- a/build.symbian/pjlib.mmp +++ b/build.symbian/pjlib.mmp @@ -49,6 +49,8 @@ SOURCE pool_buf.c SOURCE pool_caching.c SOURCE rand.c SOURCE rbtree.c +SOURCE ssl_sock_common.c +SOURCE ssl_sock_dump.c SOURCE sock_common.c SOURCE sock_qos_common.c SOURCE types.c @@ -72,7 +74,6 @@ SOURCE os_timestamp_common.c SOURCE os_time_unix.c SOURCE os_timestamp_posix.c SOURCE pool_policy_new.cpp -SOURCE ssl_sock_common.c SOURCE ssl_sock_symbian.cpp SOURCE sock_symbian.cpp SOURCE sock_select_symbian.cpp diff --git a/build.symbian/pjlib_test.mmp b/build.symbian/pjlib_test.mmp index e48a332c..65b654cf 100644 --- a/build.symbian/pjlib_test.mmp +++ b/build.symbian/pjlib_test.mmp @@ -69,7 +69,9 @@ SYSTEMINCLUDE ..\pjlib\include SYSTEMINCLUDE \epoc32\include SYSTEMINCLUDE \epoc32\include\libc -LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib securesocket.lib +LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib +LIBRARY securesocket.lib x509.lib crypto.lib x500.lib + #ifdef WINSCW STATICLIBRARY eexe.lib ecrt0.lib #endif diff --git a/build.symbian/symbian_ua.mmp b/build.symbian/symbian_ua.mmp index 9514cde3..fc5c8aee 100644 --- a/build.symbian/symbian_ua.mmp +++ b/build.symbian/symbian_ua.mmp @@ -71,7 +71,8 @@ STATICLIBRARY libresample.lib STATICLIBRARY eexe.lib ecrt0.lib #endif -LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib securesocket.lib +LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib +LIBRARY securesocket.lib x509.lib crypto.lib x500.lib // The default 8KB seems to be insufficient with all bells and // whistles turned on diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile index a3482e00..1e0e88f9 100644 --- a/pjlib/build/Makefile +++ b/pjlib/build/Makefile @@ -26,7 +26,8 @@ export PJLIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \ pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \ rbtree.o sock_common.o sock_qos_common.o sock_qos_bsd.o \ - ssl_sock_common.o ssl_sock_ossl.o string.o timer.o types.o + ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \ + string.o timer.o types.o export PJLIB_CFLAGS += $(_CFLAGS) ############################################################################### diff --git a/pjlib/build/cacert.pem b/pjlib/build/cacert.pem index 0a2ee45b..cfce0920 100644 --- a/pjlib/build/cacert.pem +++ b/pjlib/build/cacert.pem @@ -1,21 +1,14 @@ -----BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgIJAPqAwYU5OQLXMA0GCSqGSIb3DQEBBQUAMEYxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVwanNpcDESMBAG -A1UEAwwJcGpzaXAubGFiMB4XDTA5MTAyMjE3MTczN1oXDTE5MTAyMDE3MTczN1ow -RjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoMBXBq -c2lwMRIwEAYDVQQDDAlwanNpcC5sYWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQDWWvnL+oSC0Q6OwGLt2YuhXTEzIVv3B+SGQ7tajB6H3WXVeq+1NmU9 -Yzca33g4FRrU7n4smYmKLzm1aniBhNmJjA+t+gbyizKnLMaCLoG52tUoULcANGKU -aGwlmvZFugDn2eVg6UfUfRzEGbV3q3a/PzSsOEPwsMeF3YMQJPhkoyPQLtWgUXgP -89Nyq3XjGGtw/qmUgQjE8a6/P0yXc+myI0hmApmZ9nB3YmlB5W3q6WoU2gGhLXf4 -12rH/LgdnPhM4ijS554Kv9EcUDdQTTrm6bYg66tj+qTet7DolUOlTZ3vKpuCK3tt -eK9CbNPVzsMsB3yCALSLzQ347pIwfLaJAgMBAAGjUDBOMB0GA1UdDgQWBBRE/VNp -kNQmLEXKQ+NM4bOVj95zYTAfBgNVHSMEGDAWgBRE/VNpkNQmLEXKQ+NM4bOVj95z -YTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCj/gzJKTOZDEBD+zr7 -lvbVctiYE9o8ObxZQsnl/6zI2V9H/2yc1sqQyjBupzw6c37ehvk30yIyUfD3ts87 -2xaJ5VgtgUI3FI5DQ+ASyQXmDawUEmXIGqHb2gDrXBQLd6uMpvNDNW+7TouuniyA -F12JUCNITeXaVJ0c8d4A9J9DlszBfYUzI45yIQu1gbpnpH74Sp/hG77EMxrRau+x -EFFmV7gAmkCgOBnXm8jTKqNre/GfLfO7w2xoLsubSLnK46U3iLGBIJJRVGu3UQuN -k1o7CiIKf0SSWj1bQI99ipTj8obBKRqj1nSbgKF/U6FIfd8DGcVvbJCSAG2czzyA -5tdA +MIICNDCCAZ2gAwIBAgIJAIa9mZggMk2WMA0GCSqGSIb3DQEBBAUAMDMxEjAQBgNV +BAMTCXBqc2lwLmxhYjEdMBsGCSqGSIb3DQEJARYOdGVzdEBwanNpcC5sYWIwHhcN +MTAwMjEwMDkwNTQ0WhcNMjAwMjA4MDkwNTQ0WjAzMRIwEAYDVQQDEwlwanNpcC5s +YWIxHTAbBgkqhkiG9w0BCQEWDnRlc3RAcGpzaXAubGFiMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDI9T0Pf+1gKOTOAGEpZ481Q6xfm5vz6n1+6udxzQtfPKlQ +rPD5x5im2u3tmy6ABxZeY5tCdeikBPiGlc5bRIRng6KM8kidkg3gEhwhRUxHCMWb +mBpkz7rFERf/pWAOCqYCiy1RT8QrK+XOFoFdJhdF85UPDEUw+pHEsYetTDs9RQID +AQABo1AwTjBMBgNVHREERTBDgglwanNpcC5sYWKCDXNpcC5wanNpcC5sYWKBDnRl +c3RAcGpzaXAubGFihhFzaXA6c2lwLnBqc2lwLmxhYocEfwAAATANBgkqhkiG9w0B +AQQFAAOBgQCLPl/WF1QvjT36kVLH0nxfHwDOJuAzlh6nv9rYBviOLw9FTEMgW6hA +oG55YSdVjTnMynTMOH/kVp4Vxlk46A8neE+/LI8RPh6lJh52vb+iPAtBpsQoq06T ++u4DfJcN8Y/jy+QAn78jryKjwKuZWfuWny9gxsLWMUbH5Bc6v6wfQQ== -----END CERTIFICATE----- diff --git a/pjlib/build/pjlib.dsp b/pjlib/build/pjlib.dsp index 6977f8a9..d63165eb 100644 --- a/pjlib/build/pjlib.dsp +++ b/pjlib/build/pjlib.dsp @@ -340,6 +340,10 @@ SOURCE=..\src\pj\ssl_sock_common.c # End Source File # Begin Source File +SOURCE=..\src\pj\ssl_sock_dump.c +# End Source File +# Begin Source File + SOURCE=..\src\pj\ssl_sock_ossl.c # End Source File # Begin Source File diff --git a/pjlib/build/pjlib.vcproj b/pjlib/build/pjlib.vcproj index d151dfbb..9c3941bb 100644 --- a/pjlib/build/pjlib.vcproj +++ b/pjlib/build/pjlib.vcproj @@ -5648,6 +5648,10 @@ RelativePath="..\src\pj\ssl_sock_common.c" > + + diff --git a/pjlib/build/privkey.pem b/pjlib/build/privkey.pem index 3241be20..44c4b4e6 100644 --- a/pjlib/build/privkey.pem +++ b/pjlib/build/privkey.pem @@ -1,27 +1,15 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA1lr5y/qEgtEOjsBi7dmLoV0xMyFb9wfkhkO7Woweh91l1Xqv -tTZlPWM3Gt94OBUa1O5+LJmJii85tWp4gYTZiYwPrfoG8osypyzGgi6BudrVKFC3 -ADRilGhsJZr2RboA59nlYOlH1H0cxBm1d6t2vz80rDhD8LDHhd2DECT4ZKMj0C7V -oFF4D/PTcqt14xhrcP6plIEIxPGuvz9Ml3PpsiNIZgKZmfZwd2JpQeVt6ulqFNoB -oS13+Ndqx/y4HZz4TOIo0ueeCr/RHFA3UE065um2IOurY/qk3rew6JVDpU2d7yqb -git7bXivQmzT1c7DLAd8ggC0i80N+O6SMHy2iQIDAQABAoIBAQCAke7Ujz2d7WDq -9LAh8+NRdUFGZtLvd9d1RPkCVZsWaRBknIL5kVfmGzV5M+K62MXQRACAJdOeg7b8 -fpErNpD4dH8PHjG+lwlZxnyGpvh+jqhd1xP81m7ujzeW0ry2k9tpNYPkveespyJy -6Oy0i67dBT9FsTXnD1GNlJDBRTuLuEkTTBqbn/2s3+gUfChJ4HPmYMeO9HU4PcfM -yUsHatBiIkXiCKdDZVMDr5AUVD0Wo3uHPGJ8ZreURAjH+ldG09+/EsVoPberbrDZ -ZxJ70VKG+ZZTY8HZr9OsZhDZDrHiw9PdG7Hvg7bCvv+gDzZ/z8F+7YHjRjmD5Tp5 -Ex5hDco1AoGBAPfdfzwmqb79AXwYH0HZkkl2EXpzbR9LRgvWlOMSN3GlZhusvGQR -up6iGk9QnmoEtQS2IAuK4JT3r+yoM/20Nadq5ZUpkZ49SHuZ6+eZpotv3Fh8Oay8 -TAi2vBGM7EQPUOjPOWMRaYGBz3FT/GvUGPTeQ8jYt1gy8F18+A8xD8pTAoGBAN1j -7+yTly+M47U6mIUXcwoelaS4f/kMcwKHO0O182S4ktfjzc3TpQbHm68ws1rB3iFZ -SFOP/d04tVxZqPBhN2SpXRHGqTJxXthdTbu3scLMedlf4jY11SRiHX4PDnoBQ1GJ -NpdkMoex25Fw3AqSVpP61zo8sJkqpqjFfeQDbfgzAoGBAKyGx1ZmDwc6cjsfSzp5 -p+JsRVQ3XcBHk9UPooi/mEoJd55RyLvav0xFxwxoMCvZZOqHnpyKKTJniVOv7Khu -NF55AJ6n1Y0QWRB3ngWSJKOv0+7fYQHD+yShlRyeO6JQCuBRxT8Y0phrc6oNbIjd -lBV1VDdL6aqBol9gagWg/72zAoGBAK1rAx1F3z+YFSZ459AZNjvPCVkmTNhBMDXi -yEGZ3TYgfqYuA6AfET3mTcVFWLjW87EbxtPuDuWi7i2Q7gydmk53fDfYbeDdfXXu -YF2S3uPAWBI2UXQ1ZuhBEukT0jsvkhPkb6bXDd3NLDkZNsPxLXBtJPqxX4QbLME3 -Mg3RweqRAoGAJ4iXP2b4XWhg17qpDtpn8nhFxLNdhxdaDSKRL8oKQLteds3wS0fi -ZlaU1P9a3ygTpNquKlmdLJTQEDAVjV5DDlrQAPxtSSytHulNzXRMQFaeydar3Ssv -J07BPdQs6JEgV071rGGzBcL8ulo7qCdnGxU6GmhLkS4MBbTuqR6jmgU= +MIICXgIBAAKBgQDI9T0Pf+1gKOTOAGEpZ481Q6xfm5vz6n1+6udxzQtfPKlQrPD5 +x5im2u3tmy6ABxZeY5tCdeikBPiGlc5bRIRng6KM8kidkg3gEhwhRUxHCMWbmBpk +z7rFERf/pWAOCqYCiy1RT8QrK+XOFoFdJhdF85UPDEUw+pHEsYetTDs9RQIDAQAB +AoGAGV+1xQY/H7wqH8S2f/begzg3RJ8uUt8R13urm5frTqwnKNOdXbyRDshn8G9+ +sJW0gliLWxnuNP+Xrc6ujqGZIguK/yAxJ3LprAN2Ay1lW2ONyZNMquBeIY5Txhyy +SnU7U+NQYgA3+w9T7O7YQ575TTDm2gri558jIx8t55Wo9sUCQQDtjfGZ3sYXwpxR +MvtdtfwDxSKhf6glT6dn7/37KITBZXFy6Eb/tHrEEUuwR46g30vTd2JElCB+QExu +4sZDt813AkEA2I/WXdGVRXtHzVivf3AnqWyXfrfAAXlBmEkgPyIPwE1+mxeNxkU7 +TRn0MOqAfbQW4+GRIYCKSBLodRnRq2iKIwJBAJLYa8DyNQH7CyYmnbwQAvlRo1ax +0v89ff6CHD5ljar/SmH9s+XdawZIqsENet13KyhNZDGAX5WrqZPiGy1BMYECQQC1 +FREawfUfdEZF3rJgzVdcxACpZNyYXtwKipr8L28cTbBf3wIdmCZOAjW98VgfxEaf +pi3E5ca7HZRi1oQL4A4hAkEA5koHCQYl+5PDjbLtxl0VyVCpmT9BrcZ99MS+ZEaW +2+HpKIhXrEFxePQaWbCaW7gjKmKUwC0qqu0moedqJC3mzg== -----END RSA PRIVATE KEY----- diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h index 9c392143..f9fc69b0 100644 --- a/pjlib/include/pj/ssl_sock.h +++ b/pjlib/include/pj/ssl_sock.h @@ -60,17 +60,126 @@ typedef struct pj_ssl_sock_t pj_ssl_sock_t; typedef struct pj_ssl_cert_t pj_ssl_cert_t; +typedef enum pj_ssl_cert_verify_flag_t +{ + /** + * No error in verification. + */ + PJ_SSL_CERT_ESUCCESS = 0, + + /** + * The issuer certificate cannot be found. + */ + PJ_SSL_CERT_EISSUER_NOT_FOUND = (1 << 0), + + /** + * The certificate is untrusted. + */ + PJ_SSL_CERT_EUNTRUSTED = (1 << 1), + + /** + * The certificate has expired or not yet valid. + */ + PJ_SSL_CERT_EVALIDITY_PERIOD = (1 << 2), + + /** + * One or more fields of the certificate cannot be decoded due to + * invalid format. + */ + PJ_SSL_CERT_EINVALID_FORMAT = (1 << 3), + + /** + * The certificate cannot be used for the specified purpose. + */ + PJ_SSL_CERT_EINVALID_PURPOSE = (1 << 4), + + /** + * The issuer info in the certificate does not match to the (candidate) + * issuer certificate, e.g: issuer name not match to subject name + * of (candidate) issuer certificate. + */ + PJ_SSL_CERT_EISSUER_MISMATCH = (1 << 5), + + /** + * The CRL certificate cannot be found or cannot be read properly. + */ + PJ_SSL_CERT_ECRL_FAILURE = (1 << 6), + + /** + * The certificate has been revoked. + */ + PJ_SSL_CERT_EREVOKED = (1 << 7), + + /** + * The certificate chain length is too long. + */ + PJ_SSL_CERT_ECHAIN_TOO_LONG = (1 << 8), + + /** + * The server identity does not match to any identities specified in + * the certificate, e.g: subjectAltName extension, subject common name. + * This flag will only be set by application as SSL socket does not + * perform server identity verification. + */ + PJ_SSL_CERT_EIDENTITY_NOT_MATCH = (1 << 30), + + /** + * Unknown verification error. + */ + PJ_SSL_CERT_EUNKNOWN = (1 << 31) + +} pj_ssl_cert_verify_flag_t; + + +typedef enum pj_ssl_cert_name_type +{ + PJ_SSL_CERT_NAME_UNKNOWN = 0, + PJ_SSL_CERT_NAME_RFC822, + PJ_SSL_CERT_NAME_DNS, + PJ_SSL_CERT_NAME_URI, + PJ_SSL_CERT_NAME_IP +} pj_ssl_cert_name_type; + /** * Describe structure of certificate info. */ typedef struct pj_ssl_cert_info { - pj_str_t subject; /**< Subject. */ - pj_str_t issuer; /**< Issuer. */ - unsigned version; /**< Certificate version. */ - pj_time_val validity_start; /**< Validity start. */ - pj_time_val validity_end; /**< Validity end. */ - pj_bool_t validity_use_gmt; /**< Flag if validity date/time - use GMT. */ + + unsigned version; /**< Certificate version */ + + pj_uint8_t serial_no[20]; /**< Serial number, array of + octets, first index is + MSB */ + + struct { + pj_str_t cn; /**< Common name */ + pj_str_t info; /**< One line subject, fields + are separated by slash */ + } subject; /**< Subject */ + + struct { + pj_str_t cn; /**< Common name */ + pj_str_t info; /**< One line subject, fields + are separated by slash.*/ + } issuer; /**< Issuer */ + + struct { + pj_time_val start; /**< Validity start */ + pj_time_val end; /**< Validity end */ + pj_bool_t gmt; /**< Flag if validity date/time + use GMT */ + } validity; /**< Validity */ + + struct { + unsigned cnt; /**< # of entry */ + struct { + pj_ssl_cert_name_type type; + /**< Name type */ + pj_str_t name; /**< The name */ + } *entry; /**< Subject alt name entry */ + } subj_alt_name; /**< Subject alternative + name extension */ + } pj_ssl_cert_info; @@ -93,6 +202,39 @@ PJ_DECL(pj_status_t) pj_ssl_cert_load_from_files(pj_pool_t *pool, pj_ssl_cert_t **p_cert); +/** + * Dump SSL certificate info. + * + * @param ci The certificate info. + * @param prefix Prefix string for each line. + * @param buf The buffer where certificate info will be printed on. + * @param buf_size The buffer size. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci, + const char *prefix, + char *buf, + pj_size_t buf_size); + + +/** + * Get SSL certificate verification error messages from verification status. + * + * @param verify_status The SSL certificate verification status. + * @param error_strings Array of strings to receive the verification error + * messages. + * @param count On input it specifies maximum error messages should be + * retrieved. On output it specifies the number of error + * messages retrieved. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_cert_verify_error_st(pj_uint32_t verify_status, + const char *error_strings[], + unsigned *count); + + /** * Cipher suites enumeration. */ @@ -363,13 +505,18 @@ typedef struct pj_ssl_sock_info /** * Describes active local certificate info. */ - pj_ssl_cert_info local_cert_info; + pj_ssl_cert_info *local_cert_info; /** * Describes active remote certificate info. */ - pj_ssl_cert_info remote_cert_info; - + pj_ssl_cert_info *remote_cert_info; + + /** + * Status of peer certificate verification. + */ + pj_uint32_t verify_status; + } pj_ssl_sock_info; @@ -523,11 +670,11 @@ typedef struct pj_ssl_sock_param pj_bool_t require_client_cert; /** - * When secure socket is acting as client (perform outgoing connection) - * and it needs to verify server name (e.g: host or domain name) by - * matching it to the name specified in the server certificate. This - * setting is useful when the server is hosting multiple domains for - * the same listening socket. + * Server name indication. When secure socket is acting as client + * (perform outgoing connection) and the server may host multiple + * 'virtual' servers at a single underlying network address, setting + * this will allow client to tell the server a name of the server + * it is contacting. * * Default value is zero/not-set. */ diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c index 127099c6..988a8b2a 100644 --- a/pjlib/src/pj/ssl_sock_common.c +++ b/pjlib/src/pj/ssl_sock_common.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include #include #include @@ -128,6 +129,7 @@ PJ_DEF(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param) } +/* Get cipher name string */ PJ_DEF(const char*) pj_ssl_cipher_name(pj_ssl_cipher cipher) { unsigned i, n; @@ -140,3 +142,96 @@ PJ_DEF(const char*) pj_ssl_cipher_name(pj_ssl_cipher cipher) return NULL; } + + + + +PJ_DEF(pj_status_t) pj_ssl_cert_verify_error_st(pj_uint32_t verify_status, + const char *error_strings[], + unsigned *count) +{ + unsigned i = 0, shift_idx = 0; + unsigned unknown = 0; + pj_uint32_t errs; + + PJ_ASSERT_RETURN(error_strings && count, PJ_EINVAL); + + if (verify_status == PJ_SSL_CERT_ESUCCESS && *count) { + error_strings[0] = "OK"; + *count = 1; + return PJ_SUCCESS; + } + + errs = verify_status; + + while (errs && i < *count) { + pj_uint32_t err; + const char *p = NULL; + + if ((errs & 1) == 0) { + shift_idx++; + errs >>= 1; + continue; + } + + err = (1 << shift_idx); + + switch (err) { + case PJ_SSL_CERT_EISSUER_NOT_FOUND: + p = "The issuer certificate cannot be found"; + break; + case PJ_SSL_CERT_EUNTRUSTED: + p = "The certificate is untrusted"; + break; + case PJ_SSL_CERT_EVALIDITY_PERIOD: + p = "The certificate has expired or not yet valid"; + break; + case PJ_SSL_CERT_EINVALID_FORMAT: + p = "One or more fields of the certificate cannot be decoded " + "due to invalid format"; + break; + case PJ_SSL_CERT_EISSUER_MISMATCH: + p = "The issuer info in the certificate does not match to the " + "(candidate) issuer certificate"; + break; + case PJ_SSL_CERT_ECRL_FAILURE: + p = "The CRL certificate cannot be found or cannot be read " + "properly"; + break; + case PJ_SSL_CERT_EREVOKED: + p = "The certificate has been revoked"; + break; + case PJ_SSL_CERT_EINVALID_PURPOSE: + p = "The certificate or CA certificate cannot be used for the " + "specified purpose"; + break; + case PJ_SSL_CERT_ECHAIN_TOO_LONG: + p = "The certificate chain length is too long"; + break; + case PJ_SSL_CERT_EIDENTITY_NOT_MATCH: + p = "The server identity does not match to any identities " + "specified in the certificate"; + break; + case PJ_SSL_CERT_EUNKNOWN: + default: + unknown++; + break; + } + + /* Set error string */ + if (p) + error_strings[i++] = p; + + /* Next */ + shift_idx++; + errs >>= 1; + } + + /* Unknown error */ + if (unknown && i < *count) + error_strings[i++] = "Unknown verification error"; + + *count = i; + + return PJ_SUCCESS; +} diff --git a/pjlib/src/pj/ssl_sock_dump.c b/pjlib/src/pj/ssl_sock_dump.c new file mode 100644 index 00000000..45a6f7ee --- /dev/null +++ b/pjlib/src/pj/ssl_sock_dump.c @@ -0,0 +1,147 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 Teluu Inc. (http://www.teluu.com) + * + * 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 +#include +#include +#include + + +/* Only build when PJ_HAS_SSL_SOCK is enabled */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0 + +#define THIS_FILE "ssl_sock_dump.c" + +#define CHECK_BUF_LEN() \ + if ((len < 0) || ((p+=len) >= end)) { \ + *(p-1) = '\0'; \ + return PJ_ETOOSMALL; \ + } + +PJ_DEF(pj_status_t) pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci, + const char *prefix, + char *buf, + pj_size_t buf_size) +{ + const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + pj_parsed_time pt1; + pj_parsed_time pt2; + unsigned i; + int len = 0; + char *p, *end; + + p = buf; + end = buf + buf_size; + + pj_time_decode(&ci->validity.start, &pt1); + pj_time_decode(&ci->validity.end, &pt2); + + /* Version */ + len = pj_ansi_snprintf(p, end-p, "%sVersion : v%d\n", + prefix, ci->version); + CHECK_BUF_LEN(); + + /* Serial number */ + len = pj_ansi_snprintf(p, end-p, "%sSerial : ", prefix); + CHECK_BUF_LEN(); + + for (i = 0; i < sizeof(ci->serial_no) && !ci->serial_no[i]; ++i); + for (; i < sizeof(ci->serial_no); ++i) { + len = pj_ansi_snprintf(p, end-p, "%02X ", ci->serial_no[i]); + CHECK_BUF_LEN(); + } + *(p-1) = '\n'; + + /* Subject */ + len = pj_ansi_snprintf( p, end-p, "%sSubject : %.*s\n", prefix, + ci->subject.cn.slen, + ci->subject.cn.ptr); + CHECK_BUF_LEN(); + len = pj_ansi_snprintf( p, end-p, "%s %.*s\n", prefix, + ci->subject.info.slen, + ci->subject.info.ptr); + CHECK_BUF_LEN(); + + /* Issuer */ + len = pj_ansi_snprintf( p, end-p, "%sIssuer : %.*s\n", prefix, + ci->issuer.cn.slen, + ci->issuer.cn.ptr); + CHECK_BUF_LEN(); + len = pj_ansi_snprintf( p, end-p, "%s %.*s\n", prefix, + ci->issuer.info.slen, + ci->issuer.info.ptr); + CHECK_BUF_LEN(); + + /* Validity period */ + len = pj_ansi_snprintf( p, end-p, "%sValid from : %s %4d-%02d-%02d " + "%02d:%02d:%02d.%03d %s\n", prefix, + wdays[pt1.wday], pt1.year, pt1.mon+1, pt1.day, + pt1.hour, pt1.min, pt1.sec, pt1.msec, + (ci->validity.gmt? "GMT":"")); + CHECK_BUF_LEN(); + + len = pj_ansi_snprintf( p, end-p, "%sValid to : %s %4d-%02d-%02d " + "%02d:%02d:%02d.%03d %s\n", prefix, + wdays[pt2.wday], pt2.year, pt2.mon+1, pt2.day, + pt2.hour, pt2.min, pt2.sec, pt2.msec, + (ci->validity.gmt? "GMT":"")); + CHECK_BUF_LEN(); + + /* Subject alternative name extension */ + if (ci->subj_alt_name.cnt) { + unsigned i; + + len = pj_ansi_snprintf(p, end-p, "%ssubjectAltName extension\n", + prefix); + CHECK_BUF_LEN(); + + for (i = 0; i < ci->subj_alt_name.cnt; ++i) { + const char *type = NULL; + + switch(ci->subj_alt_name.entry[i].type) { + case PJ_SSL_CERT_NAME_RFC822: + type = "MAIL"; + break; + case PJ_SSL_CERT_NAME_DNS: + type = " DNS"; + break; + case PJ_SSL_CERT_NAME_URI: + type = " URI"; + break; + case PJ_SSL_CERT_NAME_IP: + type = " IP"; + break; + default: + break; + } + if (type) { + len = pj_ansi_snprintf( p, end-p, "%s %s : %.*s\n", prefix, + type, + ci->subj_alt_name.entry[i].name.slen, + ci->subj_alt_name.entry[i].name.ptr); + CHECK_BUF_LEN(); + } + } + } + + return PJ_SUCCESS; +} + + +#endif /* PJ_HAS_SSL_SOCK */ + diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c index 3b0e86fe..7fe0c435 100644 --- a/pjlib/src/pj/ssl_sock_ossl.c +++ b/pjlib/src/pj/ssl_sock_ossl.c @@ -45,6 +45,7 @@ #include #include #include +#include #ifdef _MSC_VER @@ -152,6 +153,7 @@ struct pj_ssl_sock_t enum ssl_state ssl_state; pj_ioqueue_op_key_t handshake_op_key; pj_timer_entry timer; + pj_status_t verify_status; pj_sock_t sock; pj_activesock_t *asock; @@ -207,13 +209,17 @@ static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock); #define PJ_SSL_ERRNO_SPACE_SIZE PJ_ERRNO_SPACE_SIZE -#define GET_SSL_STATUS(status) { \ - unsigned long e = ERR_get_error();\ - status = ERR_GET_LIB(e)*300 + ERR_GET_REASON(e);\ +#define STATUS_FROM_SSL_ERR(err, status) { \ + status = ERR_GET_LIB(err)*300 + ERR_GET_REASON(err);\ pj_assert(status < PJ_SSL_ERRNO_SPACE_SIZE);\ if (status) status += PJ_SSL_ERRNO_START;\ } +#define GET_SSL_STATUS(status) { \ + unsigned long e = ERR_get_error();\ + STATUS_FROM_SSL_ERR(e, status);\ +} + /* * Get error string of OpenSSL. */ @@ -235,7 +241,11 @@ static pj_str_t ssl_strerror(pj_status_t status, { const char *tmp = NULL; - tmp = ERR_reason_error_string(ssl_err); + + if (ssl_err >= 300) + tmp = ERR_reason_error_string(ssl_err); + else + tmp = X509_verify_cert_error_string(ssl_err); if (tmp) { pj_ansi_strncpy(buf, tmp, bufsize); @@ -263,6 +273,9 @@ static int openssl_reg_strerr; static pj_ssl_cipher openssl_ciphers[100]; static unsigned openssl_cipher_num; +/* OpenSSL application data index */ +static int sslsock_idx; + /* Initialize OpenSSL */ static pj_status_t init_openssl(void) @@ -329,6 +342,9 @@ static pj_status_t init_openssl(void) openssl_cipher_num = n; } + /* Create OpenSSL application data index for SSL socket */ + sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL); + return PJ_SUCCESS; } @@ -355,8 +371,107 @@ static int password_cb(char *buf, int num, int rwflag, void *user_data) } -/* Create and initialize new SSL context */ -static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx) +/* SSL password callback. */ +static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + pj_ssl_sock_t *ssock; + SSL *ossl_ssl; + int err; + + /* Get SSL instance */ + ossl_ssl = X509_STORE_CTX_get_ex_data(x509_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + pj_assert(ossl_ssl); + + /* Get SSL socket instance */ + ssock = SSL_get_ex_data(ossl_ssl, sslsock_idx); + pj_assert(ssock); + + /* Store verification status */ + err = X509_STORE_CTX_get_error(x509_ctx); + switch (err) { + case X509_V_OK: + break; + + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND; + break; + + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + break; + + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + break; + + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + ssock->verify_status |= PJ_SSL_CERT_ECRL_FAILURE; + break; + + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + break; + + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH; + break; + + case X509_V_ERR_CERT_REVOKED: + ssock->verify_status |= PJ_SSL_CERT_EREVOKED; + break; + + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_INVALID_CA: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_PURPOSE; + break; + + case X509_V_ERR_CERT_CHAIN_TOO_LONG: /* not really used */ + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + ssock->verify_status |= PJ_SSL_CERT_ECHAIN_TOO_LONG; + break; + + /* Unknown errors */ + case X509_V_ERR_OUT_OF_MEM: + default: + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + break; + } + + /* When verification is not requested just return ok here, however + * application can still get the verification status. + */ + if (PJ_FALSE == ssock->param.verify_peer) + preverify_ok = 1; + + return preverify_ok; +} + +/* Setting SSL sock cipher list */ +static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock); + + +/* Create and initialize new SSL context and instance */ +static pj_status_t create_ssl(pj_ssl_sock_t *ssock) { SSL_METHOD *ssl_method; SSL_CTX *ctx; @@ -364,7 +479,7 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx) int mode, rc; pj_status_t status; - pj_assert(ssock && p_ctx); + pj_assert(ssock); cert = ssock->cert; @@ -393,7 +508,7 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx) return PJ_EINVAL; } - /* Create SSL context for the listener */ + /* Create SSL context */ ctx = SSL_CTX_new(ssl_method); if (ctx == NULL) { GET_SSL_STATUS(status); @@ -455,29 +570,55 @@ static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx) } } - - /* SSL verification options */ - if (ssock->param.verify_peer) { - mode = SSL_VERIFY_PEER; - } else { - mode = SSL_VERIFY_NONE; + /* Create SSL instance */ + ssock->ossl_ctx = ctx; + ssock->ossl_ssl = SSL_new(ssock->ossl_ctx); + if (ssock->ossl_ssl == NULL) { + GET_SSL_STATUS(status); + return status; } + /* Set SSL sock as application data of SSL instance */ + SSL_set_ex_data(ssock->ossl_ssl, sslsock_idx, ssock); + + /* SSL verification options */ + mode = SSL_VERIFY_PEER; if (ssock->is_server && ssock->param.require_client_cert) - mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER; + mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - SSL_CTX_set_verify(ctx, mode, NULL); + SSL_set_verify(ssock->ossl_ssl, mode, &verify_cb); - *p_ctx = ctx; + /* Set cipher list */ + status = set_cipher_list(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Setup SSL BIOs */ + ssock->ossl_rbio = BIO_new(BIO_s_mem()); + ssock->ossl_wbio = BIO_new(BIO_s_mem()); + BIO_set_close(ssock->ossl_rbio, BIO_CLOSE); + BIO_set_close(ssock->ossl_wbio, BIO_CLOSE); + SSL_set_bio(ssock->ossl_ssl, ssock->ossl_rbio, ssock->ossl_wbio); return PJ_SUCCESS; } -/* Destroy SSL context */ -static void destroy_ssl_ctx(SSL_CTX *ctx) +/* Destroy SSL context and instance */ +static void destroy_ssl(pj_ssl_sock_t *ssock) { - SSL_CTX_free(ctx); + /* Destroy SSL instance */ + if (ssock->ossl_ssl) { + SSL_shutdown(ssock->ossl_ssl); + SSL_free(ssock->ossl_ssl); /* this will also close BIOs */ + ssock->ossl_ssl = NULL; + } + + /* Destroy SSL context */ + if (ssock->ossl_ctx) { + SSL_CTX_free(ssock->ossl_ctx); + ssock->ossl_ctx = NULL; + } /* Potentially shutdown OpenSSL library if this is the last * context exists. @@ -491,15 +632,8 @@ static void reset_ssl_sock_state(pj_ssl_sock_t *ssock) { ssock->ssl_state = SSL_STATE_NULL; - if (ssock->ossl_ssl) { - SSL_shutdown(ssock->ossl_ssl); - SSL_free(ssock->ossl_ssl); /* this will also close BIOs */ - ssock->ossl_ssl = NULL; - } - if (ssock->ossl_ctx) { - destroy_ssl_ctx(ssock->ossl_ctx); - ssock->ossl_ctx = NULL; - } + destroy_ssl(ssock); + if (ssock->asock) { pj_activesock_close(ssock->asock); ssock->asock = NULL; @@ -639,42 +773,143 @@ static pj_bool_t parse_ossl_asn1_time(pj_time_val *tv, pj_bool_t *gmt, } -/* Get certificate info from OpenSSL X509 */ +/* Get Common Name field string from a general name string */ +static void get_cn_from_gen_name(const pj_str_t *gen_name, pj_str_t *cn) +{ + pj_str_t CN_sign = {"/CN=", 4}; + char *p, *q; + + pj_bzero(cn, sizeof(cn)); + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 4; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, '/'); + if (q) + cn->slen = q - p; +} + + +/* Get certificate info from OpenSSL X509, in case the certificate info + * hal already populated, this function will check if the contents need + * to be updated by inspecting the issuer and the serial number. + */ 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_bool_t update_needed; + char buf[512]; + pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */ + pj_uint8_t *p; + unsigned len; + GENERAL_NAMES *names = NULL; + + pj_assert(pool && ci && x); + + /* Get issuer */ + X509_NAME_oneline(X509_get_issuer_name(x), buf, sizeof(buf)); - pj_assert(pool && ci); + /* Get serial no */ + p = (pj_uint8_t*) M_ASN1_STRING_data(X509_get_serialNumber(x)); + len = M_ASN1_STRING_length(X509_get_serialNumber(x)); + if (len > sizeof(ci->serial_no)) + len = sizeof(ci->serial_no); + pj_memcpy(serial_no + sizeof(ci->serial_no) - len, p, len); - if (!x) { - pj_bzero(ci, sizeof(pj_ssl_cert_info)); + /* Check if the contents need to be updated. */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || + pj_memcmp(ci->serial_no, serial_no, sizeof(ci->serial_no)); + if (!update_needed) return; - } - pj_bzero(&info, sizeof(info)); + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = X509_get_version(x) + 1; + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + get_cn_from_gen_name(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); - /* 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, + /* Subject */ + pj_strdup2(pool, &ci->subject.info, + X509_NAME_oneline(X509_get_subject_name(x), + buf, sizeof(buf))); + get_cn_from_gen_name(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + parse_ossl_asn1_time(&ci->validity.start, &ci->validity.gmt, X509_get_notBefore(x)); - parse_ossl_asn1_time(&info.validity_end, &info.validity_use_gmt, + parse_ossl_asn1_time(&ci->validity.end, &ci->validity.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; + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + names = (GENERAL_NAMES*) X509_get_ext_d2i(x, NID_subject_alt_name, + NULL, NULL); + } + if (names) { + unsigned i, cnt; + + cnt = sk_GENERAL_NAME_num(names); + ci->subj_alt_name.entry = pj_pool_calloc(pool, cnt, + sizeof(*ci->subj_alt_name.entry)); + + for (i = 0; i < cnt; ++i) { + unsigned char *p = 0; + pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN; + const GENERAL_NAME *name; + + name = sk_GENERAL_NAME_value(names, i); + + switch (name->type) { + case GEN_EMAIL: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GEN_DNS: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_DNS; + break; + case GEN_URI: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_URI; + break; + case GEN_IPADD: + p = ASN1_STRING_data(name->d.ip); + len = ASN1_STRING_length(name->d.ip); + type = PJ_SSL_CERT_NAME_IP; + break; + default: + break; + } + + if (p && len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + if (type == PJ_SSL_CERT_NAME_IP) { + int af = pj_AF_INET(); + if (len == sizeof(pj_in6_addr)) af = pj_AF_INET6(); + pj_inet_ntop2(af, p, buf, sizeof(buf)); + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + buf); + } else { + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + (char*)p); + OPENSSL_free(p); + } + ci->subj_alt_name.cnt++; + } + } + } } @@ -689,14 +924,22 @@ static void update_certs_info(pj_ssl_sock_t *ssock) /* 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! */ + if (x) { + get_cert_info(ssock->pool, &ssock->local_cert_info, x); + /* Don't free local's X509! */ + } else { + pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); + } /* 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); + if (x) { + get_cert_info(ssock->pool, &ssock->remote_cert_info, x); + /* Free peer's X509 */ + X509_free(x); + } else { + pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); + } } @@ -1231,29 +1474,10 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock, pj_sockaddr_cp(&ssock->rem_addr, src_addr); /* Create SSL context */ - status = create_ssl_ctx(ssock, &ssock->ossl_ctx); - if (status != PJ_SUCCESS) - goto on_return; - - /* Create SSL instance */ - ssock->ossl_ssl = SSL_new(ssock->ossl_ctx); - if (ssock->ossl_ssl == NULL) { - GET_SSL_STATUS(status); - goto on_return; - } - - /* Set cipher list */ - status = set_cipher_list(ssock); + status = create_ssl(ssock); if (status != PJ_SUCCESS) goto on_return; - /* Setup SSL BIOs */ - ssock->ossl_rbio = BIO_new(BIO_s_mem()); - ssock->ossl_wbio = BIO_new(BIO_s_mem()); - BIO_set_close(ssock->ossl_rbio, BIO_CLOSE); - BIO_set_close(ssock->ossl_wbio, BIO_CLOSE); - SSL_set_bio(ssock->ossl_ssl, ssock->ossl_rbio, ssock->ossl_wbio); - /* Prepare read buffer */ ssock->asock_rbuf = (void**)pj_pool_calloc(ssock->pool, ssock->param.async_cnt, @@ -1351,29 +1575,10 @@ static pj_bool_t asock_on_connect_complete (pj_activesock_t *asock, goto on_return; /* Create SSL context */ - status = create_ssl_ctx(ssock, &ssock->ossl_ctx); + status = create_ssl(ssock); if (status != PJ_SUCCESS) goto on_return; - /* Create SSL instance */ - ssock->ossl_ssl = SSL_new(ssock->ossl_ctx); - if (ssock->ossl_ssl == NULL) { - GET_SSL_STATUS(status); - goto on_return; - } - - /* Set cipher list */ - status = set_cipher_list(ssock); - if (status != PJ_SUCCESS) - goto on_return; - - /* Setup SSL BIOs */ - ssock->ossl_rbio = BIO_new(BIO_s_mem()); - ssock->ossl_wbio = BIO_new(BIO_s_mem()); - BIO_set_close(ssock->ossl_rbio, BIO_CLOSE); - BIO_set_close(ssock->ossl_wbio, BIO_CLOSE); - SSL_set_bio(ssock->ossl_ssl, ssock->ossl_rbio, ssock->ossl_wbio); - /* Prepare read buffer */ ssock->asock_rbuf = (void**)pj_pool_calloc(ssock->pool, ssock->param.async_cnt, @@ -1651,9 +1856,9 @@ PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock, pj_sockaddr_cp(&info->local_addr, &ssock->local_addr); if (info->established) { - /* Current cipher */ const SSL_CIPHER *cipher; + /* Current cipher */ cipher = SSL_get_current_cipher(ssock->ossl_ssl); info->cipher = (cipher->id & 0x00FFFFFF); @@ -1661,8 +1866,11 @@ PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock, 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; + info->local_cert_info = &ssock->local_cert_info; + info->remote_cert_info = &ssock->remote_cert_info; + + /* Verification status */ + info->verify_status = ssock->verify_status; } return PJ_SUCCESS; diff --git a/pjlib/src/pj/ssl_sock_symbian.cpp b/pjlib/src/pj/ssl_sock_symbian.cpp index efcf1702..ab808f62 100644 --- a/pjlib/src/pj/ssl_sock_symbian.cpp +++ b/pjlib/src/pj/ssl_sock_symbian.cpp @@ -127,6 +127,11 @@ public: return securesock_->CurrentCipherSuite(cipher); return KErrNotFound; } + const CX509Certificate *GetPeerCert() { + if (securesock_) + return securesock_->ServerCert(); + return NULL; + } private: enum ssl_state state_; @@ -409,6 +414,7 @@ typedef struct write_state_t { */ struct pj_ssl_sock_t { + pj_pool_t *pool; pj_ssl_sock_cb cb; void *user_data; @@ -434,9 +440,132 @@ struct pj_ssl_sock_t unsigned ciphers_num; pj_ssl_cipher *ciphers; pj_str_t servername; + pj_ssl_cert_info remote_cert_info; }; +static pj_str_t get_cert_name(pj_pool_t *pool, + const CX500DistinguishedName &name) +{ + TInt i; + char buf[1024]; + TUint8 *p; + TInt l = sizeof(buf); + + p = (TUint8*)buf; + for(i = 0; i < name.Count(); ++i) { + const CX520AttributeTypeAndValue &attr = name.Element(i); + + /* Print element separator */ + *p++ = '/'; + if (0 == --l) break; + + /* Print the type. */ + TPtr8 type(p, l); + type.Copy(attr.Type()); + p += type.Length(); + l -= type.Length(); + if (0 >= --l) break; + + /* Print equal sign */ + *p++ = '='; + if (0 == --l) break; + + /* Print the value. Let's just get the raw data here */ + TPtr8 value(p, l); + value.Copy(attr.EncodedValue().Mid(2)); + p += value.Length(); + l -= value.Length(); + if (0 >= --l) break; + } + + pj_str_t src, res; + pj_strset(&src, buf, sizeof(buf) - l); + pj_strdup(pool, &res, &src); + + return res; +} + +/* Get certificate info from CX509Certificate. + */ +static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, + const CX509Certificate *x) +{ + unsigned len; + + pj_assert(pool && ci && x); + + pj_bzero(ci, sizeof(*ci)); + + /* Version */ + ci->version = x->Version(); + + /* Serial number */ + len = x->SerialNumber().Length(); + if (len > sizeof(ci->serial_no)) + len = sizeof(ci->serial_no); + pj_memcpy(ci->serial_no + sizeof(ci->serial_no) - len, + x->SerialNumber().Ptr(), len); + + /* Subject */ + { + HBufC *subject = NULL; + TRAPD(err, subject = x->SubjectL()); + if (err == KErrNone) { + TPtr16 ptr16(subject->Des()); + len = ptr16.Length(); + TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len); + ptr8.Copy(ptr16); + pj_strset(&ci->subject.cn, (char*)ptr8.Ptr(), ptr8.Length()); + } + ci->subject.info = get_cert_name(pool, x->SubjectName()); + } + + /* Issuer */ + { + HBufC *issuer = NULL; + TRAPD(err, issuer = x->IssuerL()); + if (err == KErrNone) { + TPtr16 ptr16(issuer->Des()); + len = ptr16.Length(); + TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len); + ptr8.Copy(ptr16); + pj_strset(&ci->issuer.cn, (char*)ptr8.Ptr(), ptr8.Length()); + } + ci->issuer.info = get_cert_name(pool, x->IssuerName()); + } + + /* Validity */ + const CValidityPeriod &valid_period = x->ValidityPeriod(); + TTime base_time(TDateTime(1970, EJanuary, 0, 0, 0, 0, 0)); + TTimeIntervalSeconds tmp_sec; + valid_period.Start().SecondsFrom(base_time, tmp_sec); + ci->validity.start.sec = tmp_sec.Int(); + valid_period.Finish().SecondsFrom(base_time, tmp_sec); + ci->validity.end.sec = tmp_sec.Int(); +} + + +/* Update certificates info. This function should be called after handshake + * or renegotiation successfully completed. + */ +static void update_certs_info(pj_ssl_sock_t *ssock) +{ + const CX509Certificate *x; + + pj_assert(ssock && ssock->sock && + ssock->sock->GetState() == CPjSSLSocket::SSL_STATE_ESTABLISHED); + + /* Active remote certificate */ + x = ssock->sock->GetPeerCert(); + if (x) { + get_cert_info(ssock->pool, &ssock->remote_cert_info, x); + } else { + pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); + } +} + + /* * Get cipher list supported by SSL/TLS backend. */ @@ -504,6 +633,7 @@ PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool, ssock->write_state.start = ssock->write_state.buf; /* Init secure socket */ + ssock->pool = pool; ssock->sock_af = param->sock_af; ssock->sock_type = param->sock_type; ssock->cb = param->cb; @@ -643,7 +773,10 @@ PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock, /* Remote address */ pj_sockaddr_cp((pj_sockaddr_t*)&info->remote_addr, - (pj_sockaddr_t*)&ssock->rem_addr); + (pj_sockaddr_t*)&ssock->rem_addr); + + /* Certificates info */ + info->remote_cert_info = &ssock->remote_cert_info; } /* Protocol */ @@ -728,14 +861,20 @@ static void read_cb(int err, void *key) ssock->read_state.read_buf->SetLength(0); status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, ssock->read_state.flags); - if (status != PJ_EPENDING) { - /* Notify error */ - (*ssock->cb.on_data_read)(ssock, NULL, 0, status, NULL); - } } + /* Connection closed or something goes wrong */ if (status != PJ_SUCCESS && status != PJ_EPENDING) { - /* Connection closed or something goes wrong */ + /* Notify error */ + if (ssock->cb.on_data_read) { + pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, NULL, 0, + status, NULL); + if (!ret) { + /* We've been destroyed */ + return; + } + } + delete ssock->read_state.read_buf; delete ssock->read_state.orig_buf; ssock->read_state.read_buf = NULL; @@ -1001,6 +1140,7 @@ static void connect_cb(int err, void *key) status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err); if (status == PJ_SUCCESS) { ssock->established = PJ_TRUE; + update_certs_info(ssock); } else { delete ssock->sock; ssock->sock = NULL; diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c index 8302e8f1..6e0d4515 100644 --- a/pjlib/src/pjlib-test/ssl_sock.c +++ b/pjlib/src/pjlib-test/ssl_sock.c @@ -22,7 +22,7 @@ #define CERT_DIR "../build/" -#define CERT_CA_FILE NULL +#define CERT_CA_FILE CERT_DIR "cacert.pem" #define CERT_FILE CERT_DIR "cacert.pem" #define CERT_PRIVKEY_FILE CERT_DIR "privkey.pem" #define CERT_PRIVKEY_PASS "" @@ -83,26 +83,40 @@ struct test_state struct send_key send_key; /* send op key */ }; -static void dump_cert_info(const char *prefix, const pj_ssl_cert_info *ci) +static void dump_ssl_info(const pj_ssl_sock_info *si) { - 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":""))); + const char *tmp_st; + + /* Print cipher name */ + tmp_st = pj_ssl_cipher_name(si->cipher); + if (tmp_st == NULL) + tmp_st = "[Unknown]"; + PJ_LOG(3, ("", ".....Cipher: %s", tmp_st)); + + /* Print remote certificate info and verification result */ + if (si->remote_cert_info && si->remote_cert_info->subject.info.slen) + { + char buf[2048]; + const char *verif_msgs[32]; + unsigned verif_msg_cnt; + + /* Dump remote TLS certificate info */ + PJ_LOG(3, ("", ".....Remote certificate info:")); + pj_ssl_cert_info_dump(si->remote_cert_info, " ", buf, sizeof(buf)); + PJ_LOG(3,("", "\n%s", buf)); + + /* Dump remote TLS certificate verification result */ + verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs); + pj_ssl_cert_verify_error_st(si->verify_status, + verif_msgs, &verif_msg_cnt); + PJ_LOG(3,("", ".....Remote certificate verification result: %s", + (verif_msg_cnt == 1? verif_msgs[0]:""))); + if (verif_msg_cnt > 1) { + unsigned i; + for (i = 0; i < verif_msg_cnt; ++i) + PJ_LOG(3,("", "..... - %s", verif_msgs[i])); + } + } } @@ -130,25 +144,8 @@ static pj_bool_t ssl_on_connect_complete(pj_ssl_sock_t *ssock, pj_sockaddr_print((pj_sockaddr_t*)&info.remote_addr, buf2, sizeof(buf2), 1); PJ_LOG(3, ("", "...Connected %s -> %s!", buf1, buf2)); - if (st->is_verbose) { - const char *tmp_st; - - /* 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); - } - } + if (st->is_verbose) + dump_ssl_info(&info); /* Start reading data */ read_buf[0] = st->read_buf; @@ -198,6 +195,8 @@ static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock, pj_ssl_sock_get_user_data(ssock); struct test_state *st; void *read_buf[1]; + pj_ssl_sock_info info; + char buf[64]; pj_status_t status; PJ_UNUSED_ARG(src_addr_len); @@ -207,36 +206,17 @@ static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock, *st = *parent_st; pj_ssl_sock_set_user_data(newsock, st); - if (st->is_verbose) { - pj_ssl_sock_info info; - char buf[64]; - const char *tmp_st; - - 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)); + status = pj_ssl_sock_get_info(newsock, &info); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_get_info()", status); + goto on_return; + } - /* 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)); + pj_sockaddr_print(src_addr, buf, sizeof(buf), 1); + PJ_LOG(3, ("", "...Accepted connection from %s", buf)); - /* 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); - } - } + if (st->is_verbose) + dump_ssl_info(&info); /* Start reading data */ read_buf[0] = st->read_buf; @@ -460,6 +440,7 @@ static int https_client_test(unsigned ms_timeout) param.timer_heap = timer; param.timeout.sec = 0; param.timeout.msec = ms_timeout; + param.proto = PJ_SSL_SOCK_PROTO_SSL23; pj_time_val_normalize(¶m.timeout); status = pj_ssl_sock_create(pool, ¶m, &ssock); @@ -512,7 +493,8 @@ on_return: static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, - pj_ssl_cipher srv_cipher, pj_ssl_cipher cli_cipher) + pj_ssl_cipher srv_cipher, pj_ssl_cipher cli_cipher, + pj_bool_t req_client_cert, pj_bool_t client_provide_cert) { pj_pool_t *pool = NULL; pj_ioqueue_t *ioqueue = NULL; @@ -533,21 +515,6 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, 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_connect_complete = &ssl_on_connect_complete; @@ -562,10 +529,11 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); } - /* SERVER */ + /* === SERVER === */ param.proto = srv_proto; param.user_data = &state_serv; param.ciphers_num = (srv_cipher == -1)? 0 : 1; + param.require_client_cert = req_client_cert; ciphers[0] = srv_cipher; state_serv.pool = pool; @@ -578,9 +546,24 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, goto on_return; } - status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); - if (status != PJ_SUCCESS) { - goto on_return; + /* Set server 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; + } + + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } } status = pj_ssl_sock_start_accept(ssock_serv, pool, &addr, pj_sockaddr_get_len(&addr)); @@ -596,7 +579,7 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, pj_sockaddr_cp(&listen_addr, &info.local_addr); } - /* CLIENT */ + /* === CLIENT === */ param.proto = cli_proto; param.user_data = &state_cli; param.ciphers_num = (cli_cipher == -1)? 0 : 1; @@ -625,6 +608,28 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, goto on_return; } + /* Set cert for client */ + { + + if (!client_provide_cert) { + pj_str_t tmp1, tmp2; + + pj_strset2(&tmp1, (char*)CERT_CA_FILE); + pj_strset2(&tmp2, NULL); + status = pj_ssl_cert_load_from_files(pool, + &tmp1, &tmp2, &tmp2, &tmp2, + &cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + status = pj_ssl_sock_set_certificate(ssock_cli, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + status = pj_ssl_sock_start_connect(ssock_cli, pool, &addr, &listen_addr, pj_sockaddr_get_len(&addr)); if (status == PJ_SUCCESS) { ssl_on_connect_complete(ssock_cli, PJ_SUCCESS); @@ -1013,6 +1018,9 @@ static int perf_test(unsigned clients, unsigned ms_handshake_timeout) ssock_cli = pj_pool_calloc(pool, clients, sizeof(pj_ssl_sock_t*)); state_cli = pj_pool_calloc(pool, clients, sizeof(struct test_state)); + /* Get start timestamp */ + pj_gettimeofday(&start); + /* Setup clients */ for (i = 0; i < clients; ++i) { param.user_data = &state_cli[i]; @@ -1064,9 +1072,6 @@ static int perf_test(unsigned clients, unsigned ms_handshake_timeout) } } - /* Get start timestamp */ - pj_gettimeofday(&start); - /* Wait until everything has been sent/received or error */ while (clients_num) { @@ -1150,28 +1155,46 @@ int ssl_sock_test(void) PJ_LOG(3,("", "..echo test w/ TLSv1 and TLS_RSA_WITH_DES_CBC_SHA cipher")); ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_TLS1, - TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA); + TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA, + PJ_FALSE, PJ_FALSE); 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, PJ_SSL_SOCK_PROTO_SSL23, - TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA); + TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_FALSE, PJ_FALSE); if (ret != 0) return ret; PJ_LOG(3,("", "..echo test w/ incompatible proto")); ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_SSL3, - TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA); + TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA, + PJ_FALSE, PJ_FALSE); if (ret == 0) return PJ_EBUG; PJ_LOG(3,("", "..echo test w/ incompatible ciphers")); ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, - TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA); + TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_FALSE, PJ_FALSE); + if (ret == 0) + return PJ_EBUG; + + PJ_LOG(3,("", "..echo test w/ client cert required but not provided")); + ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, + TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_TRUE, PJ_FALSE); if (ret == 0) return PJ_EBUG; + PJ_LOG(3,("", "..echo test w/ client cert required and provided")); + ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, + TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_TRUE, PJ_TRUE); + if (ret != 0) + return ret; + PJ_LOG(3,("", "..client non-SSL (handshake timeout 5 secs)")); ret = client_non_ssl(5000); if (ret != 0) diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 42ce0127..d3cb8c82 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -2787,6 +2787,85 @@ static void on_mwi_info(pjsua_acc_id acc_id, pjsua_mwi_info *mwi_info) } +/* + * Transport status notification + */ +static pj_bool_t on_transport_state(pjsip_transport *tp, pj_uint32_t state, + const pjsip_transport_state_info *info) +{ + char host_port[128]; + + pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]", + (int)tp->remote_name.host.slen, + tp->remote_name.host.ptr, + tp->remote_name.port); + + if (state & PJSIP_TP_STATE_CONNECTED) { + PJ_LOG(3,(THIS_FILE, "SIP transport %s is connected to %s", + tp->type_name, host_port)); + } + else if (state & PJSIP_TP_STATE_ACCEPTED) { + PJ_LOG(3,(THIS_FILE, "SIP transport %s accepted %s", + tp->type_name, host_port)); + } + else if (state & PJSIP_TP_STATE_DISCONNECTED) { + char buf[100]; + + snprintf(buf, sizeof(buf), "SIP transport %s is disconnected from %s", + tp->type_name, host_port); + pjsua_perror(THIS_FILE, buf, info->status); + } + else if (state & PJSIP_TP_STATE_REJECTED) { + char buf[100]; + + snprintf(buf, sizeof(buf), "SIP transport %s rejected %s", + tp->type_name, host_port); + pjsua_perror(THIS_FILE, buf, info->status); + } + +#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 + + if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info && + (state == PJSIP_TP_STATE_CONNECTED || + (state & PJSIP_TP_STATE_TLS_VERIF_ERROR))) + { + pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info; + pj_ssl_sock_info *ssl_sock_info = (pj_ssl_sock_info*) + tls_info->ssl_sock_info; + char buf[2048]; + const char *verif_msgs[32]; + unsigned verif_msg_cnt; + + /* Dump server TLS certificate */ + pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, " ", + buf, sizeof(buf)); + PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf)); + + /* Dump server TLS certificate verification result */ + verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs); + pj_ssl_cert_verify_error_st(ssl_sock_info->verify_status, + verif_msgs, &verif_msg_cnt); + PJ_LOG(3,(THIS_FILE, "TLS cert verification result of %s : %s", + host_port, + (verif_msg_cnt == 1? verif_msgs[0]:""))); + if (verif_msg_cnt > 1) { + unsigned i; + for (i = 0; i < verif_msg_cnt; ++i) + PJ_LOG(3,(THIS_FILE, "- %s", verif_msgs[i])); + } + + if (state & PJSIP_TP_STATE_TLS_VERIF_ERROR && + !app_config.udp_cfg.tls_setting.verify_server) + { + PJ_LOG(3,(THIS_FILE, "PJSUA is configured to ignore TLS cert " + "verification errors")); + } + } + +#endif + return PJ_TRUE; +} + /* * Print buddy list. */ @@ -4383,6 +4462,7 @@ pj_status_t app_init(int argc, char *argv[]) app_config.cfg.cb.on_call_replaced = &on_call_replaced; app_config.cfg.cb.on_nat_detect = &on_nat_detect; app_config.cfg.cb.on_mwi_info = &on_mwi_info; + app_config.cfg.cb.on_transport_state = &on_transport_state; /* Set sound device latency */ if (app_config.capture_lat > 0) diff --git a/pjsip-apps/src/symbian_ua/ua.cpp b/pjsip-apps/src/symbian_ua/ua.cpp index 64e49aee..620e75a7 100644 --- a/pjsip-apps/src/symbian_ua/ua.cpp +++ b/pjsip-apps/src/symbian_ua/ua.cpp @@ -270,6 +270,63 @@ static void on_call_replaced(pjsua_call_id old_call_id, (int)new_ci.remote_info.slen, new_ci.remote_info.ptr)); } +/* + * Transport status notification + */ +static pj_bool_t on_transport_state(pjsip_transport *tp, pj_uint32_t state, + const pjsip_transport_state_info *info) +{ + char host_port[128]; + + pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]", + (int)tp->remote_name.host.slen, + tp->remote_name.host.ptr, + tp->remote_name.port); + + if (state & PJSIP_TP_STATE_CONNECTED) { + PJ_LOG(3,(THIS_FILE, "SIP transport %s is connected to %s", + tp->type_name, host_port)); + } + else if (state & PJSIP_TP_STATE_ACCEPTED) { + PJ_LOG(3,(THIS_FILE, "SIP transport %s accepted %s", + tp->type_name, host_port)); + } + else if (state & PJSIP_TP_STATE_DISCONNECTED) { + char buf[100]; + + snprintf(buf, sizeof(buf), "SIP transport %s is disconnected from %s", + tp->type_name, host_port); + pjsua_perror(THIS_FILE, buf, info->status); + } + else if (state & PJSIP_TP_STATE_REJECTED) { + char buf[100]; + + snprintf(buf, sizeof(buf), "SIP transport %s rejected %s", + tp->type_name, host_port); + pjsua_perror(THIS_FILE, buf, info->status); + } + +#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 + + if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info && + (state == PJSIP_TP_STATE_CONNECTED || + (state & PJSIP_TP_STATE_TLS_VERIF_ERROR))) + { + pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info; + pj_ssl_sock_info *ssl_sock_info = (pj_ssl_sock_info*) + tls_info->ssl_sock_info; + char buf[2048]; + + /* Dump server TLS certificate */ + pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, " ", + buf, sizeof(buf)); + PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf)); + } + +#endif + return PJ_TRUE; +} + //#include @@ -330,6 +387,7 @@ static pj_status_t app_startup() cfg.cb.on_call_transfer_status = &on_call_transfer_status; cfg.cb.on_call_replaced = &on_call_replaced; cfg.cb.on_nat_detect = &on_nat_detect; + cfg.cb.on_transport_state = &on_transport_state; if (SIP_PROXY) { cfg.outbound_proxy_cnt = 1; @@ -1054,7 +1112,7 @@ int ua_main() SelectIAP(); // Initialize RSocketServ - if ((err=aSocketServer.Connect()) != KErrNone) + if ((err=aSocketServer.Connect(32)) != KErrNone) return PJ_STATUS_FROM_OS(err); // Open up a connection diff --git a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp index f5a7931b..0f964ace 100644 --- a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp +++ b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp @@ -41,6 +41,7 @@ LIBRARY eikctl.lib bafl.lib fbscli.lib aknnotify.lib aknicon.lib LIBRARY etext.lib gdi.lib egul.lib insock.lib LIBRARY ecom.lib inetprotutil.lib http.lib esock.lib LIBRARY charconv.lib estlib.lib +LIBRARY securesocket.lib x509.lib crypto.lib x500.lib // Ordering static libs based on dependencies, most to least dependent, // this could be necessary for some SDKs, e.g: S60 3rd MR diff --git a/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp b/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp index f722336a..bf3f7a36 100644 --- a/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp +++ b/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp @@ -263,7 +263,7 @@ int symbian_ua_init() pj_status_t status; // Initialize RSocketServ - if ((err=aSocketServer.Connect()) != KErrNone) + if ((err=aSocketServer.Connect(32)) != KErrNone) return PJ_STATUS_FROM_OS(err); // Open up a connection diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index 1938e9d4..2abcf4a4 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -372,6 +372,35 @@ pjsip_endpt_acquire_transport( pjsip_endpoint *endpt, pjsip_transport **p_tp); +/** + * Find a SIP transport suitable for sending SIP message to the specified + * address by also considering the outgoing SIP message data. If transport + * selector ("sel") is set, then the function will check if the transport + * selected is suitable to send requests to the specified address. + * + * @see pjsip_tpmgr_acquire_transport + * + * @param endpt The SIP endpoint instance. + * @param type The type of transport to be acquired. + * @param remote The remote address to send message to. + * @param addr_len Length of the remote address. + * @param sel Optional pointer to transport selector instance which is + * used to find explicit transport, if required. + * @param tdata Optional pointer to SIP message data to be sent. + * @param p_tp Pointer to receive the transport instance, if one is found. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + pjsip_transport **p_tp); + + /***************************************************************************** * * Capabilities Management diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h index 6592fc98..9f6534cd 100644 --- a/pjsip/include/pjsip/sip_transport.h +++ b/pjsip/include/pjsip/sip_transport.h @@ -540,6 +540,10 @@ struct pjsip_tx_data */ struct { + /** Server name. + */ + pj_str_t name; + /** Server addresses resolved. */ pjsip_server_addresses addr; @@ -688,6 +692,11 @@ typedef struct pjsip_transport_key */ long type; + /** + * Hash of host name. + */ + pj_uint32_t hname; + /** * Destination address. */ @@ -918,7 +927,8 @@ struct pjsip_tpfactory pjsip_host_port addr_name; /**< Published name. */ /** - * Create new outbound connection. + * Create new outbound connection suitable for sending SIP message + * to specified remote address. * Note that the factory is responsible for both creating the * transport and registering it to the transport manager. */ @@ -929,6 +939,21 @@ struct pjsip_tpfactory int addr_len, pjsip_transport **transport); + /** + * Create new outbound connection suitable for sending SIP message + * to specified remote address by also considering outgoing SIP + * message data. + * Note that the factory is responsible for both creating the + * transport and registering it to the transport manager. + */ + pj_status_t (*create_transport2)(pjsip_tpfactory *factory, + pjsip_tpmgr *mgr, + pjsip_endpoint *endpt, + const pj_sockaddr *rem_addr, + int addr_len, + pjsip_tx_data *tdata, + pjsip_transport **transport); + /** * Destroy the listener. */ @@ -1099,6 +1124,34 @@ PJ_DECL(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, const pjsip_tpselector *sel, pjsip_transport **tp); +/** + * Find suitable transport for sending SIP message to specified remote + * destination by also considering the outgoing SIP message. If no suitable + * transport is found, a new one will be created. + * + * This is an internal function since normally application doesn't have access + * to transport manager. Application should use pjsip_endpt_acquire_transport() + * instead. + * + * @param mgr The transport manager instance. + * @param type The type of transport to be acquired. + * @param remote The remote address to send message to. + * @param addr_len Length of the remote address. + * @param sel Optional pointer to transport selector instance which is + * used to find explicit transport, if required. + * @param tdata Optional pointer to data to be sent. + * @param tp Pointer to receive the transport instance, if one is found. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + pjsip_transport **tp); + /** * Type of callback to receive notification when message or raw data * has been sent. @@ -1187,6 +1240,94 @@ PJ_DECL(pj_status_t) pjsip_tpmgr_send_raw(pjsip_tpmgr *mgr, void *token, pjsip_tp_send_callback cb); + +/** + * Enumeration of transport state types. + */ +typedef enum pjsip_transport_state_type { + + /** Transport connected. */ + PJSIP_TP_STATE_CONNECTED = (1 << 0), + + /** Transport accepted. */ + PJSIP_TP_STATE_ACCEPTED = (1 << 1), + + /** Transport disconnected. */ + PJSIP_TP_STATE_DISCONNECTED = (1 << 2), + + /** Incoming connection rejected. */ + PJSIP_TP_STATE_REJECTED = (1 << 3), + + /** TLS verification error. */ + PJSIP_TP_STATE_TLS_VERIF_ERROR = (1 << 8) + +} pjsip_transport_state_type; + + +/** + * Structure of transport state info. + */ +typedef struct pjsip_transport_state_info { + /** + * The last error code related to the transport state. + */ + pj_status_t status; + + /** + * Optional extended info, the content is specific for each transport type. + */ + void *ext_info; +} pjsip_transport_state_info; + + +/** + * Type of callback to receive transport state notifications, such as + * transport connected, disconnected or TLS verification error. + * + * @param tp The transport instance. + * @param state The transport state, this may contain single or + * combination of transport state types defined in + * #pjsip_transport_state_type. + * @param info The transport state info. + * + * @return When TLS verification fails and peer verification in + * #pjsip_tls_setting is not set, application may return + * PJ_TRUE to ignore the verification result and continue + * using the transport. On other cases, this return value + * is currently not used and will be ignored. + */ +typedef pj_bool_t (*pjsip_tp_state_callback)( + pjsip_transport *tp, + pj_uint32_t state, + const pjsip_transport_state_info *info); + + +/** + * Setting callback of transport state notification. The caller will be + * notified whenever the state of transport is changed. The type of + * events are defined in #pjsip_transport_state_type. + * + * @param mgr Transport manager. + * @param cb Callback to be called to notify caller about transport + * status changing. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_tpmgr_set_status_cb(pjsip_tpmgr *mgr, + pjsip_tp_state_callback *cb); + + +/** + * Getting the callback of transport state notification. + * + * @param mgr Transport manager. + * + * @return The transport state callback or NULL if it is not set. + */ +PJ_DECL(pjsip_tp_state_callback*) pjsip_tpmgr_get_status_cb( + const pjsip_tpmgr *mgr); + + /** * @} */ diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index 8c41e167..f97414b3 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -26,6 +26,7 @@ */ #include +#include #include #include @@ -121,27 +122,44 @@ typedef struct pjsip_tls_setting pj_str_t server_name; /** - * When PJSIP is acting as a client (outgoing TLS connections), - * it will always receive a certificate from the peer. - * If \a verify_server is disabled (set to zero), PJSIP will not - * verifiy the certificate and allows TLS connections to servers - * which do not present a valid certificate. - * If \a tls_verify_server is non-zero, PJSIP verifies the server - * certificate and will close the TLS connection if the server - * certificate is not valid. + * Specifies the action when verification of server TLS certificate + * resulting errors: + * - If \a verify_server is disabled (set to PJ_FALSE), TLS transport + * will just notify the application via #pjsip_tp_state_callback with + * state (PJSIP_TP_STATE_CONNECTED | PJSIP_TP_STATE_TLS_VERIF_ERROR) + * whenever there is any TLS verification error, the return value of + * the callback will be used to decide whether transport should be + * shutdown. + * - If \a verify_server is enabled (set to PJ_TRUE), TLS transport + * will be shutdown and application will be notified with state + * (PJSIP_TP_STATE_DISCONNECTED | PJSIP_TP_STATE_TLS_VERIF_ERROR) + * whenever there is any TLS verification error. * - * This setting corresponds to OpenSSL SSL_VERIFY_PEER flag. - * Default value is zero. + * When the verification resulting success, application will be notified + * via #pjsip_tp_state_callback with state PJSIP_TP_STATE_CONNECTED. + * + * Default value is PJ_FALSE. */ pj_bool_t verify_server; /** - * When acting as server (incoming TLS connections), setting - * \a verify_client to non-zero will cause the transport to activate - * peer verification upon receiving incoming TLS connection. + * Specifies the action when verification of server TLS certificate + * resulting errors: + * - If \a verify_client is disabled (set to PJ_FALSE), TLS transport + * will just notify the application via #pjsip_tp_state_callback with + * state (PJSIP_TP_STATE_ACCEPTED | PJSIP_TP_STATE_TLS_VERIF_ERROR) + * whenever there is any TLS verification error, the return value of + * the callback will be used to decide whether transport should be + * shutdown. + * - If \a verify_client is enabled (set to PJ_TRUE), TLS transport + * will be shutdown and application will be notified with state + * (PJSIP_TP_STATE_REJECTED | PJSIP_TP_STATE_TLS_VERIF_ERROR) + * whenever there is any TLS verification error. + * + * When the verification resulting success, application will be notified + * via #pjsip_tp_state_callback with state PJSIP_TP_STATE_ACCEPTED. * - * This setting corresponds to OpenSSL SSL_VERIFY_PEER flag. - * Default value is zero. + * Default value is PJ_FALSE. */ pj_bool_t verify_client; @@ -150,7 +168,7 @@ typedef struct pjsip_tls_setting * connection if client doesn't have a valid certificate. * * This setting corresponds to SSL_VERIFY_FAIL_IF_NO_PEER_CERT flag. - * Default value is zero. + * Default value is PJ_FALSE. */ pj_bool_t require_client_cert; @@ -190,6 +208,20 @@ typedef struct pjsip_tls_setting } pjsip_tls_setting; +/** + * This structure defines transport state extended info specifically for + * TLS transport. + */ +typedef struct pjsip_tls_state_info +{ + /** + * SSL socket info. + */ + pj_ssl_sock_info *ssl_sock_info; + +} pjsip_tls_state_info; + + /** * Initialize TLS setting with default values. * diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index ecafb25d..8d4d0a97 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -843,6 +843,25 @@ typedef struct pjsua_callback */ void (*on_mwi_info)(pjsua_acc_id acc_id, pjsua_mwi_info *mwi_info); + /** + * This callback is called when transport state is changed. See also + * #pjsip_tp_state_callback. + * + * @param tp The transport instance. + * @param state The transport state, this may contain single or + * combination of transport state types defined in + * #pjsip_transport_state_type. + * @param info The transport state info. + * + * @return When TLS verification fails and peer verification in + * #pjsip_tls_setting is not set, application may return + * PJ_TRUE to ignore the verification result and continue + * using the transport. On other cases, this return value + * is currently not used and will be ignored. + */ + pj_bool_t (*on_transport_state)(pjsip_transport *tp, pj_uint32_t state, + const pjsip_transport_state_info *info); + } pjsua_callback; diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index a6861820..e0fe4f08 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -1074,6 +1074,22 @@ PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt, } +/* + * Find/create transport. + */ +PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + pjsip_transport **transport) +{ + return pjsip_tpmgr_acquire_transport2(endpt->transport_mgr, type, remote, + addr_len, sel, tdata, transport); +} + + /* * Report error. */ diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index f5d7ee57..92fbaf4d 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -89,6 +89,8 @@ struct pjsip_tpmgr #endif void (*on_rx_msg)(pjsip_endpoint*, pj_status_t, pjsip_rx_data*); pj_status_t (*on_tx_msg)(pjsip_endpoint*, pjsip_tx_data*); + pjsip_tp_state_callback *tp_state_cb; + void *tp_state_user_data; }; @@ -864,7 +866,7 @@ PJ_DEF(pj_status_t) pjsip_transport_register( pjsip_tpmgr *mgr, /* * Register to hash table (see Trac ticket #42). */ - key_len = sizeof(tp->key.type) + tp->addr_len; + key_len = sizeof(tp->key.type) + sizeof(tp->key.hname) + tp->addr_len; pj_lock_acquire(mgr->lock); /* If entry already occupied, unregister previous entry */ @@ -914,7 +916,7 @@ static pj_status_t destroy_transport( pjsip_tpmgr *mgr, /* * Unregister from hash table (see Trac ticket #42). */ - key_len = sizeof(tp->key.type) + tp->addr_len; + key_len = sizeof(tp->key.type) + sizeof(tp->key.hname) + tp->addr_len; hval = 0; entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval); if (entry == (void*)tp) @@ -1501,6 +1503,24 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, int addr_len, const pjsip_tpselector *sel, pjsip_transport **tp) +{ + return pjsip_tpmgr_acquire_transport2(mgr, type, remote, addr_len, sel, + NULL, tp); +} + +/* + * pjsip_tpmgr_acquire_transport2() + * + * Get transport suitable to communicate to remote. Create a new one + * if necessary. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + pjsip_transport **tp) { pjsip_tpfactory *factory; pj_status_t status; @@ -1571,19 +1591,43 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, int key_len; pjsip_transport *transport; + /* + * Find factory that can create such transport. + */ + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + if (factory->type == type) + break; + factory = factory->next; + } + if (factory == &mgr->factory_list) + factory = NULL; + pj_bzero(&key, sizeof(key)); - key_len = sizeof(key.type) + addr_len; + key_len = sizeof(key.type) + sizeof(key.hname) + addr_len; /* First try to get exact destination. */ key.type = type; pj_memcpy(&key.rem_addr, remote, addr_len); + if (factory && factory->create_transport2 && + tdata && tdata->dest_info.name.slen) + { + /* Only include hostname hash in the key when the factory support + * create_transport2() and tdata is supplied. + */ + key.hname = pj_hash_calc_tolower(0, + (char*)tdata->dest_info.name.ptr, + &tdata->dest_info.name); + } transport = (pjsip_transport*) pj_hash_get(mgr->table, &key, key_len, NULL); + if (transport == NULL) { unsigned flag = pjsip_transport_get_flag_from_type(type); const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote; + /* Ignore address for loop transports. */ if (type == PJSIP_TRANSPORT_LOOP || type == PJSIP_TRANSPORT_LOOP_DGRAM) @@ -1591,7 +1635,7 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, pj_sockaddr *addr = &key.rem_addr; pj_bzero(addr, addr_len); - key_len = sizeof(key.type) + addr_len; + key_len = sizeof(key.type) + sizeof(key.hname) + addr_len; transport = (pjsip_transport*) pj_hash_get(mgr->table, &key, key_len, NULL); } @@ -1604,7 +1648,7 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, pj_bzero(addr, addr_len); addr->addr.sa_family = remote_addr->addr.sa_family; - key_len = sizeof(key.type) + addr_len; + key_len = sizeof(key.type) + sizeof(key.hname) + addr_len; transport = (pjsip_transport*) pj_hash_get(mgr->table, &key, key_len, NULL); } @@ -1624,31 +1668,28 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, /* * Transport not found! - * Find factory that can create such transport. */ - factory = mgr->factory_list.next; - while (factory != &mgr->factory_list) { - if (factory->type == type) - break; - factory = factory->next; - } - - if (factory == &mgr->factory_list) { + if (NULL == factory) { /* No factory can create the transport! */ pj_lock_release(mgr->lock); TRACE_((THIS_FILE, "No suitable factory was found either")); return PJSIP_EUNSUPTRANSPORT; } - } TRACE_((THIS_FILE, "Creating new transport from factory")); /* Request factory to create transport. */ - status = factory->create_transport(factory, mgr, mgr->endpt, - (const pj_sockaddr*) remote, addr_len, - tp); + if (factory->create_transport2) { + status = factory->create_transport2(factory, mgr, mgr->endpt, + (const pj_sockaddr*) remote, + addr_len, tdata, tp); + } else { + status = factory->create_transport(factory, mgr, mgr->endpt, + (const pj_sockaddr*) remote, + addr_len, tp); + } if (status == PJ_SUCCESS) { PJ_ASSERT_ON_FAIL(tp!=NULL, {pj_lock_release(mgr->lock); return PJ_EBUG;}); @@ -1711,3 +1752,20 @@ PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr) #endif } +PJ_DEF(pj_status_t) pjsip_tpmgr_set_status_cb(pjsip_tpmgr *mgr, + pjsip_tp_state_callback *cb) +{ + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + mgr->tp_state_cb = cb; + + return PJ_SUCCESS; +} + +PJ_DEF(pjsip_tp_state_callback*) pjsip_tpmgr_get_status_cb( + const pjsip_tpmgr *mgr) +{ + PJ_ASSERT_RETURN(mgr, NULL); + + return mgr->tp_state_cb; +} diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c index 56a4fe8f..a7c7eeba 100644 --- a/pjsip/src/pjsip/sip_transport_tcp.c +++ b/pjsip/src/pjsip/sip_transport_tcp.c @@ -166,6 +166,36 @@ static void sockaddr_to_host_port( pj_pool_t *pool, host_port->port = pj_sockaddr_get_port(addr); } + +static void tcp_init_shutdown(struct tcp_transport *tcp, pj_status_t status) +{ + pjsip_tp_state_callback *state_cb; + + if (tcp->close_reason == PJ_SUCCESS) + tcp->close_reason = status; + + if (tcp->base.is_shutdown) + return; + + /* Notify application of transport disconnected state */ + state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + state_info.status = tcp->close_reason; + (*state_cb)(&tcp->base, PJSIP_TP_STATE_DISCONNECTED, &state_info); + } + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + pjsip_transport_shutdown(&tcp->base); +} + + /* * Initialize pjsip_tcp_transport_cfg structure with default values. */ @@ -921,6 +951,7 @@ static pj_bool_t on_accept_complete(pj_activesock_t *asock, struct tcp_listener *listener; struct tcp_transport *tcp; char addr[PJ_INET6_ADDRSTRLEN+10]; + pjsip_tp_state_callback *state_cb; pj_status_t status; PJ_UNUSED_ARG(src_addr_len); @@ -966,6 +997,15 @@ static pj_bool_t on_accept_complete(pj_activesock_t *asock, tcp->ka_timer.id = PJ_TRUE; pj_gettimeofday(&tcp->last_activity); } + + /* Notify application of transport state accepted */ + state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + (*state_cb)(&tcp->base, PJSIP_TP_STATE_ACCEPTED, &state_info); + } } } @@ -1013,8 +1053,8 @@ static pj_bool_t on_data_sent(pj_activesock_t *asock, status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : -bytes_sent; - if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; - pjsip_transport_shutdown(&tcp->base); + + tcp_init_shutdown(tcp, status); return PJ_FALSE; } @@ -1109,8 +1149,8 @@ static pj_status_t tcp_send_msg(pjsip_transport *transport, if (status == PJ_SUCCESS) status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); - if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; - pjsip_transport_shutdown(&tcp->base); + + tcp_init_shutdown(tcp, status); } } } @@ -1199,14 +1239,7 @@ static pj_bool_t on_data_read(pj_activesock_t *asock, /* Transport is closed */ PJ_LOG(4,(tcp->base.obj_name, "TCP connection closed")); - /* We can not destroy the transport since high level objects may - * still keep reference to this transport. So we can only - * instruct transport manager to gracefully start the shutdown - * procedure for this transport. - */ - if (tcp->close_reason==PJ_SUCCESS) - tcp->close_reason = status; - pjsip_transport_shutdown(&tcp->base); + tcp_init_shutdown(tcp, status); return PJ_FALSE; @@ -1229,6 +1262,8 @@ static pj_bool_t on_connect_complete(pj_activesock_t *asock, pj_sockaddr_in addr; int addrlen; + pjsip_tp_state_callback *state_cb; + tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock); /* Mark that pending connect() operation has completed. */ @@ -1252,14 +1287,7 @@ static pj_bool_t on_connect_complete(pj_activesock_t *asock, on_data_sent(tcp->asock, op_key, -status); } - /* We can not destroy the transport since high level objects may - * still keep reference to this transport. So we can only - * instruct transport manager to gracefully start the shutdown - * procedure for this transport. - */ - if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; - pjsip_transport_shutdown(&tcp->base); - return PJ_FALSE; + tcp_init_shutdown(tcp, status); } PJ_LOG(4,(tcp->base.obj_name, @@ -1293,16 +1321,19 @@ static pj_bool_t on_connect_complete(pj_activesock_t *asock, /* Start pending read */ status = tcp_start_read(tcp); if (status != PJ_SUCCESS) { - /* We can not destroy the transport since high level objects may - * still keep reference to this transport. So we can only - * instruct transport manager to gracefully start the shutdown - * procedure for this transport. - */ - if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; - pjsip_transport_shutdown(&tcp->base); + tcp_init_shutdown(tcp, status); return PJ_FALSE; } + /* Notify application of transport state connected */ + state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info); + } + /* Flush all pending send operations */ tcp_flush_pending_tx(tcp); @@ -1358,7 +1389,7 @@ static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e) if (status != PJ_SUCCESS && status != PJ_EPENDING) { tcp_perror(tcp->base.obj_name, "Error sending keep-alive packet", status); - pjsip_transport_shutdown(&tcp->base); + tcp_init_shutdown(tcp, status); return; } diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c index ab96ecd9..a135c43f 100644 --- a/pjsip/src/pjsip/sip_transport_tls.c +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -80,12 +81,14 @@ struct tls_transport { pjsip_transport base; pj_bool_t is_server; + pj_str_t remote_name; pj_bool_t is_registered; pj_bool_t is_closing; pj_status_t close_reason; pj_ssl_sock_t *ssock; pj_bool_t has_pending_connect; + pj_bool_t verify_server; /* Keep-alive timer. */ pj_timer_entry ka_timer; @@ -135,6 +138,7 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pjsip_endpoint *endpt, const pj_sockaddr *rem_addr, int addr_len, + pjsip_tx_data *tdata, pjsip_transport **transport); /* Common function to create and initialize transport */ @@ -144,6 +148,7 @@ static pj_status_t tls_create(struct tls_listener *listener, pj_bool_t is_server, const pj_sockaddr_in *local, const pj_sockaddr_in *remote, + const pj_str_t *remote_name, struct tls_transport **p_tls); @@ -169,6 +174,34 @@ static void sockaddr_to_host_port( pj_pool_t *pool, } +static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status) +{ + pjsip_tp_state_callback *state_cb; + + if (tls->close_reason == PJ_SUCCESS) + tls->close_reason = status; + + if (tls->base.is_shutdown) + return; + + /* Notify application of transport disconnected state */ + state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + state_info.status = tls->close_reason; + (*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info); + } + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + pjsip_transport_shutdown(&tls->base); +} + /**************************************************************************** * The TLS listener/transport factory. @@ -243,10 +276,10 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start (pjsip_endpoint *endpt, ssock_param.async_cnt = async_cnt; ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt); ssock_param.require_client_cert = listener->tls_setting.require_client_cert; - ssock_param.server_name = listener->tls_setting.server_name; ssock_param.timeout = listener->tls_setting.timeout; ssock_param.user_data = listener; - ssock_param.verify_peer = listener->tls_setting.verify_client; + ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket + * due to verification error */ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN) ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN; if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN) @@ -371,7 +404,7 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start (pjsip_endpoint *endpt, /* Register to transport manager */ listener->endpt = endpt; listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); - listener->factory.create_transport = lis_create_transport; + listener->factory.create_transport2 = lis_create_transport; listener->factory.destroy = lis_destroy; listener->is_registered = PJ_TRUE; status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, @@ -480,6 +513,7 @@ static pj_status_t tls_create( struct tls_listener *listener, pj_bool_t is_server, const pj_sockaddr_in *local, const pj_sockaddr_in *remote, + const pj_str_t *remote_name, struct tls_transport **p_tls) { struct tls_transport *tls; @@ -501,6 +535,7 @@ static pj_status_t tls_create( struct tls_listener *listener, */ tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport); tls->is_server = is_server; + tls->verify_server = listener->tls_setting.verify_server; pj_list_init(&tls->delayed_list); tls->base.pool = pool; @@ -517,8 +552,13 @@ static pj_status_t tls_create( struct tls_listener *listener, goto on_error; } + if (remote_name) + pj_strdup(pool, &tls->remote_name, remote_name); + tls->base.key.type = PJSIP_TRANSPORT_TLS; pj_memcpy(&tls->base.key.rem_addr, remote, sizeof(pj_sockaddr_in)); + tls->base.key.hname = pj_hash_calc_tolower(0, (char*)tls->remote_name.ptr, + &tls->remote_name); tls->base.type_name = "tls"; tls->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); @@ -769,6 +809,7 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pjsip_endpoint *endpt, const pj_sockaddr *rem_addr, int addr_len, + pjsip_tx_data *tdata, pjsip_transport **p_transport) { struct tls_listener *listener; @@ -777,6 +818,7 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pj_ssl_sock_t *ssock; pj_ssl_sock_param ssock_param; pj_sockaddr_in local_addr; + pj_str_t remote_name; pj_status_t status; /* Sanity checks */ @@ -794,6 +836,12 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, POOL_TP_INIT, POOL_TP_INC); PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + /* Get remote host name from tdata */ + if (tdata) + remote_name = tdata->dest_info.name; + else + pj_bzero(&remote_name, sizeof(remote_name)); + /* Build SSL socket param */ pj_ssl_sock_param_default(&ssock_param); ssock_param.cb.on_connect_complete = &on_connect_complete; @@ -801,12 +849,12 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, ssock_param.cb.on_data_sent = &on_data_sent; ssock_param.async_cnt = 1; ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); - PJ_TODO(set_proper_servername_based_on_target); PJ_TODO(synchronize_tls_cipher_type_with_ssl_sock_cipher_type); - ssock_param.server_name = listener->tls_setting.server_name; + ssock_param.server_name = remote_name; ssock_param.timeout = listener->tls_setting.timeout; ssock_param.user_data = NULL; /* pending, must be set later */ - ssock_param.verify_peer = listener->tls_setting.verify_server; + ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket + * due to verification error */ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN) ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN; if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN) @@ -850,7 +898,7 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, /* Create the transport descriptor */ status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr, - (pj_sockaddr_in*)rem_addr, &tls); + (pj_sockaddr_in*)rem_addr, &remote_name, &tls); if (status != PJ_SUCCESS) return status; @@ -928,9 +976,13 @@ static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock, { struct tls_listener *listener; struct tls_transport *tls; + pj_ssl_sock_info ssl_info; char addr[PJ_INET6_ADDRSTRLEN+10]; pj_status_t status; + pjsip_tp_state_callback *state_cb; + pj_bool_t tls_verif_ignored; + PJ_UNUSED_ARG(src_addr_len); listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock); @@ -946,32 +998,84 @@ static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock, pj_sockaddr_print(src_addr, addr, sizeof(addr), 3), new_ssock)); + /* Retrieve SSL socket info, close the socket if this is failed + * as the SSL socket info availability is rather critical here. + */ + status = pj_ssl_sock_get_info(new_ssock, &ssl_info); + if (status != PJ_SUCCESS) { + pj_ssl_sock_close(new_ssock); + return PJ_TRUE; + } + /* * Incoming connection! * Create TLS transport for the new socket. */ status = tls_create( listener, NULL, new_ssock, PJ_TRUE, (const pj_sockaddr_in*)&listener->factory.local_addr, - (const pj_sockaddr_in*)src_addr, &tls); + (const pj_sockaddr_in*)src_addr, NULL, &tls); - if (status == PJ_SUCCESS) { - /* Set the "pending" SSL socket user data */ - pj_ssl_sock_set_user_data(new_ssock, tls); + if (status != PJ_SUCCESS) + return PJ_TRUE; - status = tls_start_read(tls); - if (status != PJ_SUCCESS) { - PJ_LOG(3,(tls->base.obj_name, "New transport cancelled")); - tls_destroy(&tls->base, status); + /* Set the "pending" SSL socket user data */ + pj_ssl_sock_set_user_data(new_ssock, tls); + + tls_verif_ignored = !listener->tls_setting.verify_client; + + /* Notify transport state to application */ + state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + pj_uint32_t tp_state = 0; + + /* Init transport state notification callback */ + pj_bzero(&tls_info, sizeof(tls_info)); + pj_bzero(&state_info, sizeof(state_info)); + + /* Set transport state based on verification status */ + if (ssl_info.verify_status) { + state_info.status = PJSIP_TLS_EACCEPT; + tp_state |= PJSIP_TP_STATE_TLS_VERIF_ERROR; + if (listener->tls_setting.verify_client) + tp_state |= PJSIP_TP_STATE_REJECTED; + else + tp_state |= PJSIP_TP_STATE_ACCEPTED; } else { - /* Start keep-alive timer */ - if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { - pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0}; - pjsip_endpt_schedule_timer(listener->endpt, - &tls->ka_timer, - &delay); - tls->ka_timer.id = PJ_TRUE; - pj_gettimeofday(&tls->last_activity); - } + tp_state |= PJSIP_TP_STATE_ACCEPTED; + } + + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + + tls_verif_ignored = (*state_cb)(&tls->base, tp_state, &state_info); + } + + /* Transport should be destroyed when there is TLS verification error + * and application doesn't want to ignore it. + */ + if (ssl_info.verify_status && + (listener->tls_setting.verify_client || !tls_verif_ignored)) + { + tls_destroy(&tls->base, PJSIP_TLS_EACCEPT); + return PJ_TRUE; + } + + status = tls_start_read(tls); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(tls->base.obj_name, "New transport cancelled")); + tls_init_shutdown(tls, status); + tls_destroy(&tls->base, status); + } else { + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0}; + pjsip_endpt_schedule_timer(listener->endpt, + &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tls->last_activity); } } @@ -1019,8 +1123,8 @@ static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock, status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : -bytes_sent; - if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; - pjsip_transport_shutdown(&tls->base); + + tls_init_shutdown(tls, status); return PJ_FALSE; } @@ -1115,8 +1219,8 @@ static pj_status_t tls_send_msg(pjsip_transport *transport, if (status == PJ_SUCCESS) status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); - if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; - pjsip_transport_shutdown(&tls->base); + + tls_init_shutdown(tls, status); } } } @@ -1204,15 +1308,8 @@ static pj_bool_t on_data_read(pj_ssl_sock_t *ssock, /* Transport is closed */ PJ_LOG(4,(tls->base.obj_name, "TLS connection closed")); - - /* We can not destroy the transport since high level objects may - * still keep reference to this transport. So we can only - * instruct transport manager to gracefully start the shutdown - * procedure for this transport. - */ - if (tls->close_reason==PJ_SUCCESS) - tls->close_reason = status; - pjsip_transport_shutdown(&tls->base); + + tls_init_shutdown(tls, status); return PJ_FALSE; @@ -1232,8 +1329,12 @@ static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, pj_status_t status) { struct tls_transport *tls; - pj_ssl_sock_info info; - + pj_ssl_sock_info ssl_info; + pj_sockaddr_in addr, *tp_addr; + + pjsip_tp_state_callback *state_cb; + pj_bool_t tls_verif_ignored; + tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock); /* Check connect() status */ @@ -1254,36 +1355,130 @@ static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, on_data_sent(tls->ssock, op_key, -status); } - /* We can not destroy the transport since high level objects may - * still keep reference to this transport. So we can only - * instruct transport manager to gracefully start the shutdown - * procedure for this transport. - */ - if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; - pjsip_transport_shutdown(&tls->base); - return PJ_FALSE; + goto on_error; } + /* Retrieve SSL socket info, shutdown the transport if this is failed + * as the SSL socket info availability is rather critical here. + */ + status = pj_ssl_sock_get_info(tls->ssock, &ssl_info); + if (status != PJ_SUCCESS) + goto on_error; + /* Update (again) local address, just in case local address currently * set is different now that the socket is connected (could happen * on some systems, like old Win32 probably?). */ + tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; + pj_sockaddr_cp((pj_sockaddr_t*)&addr, + (pj_sockaddr_t*)&ssl_info.local_addr); + if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) { + tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr; + tp_addr->sin_port = addr.sin_port; + sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, + tp_addr); + } - /* Retrieve the bound address */ - status = pj_ssl_sock_get_info(tls->ssock, &info); - if (status == PJ_SUCCESS) { - pj_sockaddr_in addr; - pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; - - pj_sockaddr_cp((pj_sockaddr_t*)&addr, (pj_sockaddr_t*)&info.local_addr); - if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) { - tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr; - tp_addr->sin_port = addr.sin_port; - sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, - tp_addr); + /* Server identity verification based on server certificate. */ + if (ssl_info.remote_cert_info->version) { + pj_str_t *remote_name; + pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info; + pj_bool_t matched = PJ_FALSE; + unsigned i; + + /* Remote name may be hostname or IP address */ + if (tls->remote_name.slen) + remote_name = &tls->remote_name; + else + remote_name = &tls->base.remote_name.host; + + /* Start matching remote name with SubjectAltName fields of + * server certificate. + */ + for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) { + pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name; + + switch (serv_cert->subj_alt_name.entry[i].type) { + case PJ_SSL_CERT_NAME_DNS: + case PJ_SSL_CERT_NAME_IP: + matched = !pj_stricmp(remote_name, cert_name); + break; + case PJ_SSL_CERT_NAME_URI: + if (pj_strnicmp2(cert_name, "sip:", 4) == 0 || + pj_strnicmp2(cert_name, "sips:", 5) == 0) + { + pj_str_t host_part; + char *p; + + p = pj_strchr(cert_name, ':') + 1; + pj_strset(&host_part, p, cert_name->slen - + (p - cert_name->ptr)); + matched = !pj_stricmp(remote_name, &host_part); + } + break; + default: + break; + } + } + + /* When still not matched or no SubjectAltName fields in server + * certificate, try with Common Name of Subject field. + */ + if (!matched) { + matched = !pj_stricmp(remote_name, &serv_cert->subject.cn); } + + if (!matched) + ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH; } + tls_verif_ignored = !tls->verify_server; + + /* Notify transport state to application */ + state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + pj_uint32_t tp_state = 0; + + /* Init transport state notification callback */ + pj_bzero(&state_info, sizeof(state_info)); + pj_bzero(&tls_info, sizeof(tls_info)); + + /* Set transport state info */ + state_info.ext_info = &tls_info; + tls_info.ssl_sock_info = &ssl_info; + + /* Set transport state based on verification status */ + if (ssl_info.verify_status) { + state_info.status = PJSIP_TLS_ECONNECT; + tp_state |= PJSIP_TP_STATE_TLS_VERIF_ERROR; + if (tls->verify_server) + tp_state |= PJSIP_TP_STATE_DISCONNECTED; + else + tp_state |= PJSIP_TP_STATE_CONNECTED; + } else { + tp_state |= PJSIP_TP_STATE_CONNECTED; + } + + tls_verif_ignored = (*state_cb)(&tls->base, tp_state, &state_info); + } + + /* Transport should be shutdown when there is TLS verification error + * and application doesn't want to ignore it. + */ + if (ssl_info.verify_status && + (tls->verify_server || !tls_verif_ignored)) + { + if (tls->close_reason == PJ_SUCCESS) + tls->close_reason = PJSIP_TLS_ECONNECT; + pjsip_transport_shutdown(&tls->base); + return PJ_FALSE; + } + + /* Mark that pending connect() operation has completed. */ + tls->has_pending_connect = PJ_FALSE; + PJ_LOG(4,(tls->base.obj_name, "TLS transport %.*s:%d is connected to %.*s:%d", (int)tls->base.local_name.host.slen, @@ -1293,21 +1488,10 @@ static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, tls->base.remote_name.host.ptr, tls->base.remote_name.port)); - /* Mark that pending connect() operation has completed. */ - tls->has_pending_connect = PJ_FALSE; - /* Start pending read */ status = tls_start_read(tls); - if (status != PJ_SUCCESS) { - /* We can not destroy the transport since high level objects may - * still keep reference to this transport. So we can only - * instruct transport manager to gracefully start the shutdown - * procedure for this transport. - */ - if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; - pjsip_transport_shutdown(&tls->base); - return PJ_FALSE; - } + if (status != PJ_SUCCESS) + goto on_error; /* Flush all pending send operations */ tls_flush_pending_tx(tls); @@ -1322,6 +1506,11 @@ static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, } return PJ_TRUE; + +on_error: + tls_init_shutdown(tls, status); + + return PJ_FALSE; } @@ -1365,7 +1554,8 @@ static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e) if (status != PJ_SUCCESS && status != PJ_EPENDING) { tls_perror(tls->base.obj_name, "Error sending keep-alive packet", status); - pjsip_transport_shutdown(&tls->base); + + tls_init_shutdown(tls, status); return; } diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 5bd0f6bf..3b1bc102 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -779,6 +779,10 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt, pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route); } + /* Copy the destination host name from the original request */ + pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name, + &req_tdata->dest_info.name); + /* Finally copy the destination info from the original request */ pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info, sizeof(req_tdata->dest_info)); @@ -1134,11 +1138,12 @@ static void stateless_send_transport_cb( void *token, cur_addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len; /* Acquire transport. */ - status = pjsip_endpt_acquire_transport( stateless_data->endpt, + status = pjsip_endpt_acquire_transport2(stateless_data->endpt, cur_addr_type, cur_addr, cur_addr_len, &tdata->tp_sel, + tdata, &stateless_data->cur_transport); if (status != PJ_SUCCESS) { sent = -status; @@ -1319,6 +1324,9 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt, * proceed to sending the request directly. */ if (tdata->dest_info.addr.count == 0) { + /* Copy the destination host name to TX data */ + pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host); + pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data, &stateless_send_resolver_callback); } else { @@ -1466,6 +1474,9 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_raw_to_uri(pjsip_endpoint *endpt, pjsip_tpselector_add_ref(sraw_data->sel); } + /* Copy the destination host name to TX data */ + pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host); + /* Resolve destination host. * The processing then resumed when the resolving callback is called. */ @@ -1622,11 +1633,12 @@ static void send_response_resolver_cb( pj_status_t status, void *token, /* Only handle the first address resolved. */ /* Acquire transport. */ - status = pjsip_endpt_acquire_transport( send_state->endpt, + status = pjsip_endpt_acquire_transport2(send_state->endpt, addr->entry[0].type, &addr->entry[0].addr, addr->entry[0].addr_len, &send_state->tdata->tp_sel, + send_state->tdata, &send_state->cur_transport); if (status != PJ_SUCCESS) { if (send_state->app_cb) { @@ -1702,6 +1714,10 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, return status; } } else { + /* Copy the destination host name to TX data */ + pj_strdup(tdata->pool, &tdata->dest_info.name, + &res_addr->dst_host.addr.host); + pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, send_state, &send_response_resolver_cb); return PJ_SUCCESS; diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 52d436e3..8870b87e 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -1810,9 +1810,6 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, /* * Create TLS transport. */ - /* - * Create TCP transport. - */ pjsua_transport_config config; pjsip_host_port a_name; pjsip_tpfactory *tls; @@ -1867,6 +1864,11 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, goto on_return; } + /* Set transport state callback */ + if (pjsua_var.ua_cfg.cb.on_transport_state) { + pjsip_tpmgr_set_status_cb(pjsip_endpt_get_tpmgr(pjsua_var.endpt), + &pjsua_var.ua_cfg.cb.on_transport_state); + } /* Return the ID */ if (p_id) *p_id = id; -- cgit v1.2.3