diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2010-02-24 05:43:34 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2010-02-24 05:43:34 +0000 |
commit | bb2fc905eb58b9ebdf66e89330599be996821db7 (patch) | |
tree | f6bedef48655a824a1393efbb667a3b8af560b63 /pjlib/src/pj/ssl_sock_ossl.c | |
parent | df622f00fa10e2cbcde9df6169ad628fe3e72226 (diff) |
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
Diffstat (limited to 'pjlib/src/pj/ssl_sock_ossl.c')
-rw-r--r-- | pjlib/src/pj/ssl_sock_ossl.c | 414 |
1 files changed, 311 insertions, 103 deletions
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 <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> +#include <openssl/x509v3.h> #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; |