From e7e4ace40aadeb3c3f554007ae8e81e9a1809de2 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 15 Feb 2024 10:04:22 +0700 Subject: [PATCH 01/20] Initial version (Working: connect, send/recv. Not working: param settings, cipher enum, cert info). --- pjlib/include/pj/config.h | 3 + pjlib/src/pj/activesock.c | 6 +- pjlib/src/pj/ssl_sock_imp_common.c | 58 +- pjlib/src/pj/ssl_sock_imp_common.h | 2 + pjlib/src/pj/ssl_sock_schannel.c | 875 +++++++++++++++++++++++++++++ 5 files changed, 930 insertions(+), 14 deletions(-) create mode 100644 pjlib/src/pj/ssl_sock_schannel.c diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h index 1a234fd8f7..6736d47344 100644 --- a/pjlib/include/pj/config.h +++ b/pjlib/include/pj/config.h @@ -1077,6 +1077,9 @@ /** Using Apple's Network framework */ #define PJ_SSL_SOCK_IMP_APPLE 4 +/** Using Windows's Schannel */ +#define PJ_SSL_SOCK_IMP_SCHANNEL 5 + /** * Select which SSL socket implementation to use. Currently pjlib supports * PJ_SSL_SOCK_IMP_OPENSSL, which uses OpenSSL, and PJ_SSL_SOCK_IMP_GNUTLS, diff --git a/pjlib/src/pj/activesock.c b/pjlib/src/pj/activesock.c index a892a658d1..c1c4a09856 100644 --- a/pjlib/src/pj/activesock.c +++ b/pjlib/src/pj/activesock.c @@ -513,7 +513,8 @@ static void ioqueue_on_read_complete(pj_ioqueue_key_t *key, ret = (*asock->cb.on_data_read)(asock, r->pkt, r->size, PJ_SUCCESS, &remainder); PJ_ASSERT_ON_FAIL( - !asock->stream_oriented || remainder <= r->size, { + !ret || !asock->stream_oriented || remainder <= r->size, + { PJ_LOG(2, ("", "App bug! Invalid remainder length from " "activesock on_data_read().")); @@ -589,7 +590,8 @@ static void ioqueue_on_read_complete(pj_ioqueue_key_t *key, ret = (*asock->cb.on_data_read)(asock, r->pkt, r->size, status, &remainder); PJ_ASSERT_ON_FAIL( - !asock->stream_oriented || remainder <= r->size, { + !ret || !asock->stream_oriented || remainder <= r->size, + { PJ_LOG(2, ("", "App bug! Invalid remainder length from " "activesock on_data_read().")); diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c index 11e3bd4152..a272376217 100644 --- a/pjlib/src/pj/ssl_sock_imp_common.c +++ b/pjlib/src/pj/ssl_sock_imp_common.c @@ -52,9 +52,25 @@ static pj_bool_t asock_on_data_sent (pj_activesock_t *asock, ******************************************************************* */ +static pj_size_t next_pow2(pj_size_t n) +{ + /* Next 32-bit power of two */ + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; +} + static pj_status_t circ_init(pj_pool_factory *factory, circ_buf_t *cb, pj_size_t cap) { + /* Round-up cap */ + cap = next_pow2(cap); + cb->cap = cap; cb->readp = 0; cb->writep = 0; @@ -68,17 +84,24 @@ static pj_status_t circ_init(pj_pool_factory *factory, /* Allocate circular buffer */ cb->buf = pj_pool_alloc(cb->pool, cap); if (!cb->buf) { - pj_pool_release(cb->pool); + pj_pool_secure_release(&cb->pool); return PJ_ENOMEM; } return PJ_SUCCESS; } +static void circ_reset(circ_buf_t* cb) +{ + cb->readp = 0; + cb->writep = 0; + cb->size = 0; +} + static void circ_deinit(circ_buf_t *cb) { if (cb->pool) { - pj_pool_release(cb->pool); + pj_pool_secure_release(&cb->pool); cb->pool = NULL; } } @@ -104,6 +127,8 @@ static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) pj_size_t tbc = PJ_MIN(size_after, len); pj_size_t rem = len - tbc; + pj_assert(cb->size >= len); + pj_memcpy(dst, cb->buf + cb->readp, tbc); pj_memcpy(dst + tbc, cb->buf, rem); @@ -113,6 +138,21 @@ static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) cb->size -= len; } +/* Cancel previous read, partially or fully. + * Should be called in the same mutex block as circ_read(). + */ +static void circ_read_cancel(circ_buf_t* cb, pj_size_t len) +{ + pj_assert(cb->cap - cb->size >= len); + + if (cb->readp < len) + cb->readp = cb->cap - (len - cb->readp); + else + cb->readp -= len; + + cb->size += len; +} + static pj_status_t circ_write(circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) { @@ -121,14 +161,8 @@ static pj_status_t circ_write(circ_buf_t *cb, /* Minimum required capacity */ pj_size_t min_cap = len + cb->size; - /* Next 32-bit power of two */ - min_cap--; - min_cap |= min_cap >> 1; - min_cap |= min_cap >> 2; - min_cap |= min_cap >> 4; - min_cap |= min_cap >> 8; - min_cap |= min_cap >> 16; - min_cap++; + /* Round-up minimum capacity */ + min_cap = next_pow2(min_cap); /* Create a new pool to hold a bigger buffer, using the same factory */ pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p", @@ -153,7 +187,7 @@ static pj_status_t circ_write(circ_buf_t *cb, cb->size = old_size; /* Release the previous pool */ - pj_pool_release(cb->pool); + pj_pool_secure_release(&cb->pool); /* Update circular buffer members */ cb->pool = pool; @@ -1737,7 +1771,7 @@ static pj_status_t ssl_send (pj_ssl_sock_t *ssock, unsigned flags) { pj_status_t status; - int nwritten; + int nwritten = 0; /* Write the plain data to SSL, after SSL encrypts it, the buffer will * contain the secured data to be sent via socket. Note that re- diff --git a/pjlib/src/pj/ssl_sock_imp_common.h b/pjlib/src/pj/ssl_sock_imp_common.h index 14ee122da8..e7b8ec61fb 100644 --- a/pjlib/src/pj/ssl_sock_imp_common.h +++ b/pjlib/src/pj/ssl_sock_imp_common.h @@ -205,9 +205,11 @@ static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock); static pj_status_t circ_init(pj_pool_factory *factory, circ_buf_t *cb, pj_size_t cap); static void circ_deinit(circ_buf_t *cb); +static void circ_reset(circ_buf_t* cb); static pj_bool_t circ_empty(const circ_buf_t *cb); static pj_size_t circ_size(const circ_buf_t *cb); static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len); +static void circ_read_cancel(circ_buf_t* cb, pj_size_t len); static pj_status_t circ_write(circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len); diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c new file mode 100644 index 0000000000..73def02261 --- /dev/null +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -0,0 +1,875 @@ +/* + * Copyright (C) 2024 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK is enabled and when the backend is + * Schannel. + * + * Note: + * - Older Windows SDK versions are not supported (some APIs deprecated, + * further more they must be missing newer/safer TLS protocol versions). + */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ + (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) && \ + defined(_MSC_VER) && _MSC_VER>=1900 + +#define THIS_FILE "ssl_sock_schannel.c" + +/* Sender string in logging */ +#define SENDER "Schannel" + +/* Debugging */ +#define DEBUG_SCHANNEL 0 + +#if DEBUG_SCHANNEL +# define LOG_DEBUG(title) PJ_LOG(4,(SENDER, title)) +# define LOG_DEBUG1(title, p1) PJ_LOG(4,(SENDER, title, p1)) +# define LOG_DEBUG2(title, p1, p2) PJ_LOG(4,(SENDER, title, p1, p2)) +# define LOG_DEBUG_ERR(title, sec_status) log_sec_err(4, title, sec_status) +#else +# define LOG_DEBUG(s) +# define LOG_DEBUG1(title, p1) +# define LOG_DEBUG2(title, p1, p2) +# define LOG_DEBUG_ERR(title, sec_status) +#endif + + +/* For using SSPI */ +#define SECURITY_WIN32 + +/* For using SCH_CREDENTIALS */ +#define SCHANNEL_USE_BLACKLISTS +//#define UNICODE_STRING +//#define PUNICODE_STRING +//#include // error: many redefinitions +//#include +#include + +#include +#include + +#pragma comment (lib, "secur32.lib") +#pragma comment (lib, "shlwapi.lib") +#pragma comment (lib, "Crypt32.lib") // for creating & manipulating certs + + +/* SSL sock implementation API */ +#define SSL_SOCK_IMP_USE_CIRC_BUF +#include "ssl_sock_imp_common.h" + +#define MIN_READ_BUF_CAP (1024*8) +#define MIN_WRITE_BUF_CAP (1024*8) + + +/* + * Schannel global vars. + */ +static struct sch_ssl_t +{ + pj_caching_pool cp; + pj_pool_t *pool; + unsigned long init_cnt; +} sch_ssl; + + +/* + * Secure socket structure definition. + */ +typedef struct sch_ssl_sock_t +{ + pj_ssl_sock_t base; + + CredHandle cred_handle; + CtxtHandle ctx_handle; + SecPkgContext_StreamSizes strm_sizes; + pj_uint8_t *write_buf; + pj_uint8_t *read_buf; + circ_buf_t decrypted_buf; +} sch_ssl_sock_t; + + +#include "ssl_sock_imp_common.c" + + +/* === Helper functions === */ + +static void sch_deinit(void) +{ + if (sch_ssl.pool) { + pj_pool_secure_release(&sch_ssl.pool); + pj_caching_pool_destroy(&sch_ssl.cp); + } + + pj_bzero(&sch_ssl, sizeof(sch_ssl)); +} + +static void sch_inc() +{ + if (++sch_ssl.init_cnt == 1 && sch_ssl.pool == NULL) { + pj_caching_pool_init(&sch_ssl.cp, NULL, 0); + sch_ssl.pool = pj_pool_create(&sch_ssl.cp.factory, "sch%p", + 512, 512, NULL); + if (pj_atexit(&sch_deinit) != PJ_SUCCESS) { + PJ_LOG(1,(SENDER, "Failed to register atexit() for Schannel.")); + } + } +} + +static void sch_dec() +{ + pj_assert(sch_ssl.init_cnt > 0); + --sch_ssl.init_cnt; +} + +/* Print Schannel error to log */ +void log_sec_err(int log_level, const char* title, SECURITY_STATUS ss) +{ + char *str; + DWORD len; + len = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ss, 0, (LPTSTR)&str, 0, NULL); + /* Trim new line chars */ + while (str[len-1] == '\r' || str[len-1] == '\n') str[--len] = 0; + + switch (log_level) { + case 1: + PJ_LOG(1, (SENDER, "%s: 0x%x-%s", title, ss, str)); + break; + case 2: + PJ_LOG(2, (SENDER, "%s: 0x%x-%s", title, ss, str)); + break; + case 3: + PJ_LOG(3, (SENDER, "%s: 0x%x-%s", title, ss, str)); + break; + case 4: + PJ_LOG(4, (SENDER, "%s: 0x%x-%s", title, ss, str)); + break; + case 5: + PJ_LOG(5, (SENDER, "%s: 0x%x-%s", title, ss, str)); + break; + default: + PJ_LOG(6, (SENDER, "%s: 0x%x-%s", title, ss, str)); + break; + } + + LocalFree(str); +} + + +/* === SSL socket implementations === */ + +/* Allocate SSL backend struct */ +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + sch_ssl_sock_t *sch_ssock = NULL; + + sch_inc(); + + sch_ssock = (sch_ssl_sock_t*) PJ_POOL_ZALLOC_T(pool, sch_ssl_sock_t); + if (!sch_ssock) + return NULL; + + SecInvalidateHandle(&sch_ssock->cred_handle); + SecInvalidateHandle(&sch_ssock->ctx_handle); + + return &sch_ssock->base; +} + +/* Create and initialize new SSL context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + pj_ssize_t read_cap; + pj_status_t status = PJ_SUCCESS; + + read_cap = PJ_MAX(MIN_READ_BUF_CAP, ssock->param.read_buffer_size); + + /* Allocate read buffer */ + sch_ssock->read_buf = + (pj_uint8_t*)pj_pool_zalloc(ssock->pool, read_cap); + if (!sch_ssock->read_buf) { + status = PJ_ENOMEM; + goto on_return; + } + + /* Initialize input circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, + read_cap); + if (status != PJ_SUCCESS) + goto on_return; + + /* Initialize output circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, + PJ_MAX(MIN_WRITE_BUF_CAP, + ssock->param.send_buffer_size)); + if (status != PJ_SUCCESS) + goto on_return; + + /* Initialize decrypted circular buffer */ + status = circ_init(ssock->pool->factory, &sch_ssock->decrypted_buf, + read_cap); + if (status != PJ_SUCCESS) + goto on_return; + +on_return: + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (SENDER, status, "Error allocating SSL")); + } + + return status; +} + + +/* Destroy SSL context and instance */ +static void ssl_destroy(pj_ssl_sock_t* ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + + /* Destroy circular buffers */ + circ_deinit(&ssock->circ_buf_input); + circ_deinit(&ssock->circ_buf_output); + circ_deinit(&sch_ssock->decrypted_buf); + + sch_dec(); +} + +/* Reset SSL socket state */ +static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + SECURITY_STATUS ss; + + LOG_DEBUG("SSL reset"); + + pj_lock_acquire(ssock->write_mutex); + + /* Signal shutdown only when SSL connection has been established */ + if (ssock->ssl_state == SSL_STATE_ESTABLISHED && + SecIsValidHandle(&sch_ssock->ctx_handle)) + { + DWORD type = SCHANNEL_SHUTDOWN; + + SecBuffer buf_in[1] = { {0} }; + buf_in[0].BufferType = SECBUFFER_TOKEN; + buf_in[0].pvBuffer = &type; + buf_in[0].cbBuffer = sizeof(type); + SecBufferDesc buf_desc_in = { 0 }; + buf_desc_in.ulVersion = SECBUFFER_VERSION; + buf_desc_in.cBuffers = ARRAYSIZE(buf_in); + buf_desc_in.pBuffers = buf_in; + ApplyControlToken(&sch_ssock->ctx_handle, &buf_desc_in); + + SecBuffer buf_out[1] = { {0} }; + buf_out[0].BufferType = SECBUFFER_TOKEN; + SecBufferDesc buf_desc_out = { 0 }; + buf_desc_out.ulVersion = SECBUFFER_VERSION; + buf_desc_out.cBuffers = ARRAYSIZE(buf_out); + buf_desc_out.pBuffers = buf_out; + + DWORD flags = + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_STREAM; + ss = InitializeSecurityContext(&sch_ssock->cred_handle, + &sch_ssock->ctx_handle, + NULL, + flags, 0, 0, + &buf_desc_out, 0, NULL, + &buf_desc_out, + &flags, NULL); + if (ss == SEC_E_OK) { + /* May need to send TLS shutdown packets */ + if (buf_out->cbBuffer > 0 && buf_out[0].pvBuffer) { + pj_status_t status; + + status = circ_write(&ssock->circ_buf_output, + buf_out[0].pvBuffer, + buf_out[0].cbBuffer); + FreeContextBuffer(buf_out[0].pvBuffer); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (SENDER, status, + "Failed queueing handshake packets")); + } else { + flush_circ_buf_output(ssock, &ssock->shutdown_op_key, + 0, 0); + } + } + } else { + log_sec_err(1, "Error in shutting down SSL", ss); + } + } + + ssock->ssl_state = SSL_STATE_NULL; + + if (SecIsValidHandle(&sch_ssock->ctx_handle)) { + DeleteSecurityContext(&sch_ssock->ctx_handle); + SecInvalidateHandle(&sch_ssock->ctx_handle); + } + + if (SecIsValidHandle(&sch_ssock->cred_handle)) { + FreeCredentialsHandle(&sch_ssock->cred_handle); + SecInvalidateHandle(&sch_ssock->cred_handle); + } + circ_reset(&ssock->circ_buf_input); + circ_reset(&ssock->circ_buf_output); + circ_reset(&sch_ssock->decrypted_buf); + + pj_lock_release(ssock->write_mutex); + + ssl_close_sockets(ssock); +} + +/* Ciphers and certs */ +static void ssl_ciphers_populate() +{ + PJ_TODO(implement_this); +} + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + SecPkgContext_CipherInfo ci; + SECURITY_STATUS ss; + + if (!SecIsValidHandle(&sch_ssock->ctx_handle)) { + return PJ_TLS_UNKNOWN_CIPHER; + } + + ss = QueryContextAttributes(&sch_ssock->ctx_handle, + SECPKG_ATTR_CIPHER_INFO, + &ci); + if (ss == SEC_E_OK) { + pj_ssl_cipher c = (pj_ssl_cipher)ci.dwCipherSuite; + + /* Add cipher to cipher list, if not yet */ + if (ssl_cipher_num < PJ_SSL_SOCK_MAX_CIPHERS && + !pj_ssl_cipher_name(c)) + { + char tmp_buf[SZ_ALG_MAX_SIZE]; + pj_str_t tmp_st; + + pj_unicode_to_ansi(ci.szCipherSuite, SZ_ALG_MAX_SIZE, + tmp_buf, sizeof(tmp_buf)); + pj_strdup2_with_null(sch_ssl.pool, &tmp_st, tmp_buf); + + ssl_ciphers[ssl_cipher_num].id = c; + ssl_ciphers[ssl_cipher_num].name = tmp_st.ptr; + ++ssl_cipher_num; + } + return (pj_ssl_cipher)ci.dwCipherSuite; + } + + return PJ_TLS_UNKNOWN_CIPHER; +} + +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + PJ_TODO(implement_this); +} + +/* SSL session functions */ +static void ssl_set_state(pj_ssl_sock_t* ssock, pj_bool_t is_server) +{ + /* Nothing to do */ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(is_server); +} + +static void ssl_set_peer_name(pj_ssl_sock_t* ssock) +{ + /* Nothing to do */ + PJ_UNUSED_ARG(ssock); +} + + +static PCCERT_CONTEXT create_self_signed_cert() +{ + CERT_CONTEXT const* cert_context = NULL; + CERT_EXTENSIONS cert_extensions = { 0 }; + + BYTE cert_name_buffer[16]; + CERT_NAME_BLOB cert_name; + cert_name.pbData = cert_name_buffer; + cert_name.cbData = ARRAYSIZE(cert_name_buffer); + + if (!CertStrToName(X509_ASN_ENCODING, "", CERT_X500_NAME_STR, NULL, + cert_name.pbData, &cert_name.cbData, NULL)) + { + return NULL; + } + + cert_context = CertCreateSelfSignCertificate( + 0, &cert_name, 0, NULL, + NULL, NULL, NULL, &cert_extensions); + + return cert_context; +} + + +static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + pj_size_t data_in_size = 0; + pj_uint8_t* data_in = NULL; + SECURITY_STATUS ss; + pj_status_t status = PJ_EPENDING; + pj_status_t status2; + + pj_lock_acquire(ssock->write_mutex); + + /* Create credential handle, if not yet */ + if (!SecIsValidHandle(&sch_ssock->cred_handle)) { + SCH_CREDENTIALS creds = { 0 }; + + creds.dwVersion = SCH_CREDENTIALS_VERSION; + creds.dwFlags = SCH_USE_STRONG_CRYPTO; + + if (ssock->is_server) { + PCCERT_CONTEXT cert_context = create_self_signed_cert(); + creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; + creds.cCreds = 1; + creds.paCred = &cert_context; + } else { + creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION | + // SCH_CRED_AUTO_CRED_VALIDATION | + SCH_CRED_NO_DEFAULT_CREDS; + } + + ss = AcquireCredentialsHandle(NULL, UNISP_NAME, + ssock->is_server? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, + NULL, &creds, NULL, NULL, + &sch_ssock->cred_handle, NULL); + if (ss < 0) { + log_sec_err(1, "Failed AcquireCredentialsHandle()", ss); + status = PJ_EUNKNOWN; + goto on_return; + } + } + + /* Start handshake iteration */ + + pj_lock_acquire(ssock->circ_buf_input_mutex); + + if (!circ_empty(&ssock->circ_buf_input)) { + data_in = sch_ssock->read_buf; + data_in_size = PJ_MIN(MIN_READ_BUF_CAP, + circ_size(&ssock->circ_buf_input)); + circ_read(&ssock->circ_buf_input, data_in, data_in_size); + } + + SecBuffer buf_in[2] = { {0} }; + buf_in[0].BufferType = SECBUFFER_TOKEN; + buf_in[0].pvBuffer = data_in; + buf_in[0].cbBuffer = (unsigned long)data_in_size; + buf_in[1].BufferType = SECBUFFER_EMPTY; + SecBufferDesc buf_desc_in = { 0 }; + buf_desc_in.ulVersion = SECBUFFER_VERSION; + buf_desc_in.cBuffers = ARRAYSIZE(buf_in); + buf_desc_in.pBuffers = buf_in; + + SecBuffer buf_out[1] = { {0} }; + buf_out[0].BufferType = SECBUFFER_TOKEN; + SecBufferDesc buf_desc_out = { 0 }; + buf_desc_out.ulVersion = SECBUFFER_VERSION; + buf_desc_out.cBuffers = ARRAYSIZE(buf_out); + buf_desc_out.pBuffers = buf_out; + + /* As client */ + if (!ssock->is_server) { + DWORD flags = + ISC_REQ_USE_SUPPLIED_CREDS | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_STREAM; + + ss = InitializeSecurityContext( + &sch_ssock->cred_handle, + SecIsValidHandle(&sch_ssock->ctx_handle)? + &sch_ssock->ctx_handle : NULL, + (SEC_CHAR*)ssock->param.server_name.ptr, + flags, 0, 0, + data_in_size? &buf_desc_in : NULL, + 0, + SecIsValidHandle(&sch_ssock->ctx_handle)? + NULL : &sch_ssock->ctx_handle, + &buf_desc_out, &flags, NULL); + } + + /* As server */ + else { + DWORD flags = + ASC_REQ_ALLOCATE_MEMORY | + ASC_REQ_CONFIDENTIALITY | + ASC_REQ_REPLAY_DETECT | + ASC_REQ_SEQUENCE_DETECT | + ASC_REQ_STREAM; + + ss = AcceptSecurityContext( + &sch_ssock->cred_handle, + SecIsValidHandle(&sch_ssock->ctx_handle) ? + &sch_ssock->ctx_handle : NULL, + data_in_size ? &buf_desc_in : NULL, + flags, 0, + SecIsValidHandle(&sch_ssock->ctx_handle) ? + NULL : &sch_ssock->ctx_handle, + &buf_desc_out, &flags, NULL); + } + + /* Check for any unprocessed input data, put it back to buffer */ + if (buf_in[1].BufferType==SECBUFFER_EXTRA && buf_in[1].cbBuffer>0) { + circ_read_cancel(&ssock->circ_buf_input, buf_in[1].cbBuffer); + } + + if (ss == SEC_E_OK) { + /* Handshake completed! */ + ssock->ssl_state = SSL_STATE_ESTABLISHED; + status = PJ_SUCCESS; + PJ_LOG(3, (SENDER, "TLS handshake completed!")); + + /* Get stream sizes */ + ss = QueryContextAttributes(&sch_ssock->ctx_handle, + SECPKG_ATTR_STREAM_SIZES, + &sch_ssock->strm_sizes); + if (ss != SEC_E_OK) { + log_sec_err(1, "Failed querying stream sizes", ss); + ssl_reset_sock_state(ssock); + status = PJ_EUNKNOWN; + } + + /* Allocate SSL write buf, if not yet */ + if (!sch_ssock->write_buf) { + pj_size_t size; + + sch_ssock->strm_sizes.cbMaximumMessage = + PJ_MIN(sch_ssock->strm_sizes.cbMaximumMessage, + (unsigned long)ssock->param.send_buffer_size); + sch_ssock->strm_sizes.cbMaximumMessage = + PJ_MAX(MIN_WRITE_BUF_CAP, + sch_ssock->strm_sizes.cbMaximumMessage); + + size = (pj_size_t)sch_ssock->strm_sizes.cbHeader + + sch_ssock->strm_sizes.cbMaximumMessage + + sch_ssock->strm_sizes.cbTrailer; + sch_ssock->write_buf = (pj_uint8_t*) + pj_pool_zalloc(ssock->pool, size); + } + } + + else if (ss == SEC_I_COMPLETE_NEEDED || + ss == SEC_I_COMPLETE_AND_CONTINUE) + { + /* Perhaps CompleteAuthToken() is unnecessary for Schannel, but + * the sample code seems to call it. + */ + LOG_DEBUG_ERR("Handshake progress", ss); + ss = CompleteAuthToken(&sch_ssock->ctx_handle, &buf_desc_out); + if (ss != SEC_E_OK) { + log_sec_err(1, "Handshake error in CompleteAuthToken()", ss); + status = PJ_EUNKNOWN; + } + } + + else if (ss == SEC_I_CONTINUE_NEEDED) + { + LOG_DEBUG_ERR("Handshake progress", ss); + } + + else if (ss == SEC_E_INCOMPLETE_MESSAGE) + { + LOG_DEBUG_ERR("Handshake progress", ss); + + /* Put back the incomplete message */ + circ_read_cancel(&ssock->circ_buf_input, data_in_size); + } + + else { + /* Handshake failed */ + log_sec_err(1, "Handshake failed!", ss); + status = PJ_EUNKNOWN; + } + + pj_lock_release(ssock->circ_buf_input_mutex); + + if (buf_out[0].cbBuffer > 0 && buf_out[0].pvBuffer) { + /* Queue output data to send */ + status2 = circ_write(&ssock->circ_buf_output, buf_out[0].pvBuffer, + buf_out[0].cbBuffer); + FreeContextBuffer(buf_out[0].pvBuffer); + if (status2 != PJ_SUCCESS) { + PJ_PERROR(1,(SENDER, status2, + "Failed queueing handshake packets")); + status = status2; + } + } + + /* Send handshake packets to wire */ + status2 = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status2 != PJ_SUCCESS && status2 != PJ_EPENDING) { + PJ_PERROR(1,(SENDER, status2, "Failed sending handshake packets")); + status = status2; + } + +on_return: + if (status != PJ_SUCCESS && status != PJ_EPENDING) + ssl_reset_sock_state(ssock); + + pj_lock_release(ssock->write_mutex); + + return status; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + PJ_TODO(implement_this); + return PJ_ENOTSUP; +} + +static int find_sec_buffer(const SecBuffer* buf, int buf_cnt, + unsigned long sec_type) +{ + for (int i = 0; i < buf_cnt; ++i) + if (buf[i].BufferType == sec_type) + return i; + return -1; +} + +static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + pj_size_t size_ = 0; + pj_uint8_t* data_ = NULL; + SECURITY_STATUS ss; + pj_status_t status = PJ_SUCCESS; + int i, need = *size, requested = *size; + + /* Avoid compile warning of unused debugging var */ + PJ_UNUSED_ARG(requested); + + pj_lock_acquire(ssock->circ_buf_input_mutex); + + /* Try read from the decrypted buffer */ + size_ = circ_size(&sch_ssock->decrypted_buf); + if (need <= size_) { + /* Got all from the decrypted buffer */ + circ_read(&sch_ssock->decrypted_buf, data, need); + *size = need; + LOG_DEBUG1("Read %d: returned all from decrypted buffer.", requested); + pj_lock_release(ssock->circ_buf_input_mutex); + return PJ_SUCCESS; + } + + /* Get all data of the decrypted buffer, then decrypt more */ + LOG_DEBUG2("Read %d: %d from decrypted buffer..", requested, size_); + circ_read(&sch_ssock->decrypted_buf, data, size_); + *size = (int)size_; + need -= (int)size_; + + /* Decrypt data of network input buffer */ + if (!circ_empty(&ssock->circ_buf_input)) { + data_ = sch_ssock->read_buf; + size_ = PJ_MIN(MIN_READ_BUF_CAP, circ_size(&ssock->circ_buf_input)); + circ_read(&ssock->circ_buf_input, data_, size_); + } else { + LOG_DEBUG2("Read %d: no data to decrypt, returned %d.", + requested, *size); + pj_lock_release(ssock->circ_buf_input_mutex); + return PJ_SUCCESS; + } + + SecBuffer buf[4] = { {0} }; + buf[0].BufferType = SECBUFFER_DATA; + buf[0].pvBuffer = data_; + buf[0].cbBuffer = (unsigned long)size_; + buf[1].BufferType = SECBUFFER_EMPTY; + buf[2].BufferType = SECBUFFER_EMPTY; + buf[3].BufferType = SECBUFFER_EMPTY; + SecBufferDesc buf_desc = { 0 }; + buf_desc.ulVersion = SECBUFFER_VERSION; + buf_desc.cBuffers = ARRAYSIZE(buf); + buf_desc.pBuffers = buf; + + ss = DecryptMessage(&sch_ssock->ctx_handle, &buf_desc, 0, NULL); + + /* Check for any unprocessed input data, put it back to buffer */ + i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_EXTRA); + if (i >= 0) { + circ_read_cancel(&ssock->circ_buf_input, buf[i].cbBuffer); + } + + if (ss == SEC_E_OK) { + i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_DATA); + if (i >= 0) { + pj_uint8_t *p = buf[i].pvBuffer; + pj_size_t len = buf[i].cbBuffer; + if (need <= len) { + /* All requested fulfilled, may have excess */ + pj_memcpy((pj_uint8_t*)data + *size, p, need); + *size += need; + len -= need; + p += need; + + /* Store any excess to the decrypted buffer */ + if (len) + circ_write(&sch_ssock->decrypted_buf, p, len); + + LOG_DEBUG2("Read %d: after decrypt, excess=%d", + requested, len); + } else { + /* Not enough, gave everyting */ + pj_memcpy((pj_uint8_t*)data + *size, p, len); + *size += (int)len; + LOG_DEBUG2("Read %d: after decrypt, only got %d", + requested, len); + } + } + } + + else if (ss == SEC_E_INCOMPLETE_MESSAGE) + { + /* Put back the incomplete message */ + circ_read_cancel(&ssock->circ_buf_input, size_); + } + + else if (ss == SEC_I_RENEGOTIATE) { + PJ_LOG(3, (SENDER, "Remote signals renegotiation")); + + if (SecIsValidHandle(&sch_ssock->ctx_handle)) { + DeleteSecurityContext(&sch_ssock->ctx_handle); + SecInvalidateHandle(&sch_ssock->ctx_handle); + } + + ssock->ssl_state = SSL_STATE_HANDSHAKING; + status = PJ_EEOF; + + /* Any unprocessed data should have been returned via buffer type + * SECBUFFER_EXTRA above (docs seems to say so). + */ + //circ_read_cancel(&ssock->circ_buf_input, size_); + } + + else if (ss == SEC_I_CONTEXT_EXPIRED) + { + PJ_LOG(3, (SENDER, "TLS connection closed")); + ssock->ssl_state = SSL_STATE_ERROR; + } + + else { + log_sec_err(1, "Decrypt error", ss); + status = PJ_EUNKNOWN; + } + + pj_lock_release(ssock->circ_buf_input_mutex); + + /* Reset SSL if it is not renegotiating */ + if (status != PJ_SUCCESS && ssock->ssl_state != SSL_STATE_HANDSHAKING) + ssl_reset_sock_state(ssock); + + LOG_DEBUG2("Read %d: returned=%d.", requested, *size); + return status; +} + + +static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, + pj_ssize_t size, int* nwritten) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + pj_ssize_t total = 0; + pj_status_t status = PJ_SUCCESS; + + pj_lock_acquire(ssock->write_mutex); + + while (total < size) { + SECURITY_STATUS ss; + pj_uint8_t *p_header, *p_data, *p_trailer; + pj_ssize_t write_len, out_size; + + write_len = PJ_MIN(size-total, sch_ssock->strm_sizes.cbMaximumMessage); + p_header = sch_ssock->write_buf; + p_data = p_header + sch_ssock->strm_sizes.cbHeader; + p_trailer = p_data + write_len; + + pj_memcpy(p_data, (pj_uint8_t*)data + total, write_len); + + SecBuffer buf[4] = { {0} }; + buf[0].BufferType = SECBUFFER_STREAM_HEADER; + buf[0].pvBuffer = p_header; + buf[0].cbBuffer = sch_ssock->strm_sizes.cbHeader; + buf[1].BufferType = SECBUFFER_DATA; + buf[1].pvBuffer = p_data; + buf[1].cbBuffer = (unsigned long)write_len; + buf[2].BufferType = SECBUFFER_STREAM_TRAILER; + buf[2].pvBuffer = p_trailer; + buf[2].cbBuffer = sch_ssock->strm_sizes.cbTrailer; + buf[3].BufferType = SECBUFFER_EMPTY; + + SecBufferDesc buf_desc = { 0 }; + buf_desc.ulVersion = SECBUFFER_VERSION; + buf_desc.cBuffers = ARRAYSIZE(buf); + buf_desc.pBuffers = buf; + + ss = EncryptMessage(&sch_ssock->ctx_handle, 0, &buf_desc, 0); + + if (ss != SEC_E_OK) { + log_sec_err(1, "Encrypt error", ss); + status = (ss==SEC_E_CONTEXT_EXPIRED)? PJ_EEOF : PJ_EUNKNOWN; + break; + } + + out_size = (pj_ssize_t)buf[0].cbBuffer + buf[1].cbBuffer + + buf[2].cbBuffer; + status = circ_write(&ssock->circ_buf_output, sch_ssock->write_buf, + out_size); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (SENDER, status, + "Failed queueing outgoing packets")); + break; + } + + total += write_len; + } + + pj_lock_release(ssock->write_mutex); + + *nwritten = (int)total; + + return status; +} + + +#endif /* PJ_HAS_SSL_SOCK */ From bb5853af3d0872fe104fcc195a822fe5c3a0f337 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 15 Feb 2024 16:07:16 +0700 Subject: [PATCH 02/20] Add getting cipher & cert info --- pjlib/src/pj/ssl_sock_schannel.c | 285 +++++++++++++++++++++++++++++-- 1 file changed, 269 insertions(+), 16 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 73def02261..50bb8aa3e8 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -78,9 +78,16 @@ #include #include +#include // for enumerating ciphers +//#include // for enumerating ciphers +//#include // for enumerating ciphers + + #pragma comment (lib, "secur32.lib") #pragma comment (lib, "shlwapi.lib") #pragma comment (lib, "Crypt32.lib") // for creating & manipulating certs +#pragma comment (lib, "Bcrypt.lib") // for enumerating ciphers +//#pragma comment (lib, "Ncrypt.lib") // for enumerating ciphers /* SSL sock implementation API */ @@ -356,7 +363,39 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) /* Ciphers and certs */ static void ssl_ciphers_populate() { - PJ_TODO(implement_this); + PCRYPT_CONTEXT_FUNCTIONS fn = NULL; + ULONG size = 0; + NTSTATUS s; + + /* Populate once only */ + if (ssl_cipher_num) + return; + + s = BCryptEnumContextFunctions(CRYPT_LOCAL, L"SSL", + NCRYPT_SCHANNEL_INTERFACE, + &size, &fn); + if (s < 0) { + PJ_LOG(1,(SENDER, "Error in enumerating ciphers (code=0x%x)", s)); + return; + } + + for (ULONG i = 0; i < fn->cFunctions; i++) { + char tmp_buf[SZ_ALG_MAX_SIZE]; + pj_str_t tmp_st; + + pj_unicode_to_ansi(fn->rgpszFunctions[i], SZ_ALG_MAX_SIZE, + tmp_buf, sizeof(tmp_buf)); + pj_strdup2_with_null(sch_ssl.pool, &tmp_st, tmp_buf); + + /* Unfortunately we cannot get the ID, set ID to 0 for now, + * may be updated later. + */ + ssl_ciphers[ssl_cipher_num].id = 0; + ssl_ciphers[ssl_cipher_num].name = tmp_st.ptr; + ++ssl_cipher_num; + } + + BCryptFreeBuffer(fn); } static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) @@ -375,30 +414,244 @@ static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) if (ss == SEC_E_OK) { pj_ssl_cipher c = (pj_ssl_cipher)ci.dwCipherSuite; - /* Add cipher to cipher list, if not yet */ + /* Check if this is in the cipher list */ if (ssl_cipher_num < PJ_SSL_SOCK_MAX_CIPHERS && !pj_ssl_cipher_name(c)) { - char tmp_buf[SZ_ALG_MAX_SIZE]; - pj_str_t tmp_st; + char tmp_buf[SZ_ALG_MAX_SIZE+1]; + unsigned i; pj_unicode_to_ansi(ci.szCipherSuite, SZ_ALG_MAX_SIZE, tmp_buf, sizeof(tmp_buf)); - pj_strdup2_with_null(sch_ssl.pool, &tmp_st, tmp_buf); - ssl_ciphers[ssl_cipher_num].id = c; - ssl_ciphers[ssl_cipher_num].name = tmp_st.ptr; - ++ssl_cipher_num; + /* If cipher is in the list but ID is 0, update it + * (we init'd cipher list without ID) + */ + for (i = 0; i < ssl_cipher_num; ++i) { + if (!pj_ansi_stricmp(ssl_ciphers[i].name, tmp_buf)) { + if (ssl_ciphers[i].id == 0) { + ssl_ciphers[i].id = c; + break; + } + } + } + + /* Add to cipher list if not found */ + if (i == ssl_cipher_num) { + pj_str_t tmp_st; + pj_strdup2_with_null(sch_ssl.pool, &tmp_st, tmp_buf); + + ssl_ciphers[ssl_cipher_num].id = c; + ssl_ciphers[ssl_cipher_num].name = tmp_st.ptr; + ++ssl_cipher_num; + } } - return (pj_ssl_cipher)ci.dwCipherSuite; + return c; } return PJ_TLS_UNKNOWN_CIPHER; } +static pj_status_t blob_to_str(DWORD enc_type, CERT_NAME_BLOB* blob, + DWORD flag, char *buf, unsigned buf_len) +{ + DWORD ret; + ret = CertNameToStrA(enc_type, blob, flag, buf, buf_len); + if (ret < 0) { + PJ_LOG(3,(SENDER, "Failed convert cert blob to string")); + return PJ_ETOOSMALL; + } + return PJ_SUCCESS; +} + + +static pj_status_t file_time_to_time_val(const FILETIME* file_time, + pj_time_val* time_val) +{ + FILETIME local_file_time; + SYSTEMTIME localTime; + pj_parsed_time pt; + + if (!FileTimeToLocalFileTime(file_time, &local_file_time)) + return PJ_RETURN_OS_ERROR(GetLastError()); + + if (!FileTimeToSystemTime(file_time, &localTime)) + return PJ_RETURN_OS_ERROR(GetLastError()); + + pj_bzero(&pt, sizeof(pt)); + pt.year = localTime.wYear; + pt.mon = localTime.wMonth - 1; + pt.day = localTime.wDay; + pt.wday = localTime.wDayOfWeek; + + pt.hour = localTime.wHour; + pt.min = localTime.wMinute; + pt.sec = localTime.wSecond; + pt.msec = localTime.wMilliseconds; + + return pj_time_encode(&pt, time_val); +} + + +static void cert_parse_info(pj_pool_t* pool, pj_ssl_cert_info* ci, + const CERT_CONTEXT *cert) +{ + PCERT_INFO cert_info = cert->pCertInfo; + char buf[512]; + pj_uint8_t serial_no[20]; + unsigned serial_size; + pj_bool_t update_needed; + pj_status_t status; + + /* Get issuer & serial no first */ + status = blob_to_str(cert->dwCertEncodingType, &cert_info->Issuer, + CERT_SIMPLE_NAME_STR, + buf, sizeof(buf)); + + serial_size = PJ_MIN(cert_info->SerialNumber.cbData, sizeof(serial_no)); + pj_memcpy(&serial_no, cert_info->SerialNumber.pbData, serial_size); + + /* Check if the contents need to be updated */ + update_needed = status == PJ_SUCCESS && + (pj_strcmp2(&ci->issuer.info, buf) || + pj_memcmp(ci->serial_no, serial_no, serial_size)); + if (!update_needed) + return; + + /* Update info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = cert_info->dwVersion; + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + status = blob_to_str(cert->dwCertEncodingType, &cert_info->Issuer, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + buf, sizeof(buf)); + if (status == PJ_SUCCESS) + pj_strdup2(pool, &ci->issuer.cn, buf); + + /* Serial number */ + pj_memcpy(ci->serial_no, serial_no, serial_size); + + /* Subject */ + status = blob_to_str(cert->dwCertEncodingType, &cert_info->Subject, + CERT_SIMPLE_NAME_STR, + buf, sizeof(buf)); + if (status == PJ_SUCCESS) + pj_strdup2(pool, &ci->subject.info, buf); + + status = blob_to_str(cert->dwCertEncodingType, &cert_info->Subject, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + buf, sizeof(buf)); + if (status == PJ_SUCCESS) + pj_strdup2(pool, &ci->subject.cn, buf); + + /* Validity */ + file_time_to_time_val(&cert_info->NotAfter, &ci->validity.end); + file_time_to_time_val(&cert_info->NotBefore, &ci->validity.start); + ci->validity.gmt = 0; + + /* Subject Alternative Name extension */ + while (1) { + PCERT_EXTENSION ext = CertFindExtension(szOID_SUBJECT_ALT_NAME2, + cert_info->cExtension, + cert_info->rgExtension); + if (!ext) + break; + + CERT_ALT_NAME_INFO* alt_name_info = NULL; + DWORD alt_name_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(cert->dwCertEncodingType, + szOID_SUBJECT_ALT_NAME2, + ext->Value.pbData, + ext->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | + CRYPT_DECODE_NOCOPY_FLAG, + NULL, + &alt_name_info, + &alt_name_info_size); + if (!rv) + break; + + ci->subj_alt_name.entry = pj_pool_calloc( + pool, alt_name_info->cAltEntry, + sizeof(*ci->subj_alt_name.entry)); + if (!ci->subj_alt_name.entry) { + PJ_LOG(3,(SENDER, "Failed allocate memory for subject alt name")); + LocalFree(alt_name_info); + break; + } + + for (unsigned i = 0; i < alt_name_info->cAltEntry; ++i) { + CERT_ALT_NAME_ENTRY *ane = &alt_name_info->rgAltEntry[i]; + pj_ssl_cert_name_type type; + unsigned len = 0; + + switch (ane->dwAltNameChoice) { + case CERT_ALT_NAME_DNS_NAME: + type = PJ_SSL_CERT_NAME_DNS; + len = pj_unicode_to_ansi(ane->pwszDNSName, sizeof(buf), + buf, sizeof(buf)) != NULL; + break; + case CERT_ALT_NAME_IP_ADDRESS: + type = PJ_SSL_CERT_NAME_IP; + pj_inet_ntop2(ane->IPAddress.cbData == sizeof(pj_in6_addr)? + pj_AF_INET6() : pj_AF_INET(), + ane->IPAddress.pbData, buf, sizeof(buf)); + break; + case CERT_ALT_NAME_URL: + type = PJ_SSL_CERT_NAME_URI; + len = pj_unicode_to_ansi(ane->pwszDNSName, sizeof(buf), + buf, sizeof(buf)) != NULL; + break; + case CERT_ALT_NAME_RFC822_NAME: + type = PJ_SSL_CERT_NAME_RFC822; + len = pj_unicode_to_ansi(ane->pwszDNSName, sizeof(buf), + buf, sizeof(buf)) != NULL; + break; + default: + type = PJ_SSL_CERT_NAME_UNKNOWN; + break; + } + + if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + buf); + ci->subj_alt_name.cnt++; + } + } + + /* Done parsing Subject Alt Name */ + LocalFree(alt_name_info); + break; + } +} + static void ssl_update_certs_info(pj_ssl_sock_t *ssock) { - PJ_TODO(implement_this); + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + CERT_CONTEXT *cert_ctx = NULL; + SECURITY_STATUS ss; + + if (!SecIsValidHandle(&sch_ssock->ctx_handle)) { + return; + } + + ss = QueryContextAttributes(&sch_ssock->ctx_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &cert_ctx); + + if (ss != SEC_E_OK || !cert_ctx) { + log_sec_err(3, "Failed getting remote certificate", ss); + } else { + cert_parse_info(ssock->pool, &ssock->remote_cert_info, cert_ctx); + } } /* SSL session functions */ @@ -418,8 +671,8 @@ static void ssl_set_peer_name(pj_ssl_sock_t* ssock) static PCCERT_CONTEXT create_self_signed_cert() { - CERT_CONTEXT const* cert_context = NULL; - CERT_EXTENSIONS cert_extensions = { 0 }; + PCCERT_CONTEXT cert_ctx = NULL; + CERT_EXTENSIONS cert_ext = { 0 }; BYTE cert_name_buffer[16]; CERT_NAME_BLOB cert_name; @@ -432,11 +685,11 @@ static PCCERT_CONTEXT create_self_signed_cert() return NULL; } - cert_context = CertCreateSelfSignCertificate( + cert_ctx = CertCreateSelfSignCertificate( 0, &cert_name, 0, NULL, - NULL, NULL, NULL, &cert_extensions); + NULL, NULL, NULL, &cert_ext); - return cert_context; + return cert_ctx; } @@ -751,7 +1004,7 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) LOG_DEBUG2("Read %d: after decrypt, excess=%d", requested, len); } else { - /* Not enough, gave everyting */ + /* Not enough, give everyting */ pj_memcpy((pj_uint8_t*)data + *size, p, len); *size += (int)len; LOG_DEBUG2("Read %d: after decrypt, only got %d", From e57d4a8eb9a714e49985747406b6d23e5ac37c2a Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 16 Feb 2024 10:37:52 +0700 Subject: [PATCH 03/20] Mem management: use preallocated send buffer instead of relying on system alloc/free, reuse self-signed cert. Minor: update log strings. --- pjlib/src/pj/ssl_sock_schannel.c | 148 ++++++++++++++++++------------- 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 50bb8aa3e8..e7069f96c7 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -46,7 +46,7 @@ #define THIS_FILE "ssl_sock_schannel.c" /* Sender string in logging */ -#define SENDER "Schannel" +#define SENDER "ssl_schannel" /* Debugging */ #define DEBUG_SCHANNEL 0 @@ -106,6 +106,7 @@ static struct sch_ssl_t pj_caching_pool cp; pj_pool_t *pool; unsigned long init_cnt; + PCCERT_CONTEXT self_signed_cert; } sch_ssl; @@ -120,7 +121,9 @@ typedef struct sch_ssl_sock_t CtxtHandle ctx_handle; SecPkgContext_StreamSizes strm_sizes; pj_uint8_t *write_buf; + pj_size_t write_buf_cap; pj_uint8_t *read_buf; + pj_size_t read_buf_cap; circ_buf_t decrypted_buf; } sch_ssl_sock_t; @@ -132,6 +135,9 @@ typedef struct sch_ssl_sock_t static void sch_deinit(void) { + if (sch_ssl.self_signed_cert) + CertFreeCertificateContext(sch_ssl.self_signed_cert); + if (sch_ssl.pool) { pj_pool_secure_release(&sch_ssl.pool); pj_caching_pool_destroy(&sch_ssl.cp); @@ -218,35 +224,42 @@ static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) static pj_status_t ssl_create(pj_ssl_sock_t *ssock) { sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; - pj_ssize_t read_cap; + pj_pool_factory* pf = ssock->pool->factory; + pj_pool_t* pool = ssock->pool; + pj_ssize_t read_cap, write_cap; pj_status_t status = PJ_SUCCESS; - read_cap = PJ_MAX(MIN_READ_BUF_CAP, ssock->param.read_buffer_size); + read_cap = PJ_MAX(MIN_READ_BUF_CAP, ssock->param.read_buffer_size); + write_cap = PJ_MAX(MIN_WRITE_BUF_CAP, ssock->param.send_buffer_size); /* Allocate read buffer */ - sch_ssock->read_buf = - (pj_uint8_t*)pj_pool_zalloc(ssock->pool, read_cap); + sch_ssock->read_buf_cap = read_cap; + sch_ssock->read_buf = (pj_uint8_t*)pj_pool_zalloc(pool, read_cap); if (!sch_ssock->read_buf) { status = PJ_ENOMEM; goto on_return; } + /* Allocate write buffer */ + sch_ssock->write_buf_cap = write_cap; + sch_ssock->write_buf = (pj_uint8_t*)pj_pool_zalloc(pool, write_cap); + if (!sch_ssock->write_buf) { + status = PJ_ENOMEM; + goto on_return; + } + /* Initialize input circular buffer */ - status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, - read_cap); + status = circ_init(pf, &ssock->circ_buf_input, read_cap); if (status != PJ_SUCCESS) goto on_return; /* Initialize output circular buffer */ - status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, - PJ_MAX(MIN_WRITE_BUF_CAP, - ssock->param.send_buffer_size)); + status = circ_init(pf, &ssock->circ_buf_output, write_cap); if (status != PJ_SUCCESS) goto on_return; /* Initialize decrypted circular buffer */ - status = circ_init(ssock->pool->factory, &sch_ssock->decrypted_buf, - read_cap); + status = circ_init(pf, &sch_ssock->decrypted_buf, read_cap); if (status != PJ_SUCCESS) goto on_return; @@ -300,13 +313,14 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) SecBuffer buf_out[1] = { {0} }; buf_out[0].BufferType = SECBUFFER_TOKEN; + buf_out[0].pvBuffer = sch_ssock->write_buf; + buf_out[0].cbBuffer = (ULONG)sch_ssock->write_buf_cap; SecBufferDesc buf_desc_out = { 0 }; buf_desc_out.ulVersion = SECBUFFER_VERSION; buf_desc_out.cBuffers = ARRAYSIZE(buf_out); buf_desc_out.pBuffers = buf_out; DWORD flags = - ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | @@ -326,10 +340,9 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) status = circ_write(&ssock->circ_buf_output, buf_out[0].pvBuffer, buf_out[0].cbBuffer); - FreeContextBuffer(buf_out[0].pvBuffer); if (status != PJ_SUCCESS) { PJ_PERROR(1, (SENDER, status, - "Failed queueing handshake packets")); + "Failed to queuehandshake packets")); } else { flush_circ_buf_output(ssock, &ssock->shutdown_op_key, 0, 0); @@ -458,7 +471,7 @@ static pj_status_t blob_to_str(DWORD enc_type, CERT_NAME_BLOB* blob, DWORD ret; ret = CertNameToStrA(enc_type, blob, flag, buf, buf_len); if (ret < 0) { - PJ_LOG(3,(SENDER, "Failed convert cert blob to string")); + PJ_LOG(3,(SENDER, "Failed to convert cert blob to string")); return PJ_ETOOSMALL; } return PJ_SUCCESS; @@ -581,7 +594,7 @@ static void cert_parse_info(pj_pool_t* pool, pj_ssl_cert_info* ci, pool, alt_name_info->cAltEntry, sizeof(*ci->subj_alt_name.entry)); if (!ci->subj_alt_name.entry) { - PJ_LOG(3,(SENDER, "Failed allocate memory for subject alt name")); + PJ_LOG(3,(SENDER, "Failed to allocate memory for SubjectAltName")); LocalFree(alt_name_info); break; } @@ -648,10 +661,23 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) &cert_ctx); if (ss != SEC_E_OK || !cert_ctx) { - log_sec_err(3, "Failed getting remote certificate", ss); + if (!ssock->is_server) + log_sec_err(1, "Failed to retrieve remote certificate", ss); } else { cert_parse_info(ssock->pool, &ssock->remote_cert_info, cert_ctx); } + + ss = QueryContextAttributes(&sch_ssock->ctx_handle, + SECPKG_ATTR_LOCAL_CERT_CONTEXT, + &cert_ctx); + + if (ss != SEC_E_OK || !cert_ctx) { + if (ssock->is_server) + log_sec_err(3, "Failed to retrieve local certificate", ss); + } + else { + cert_parse_info(ssock->pool, &ssock->local_cert_info, cert_ctx); + } } /* SSL session functions */ @@ -671,25 +697,27 @@ static void ssl_set_peer_name(pj_ssl_sock_t* ssock) static PCCERT_CONTEXT create_self_signed_cert() { - PCCERT_CONTEXT cert_ctx = NULL; - CERT_EXTENSIONS cert_ext = { 0 }; + if (!sch_ssl.self_signed_cert) { + CERT_EXTENSIONS cert_ext = { 0 }; + BYTE cert_name_buffer[64]; + CERT_NAME_BLOB cert_name; + cert_name.pbData = cert_name_buffer; + cert_name.cbData = ARRAYSIZE(cert_name_buffer); - BYTE cert_name_buffer[16]; - CERT_NAME_BLOB cert_name; - cert_name.pbData = cert_name_buffer; - cert_name.cbData = ARRAYSIZE(cert_name_buffer); + if (!CertStrToNameA(X509_ASN_ENCODING, "CN=lab.pjsip.org", + CERT_X500_NAME_STR, NULL, + cert_name.pbData, &cert_name.cbData, NULL)) + { + return NULL; + } - if (!CertStrToName(X509_ASN_ENCODING, "", CERT_X500_NAME_STR, NULL, - cert_name.pbData, &cert_name.cbData, NULL)) - { - return NULL; + sch_ssl.self_signed_cert = CertCreateSelfSignCertificate( + 0, &cert_name, 0, NULL, + NULL, NULL, NULL, + &cert_ext); } - cert_ctx = CertCreateSelfSignCertificate( - 0, &cert_name, 0, NULL, - NULL, NULL, NULL, &cert_ext); - - return cert_ctx; + return sch_ssl.self_signed_cert; } @@ -706,13 +734,14 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Create credential handle, if not yet */ if (!SecIsValidHandle(&sch_ssock->cred_handle)) { + PCCERT_CONTEXT cert_context; SCH_CREDENTIALS creds = { 0 }; creds.dwVersion = SCH_CREDENTIALS_VERSION; creds.dwFlags = SCH_USE_STRONG_CRYPTO; - if (ssock->is_server) { - PCCERT_CONTEXT cert_context = create_self_signed_cert(); + if (ssock->is_server && create_self_signed_cert()) { + cert_context = create_self_signed_cert(); creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; creds.cCreds = 1; creds.paCred = &cert_context; @@ -727,7 +756,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) NULL, &creds, NULL, NULL, &sch_ssock->cred_handle, NULL); if (ss < 0) { - log_sec_err(1, "Failed AcquireCredentialsHandle()", ss); + log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); status = PJ_EUNKNOWN; goto on_return; } @@ -739,7 +768,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) if (!circ_empty(&ssock->circ_buf_input)) { data_in = sch_ssock->read_buf; - data_in_size = PJ_MIN(MIN_READ_BUF_CAP, + data_in_size = PJ_MIN(sch_ssock->read_buf_cap, circ_size(&ssock->circ_buf_input)); circ_read(&ssock->circ_buf_input, data_in, data_in_size); } @@ -747,7 +776,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) SecBuffer buf_in[2] = { {0} }; buf_in[0].BufferType = SECBUFFER_TOKEN; buf_in[0].pvBuffer = data_in; - buf_in[0].cbBuffer = (unsigned long)data_in_size; + buf_in[0].cbBuffer = (ULONG)data_in_size; buf_in[1].BufferType = SECBUFFER_EMPTY; SecBufferDesc buf_desc_in = { 0 }; buf_desc_in.ulVersion = SECBUFFER_VERSION; @@ -756,6 +785,8 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) SecBuffer buf_out[1] = { {0} }; buf_out[0].BufferType = SECBUFFER_TOKEN; + buf_out[0].pvBuffer = sch_ssock->write_buf; + buf_out[0].cbBuffer = (ULONG)sch_ssock->write_buf_cap; SecBufferDesc buf_desc_out = { 0 }; buf_desc_out.ulVersion = SECBUFFER_VERSION; buf_desc_out.cBuffers = ARRAYSIZE(buf_out); @@ -765,7 +796,6 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) if (!ssock->is_server) { DWORD flags = ISC_REQ_USE_SUPPLIED_CREDS | - ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | @@ -787,7 +817,6 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* As server */ else { DWORD flags = - ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONFIDENTIALITY | ASC_REQ_REPLAY_DETECT | ASC_REQ_SEQUENCE_DETECT | @@ -820,27 +849,20 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) SECPKG_ATTR_STREAM_SIZES, &sch_ssock->strm_sizes); if (ss != SEC_E_OK) { - log_sec_err(1, "Failed querying stream sizes", ss); + log_sec_err(1, "Failed to query stream sizes", ss); ssl_reset_sock_state(ssock); status = PJ_EUNKNOWN; } - /* Allocate SSL write buf, if not yet */ + /* Adjust maximum message size to our allocated buffer size */ if (!sch_ssock->write_buf) { - pj_size_t size; + pj_size_t max_msg = sch_ssock->write_buf_cap - + sch_ssock->strm_sizes.cbHeader - + sch_ssock->strm_sizes.cbTrailer; sch_ssock->strm_sizes.cbMaximumMessage = - PJ_MIN(sch_ssock->strm_sizes.cbMaximumMessage, - (unsigned long)ssock->param.send_buffer_size); - sch_ssock->strm_sizes.cbMaximumMessage = - PJ_MAX(MIN_WRITE_BUF_CAP, - sch_ssock->strm_sizes.cbMaximumMessage); - - size = (pj_size_t)sch_ssock->strm_sizes.cbHeader + - sch_ssock->strm_sizes.cbMaximumMessage + - sch_ssock->strm_sizes.cbTrailer; - sch_ssock->write_buf = (pj_uint8_t*) - pj_pool_zalloc(ssock->pool, size); + PJ_MIN((ULONG)max_msg, + sch_ssock->strm_sizes.cbMaximumMessage); } } @@ -879,14 +901,15 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) pj_lock_release(ssock->circ_buf_input_mutex); - if (buf_out[0].cbBuffer > 0 && buf_out[0].pvBuffer) { + if ((ss == SEC_E_OK || ss == SEC_I_CONTINUE_NEEDED) && + buf_out[0].cbBuffer > 0 && buf_out[0].pvBuffer) + { /* Queue output data to send */ status2 = circ_write(&ssock->circ_buf_output, buf_out[0].pvBuffer, buf_out[0].cbBuffer); - FreeContextBuffer(buf_out[0].pvBuffer); if (status2 != PJ_SUCCESS) { PJ_PERROR(1,(SENDER, status2, - "Failed queueing handshake packets")); + "Failed to queue handshake packets")); status = status2; } } @@ -894,7 +917,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Send handshake packets to wire */ status2 = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); if (status2 != PJ_SUCCESS && status2 != PJ_EPENDING) { - PJ_PERROR(1,(SENDER, status2, "Failed sending handshake packets")); + PJ_PERROR(1,(SENDER, status2, "Failed to send handshake packets")); status = status2; } @@ -956,7 +979,8 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) /* Decrypt data of network input buffer */ if (!circ_empty(&ssock->circ_buf_input)) { data_ = sch_ssock->read_buf; - size_ = PJ_MIN(MIN_READ_BUF_CAP, circ_size(&ssock->circ_buf_input)); + size_ = PJ_MIN(sch_ssock->read_buf_cap, + circ_size(&ssock->circ_buf_input)); circ_read(&ssock->circ_buf_input, data_, size_); } else { LOG_DEBUG2("Read %d: no data to decrypt, returned %d.", @@ -968,7 +992,7 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) SecBuffer buf[4] = { {0} }; buf[0].BufferType = SECBUFFER_DATA; buf[0].pvBuffer = data_; - buf[0].cbBuffer = (unsigned long)size_; + buf[0].cbBuffer = (ULONG)size_; buf[1].BufferType = SECBUFFER_EMPTY; buf[2].BufferType = SECBUFFER_EMPTY; buf[3].BufferType = SECBUFFER_EMPTY; @@ -1085,7 +1109,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, buf[0].cbBuffer = sch_ssock->strm_sizes.cbHeader; buf[1].BufferType = SECBUFFER_DATA; buf[1].pvBuffer = p_data; - buf[1].cbBuffer = (unsigned long)write_len; + buf[1].cbBuffer = (ULONG)write_len; buf[2].BufferType = SECBUFFER_STREAM_TRAILER; buf[2].pvBuffer = p_trailer; buf[2].cbBuffer = sch_ssock->strm_sizes.cbTrailer; @@ -1110,7 +1134,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, out_size); if (status != PJ_SUCCESS) { PJ_PERROR(1, (SENDER, status, - "Failed queueing outgoing packets")); + "Failed to queue outgoing packets")); break; } From 8cafc330b49aa3cc763acbc89dd561739ff89608 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 16 Feb 2024 15:47:25 +0700 Subject: [PATCH 04/20] Implement TLS certificate setting: read cert from OS cert store. --- pjlib/include/pj/ssl_sock.h | 11 ++++ pjlib/src/pj/ssl_sock_schannel.c | 78 ++++++++++++++++++++++--- pjsip/include/pjsip/sip_transport_tls.h | 12 ++++ pjsip/include/pjsua2/siptypes.hpp | 11 ++++ pjsip/src/pjsip/sip_transport_tls.c | 1 + pjsip/src/pjsua2/siptypes.cpp | 4 ++ 6 files changed, 109 insertions(+), 8 deletions(-) diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h index f82953af8c..d284703db1 100644 --- a/pjlib/include/pj/ssl_sock.h +++ b/pjlib/include/pj/ssl_sock.h @@ -1041,6 +1041,17 @@ typedef struct pj_ssl_sock_param */ pj_str_t server_name; + /** + * For Windows SSPI Schannel backend. This specifies the subject keyword + * used for searching certificate in OS certificate stores. The search + * will be performed in local machine and user account stores. + * + * The certificate will be used as client-side certificate for outgoing + * TLS connection, and server-side certificate for incoming TLS + * connection. + */ + pj_str_t cert_subject; + /** * Specify if SO_REUSEADDR should be used for listening socket. This * option will only be used with accept() operation. diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index e7069f96c7..318f891e84 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -119,7 +119,9 @@ typedef struct sch_ssl_sock_t CredHandle cred_handle; CtxtHandle ctx_handle; + PCCERT_CONTEXT cert_ctx; SecPkgContext_StreamSizes strm_sizes; + pj_uint8_t *write_buf; pj_size_t write_buf_cap; pj_uint8_t *read_buf; @@ -282,6 +284,12 @@ static void ssl_destroy(pj_ssl_sock_t* ssock) circ_deinit(&ssock->circ_buf_output); circ_deinit(&sch_ssock->decrypted_buf); + /* Free certificate */ + if (sch_ssock->cert_ctx) { + CertFreeCertificateContext(sch_ssock->cert_ctx); + sch_ssock->cert_ctx = NULL; + } + sch_dec(); } @@ -488,7 +496,7 @@ static pj_status_t file_time_to_time_val(const FILETIME* file_time, if (!FileTimeToLocalFileTime(file_time, &local_file_time)) return PJ_RETURN_OS_ERROR(GetLastError()); - if (!FileTimeToSystemTime(file_time, &localTime)) + if (!FileTimeToSystemTime(&local_file_time, &localTime)) return PJ_RETURN_OS_ERROR(GetLastError()); pj_bzero(&pt, sizeof(pt)); @@ -717,7 +725,45 @@ static PCCERT_CONTEXT create_self_signed_cert() &cert_ext); } - return sch_ssl.self_signed_cert; + return CertDuplicateCertificateContext(sch_ssl.self_signed_cert); +} + +static PCCERT_CONTEXT find_cert_in_stores(const pj_str_t *subject) +{ + HCERTSTORE store = NULL; + PCCERT_CONTEXT cert = NULL; + + /* Find in Current User store */ + store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING, 0, + CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); + if (store) { + cert = CertFindCertificateInStore(store, X509_ASN_ENCODING, + 0, CERT_FIND_SUBJECT_STR_A, subject->ptr, NULL); + CertCloseStore(store, 0); + if (cert) + return cert; + } else { + log_sec_err(1, "Error opening current user cert store.", + GetLastError()); + } + + /* Find in Local Machine store */ + store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING, 0, + CERT_SYSTEM_STORE_LOCAL_MACHINE, L"MY"); + if (store) { + cert = CertFindCertificateInStore(store, X509_ASN_ENCODING, + 0, CERT_FIND_SUBJECT_STR_A, subject->ptr, NULL); + CertCloseStore(store, 0); + if (cert) + return cert; + } else { + log_sec_err(1, "Error opening local machine cert store.", + GetLastError()); + } + + PJ_LOG(1,(SENDER, "Cannot find certificate with specified subject: %.*s", + subject->slen, subject->ptr)); + return NULL; } @@ -734,22 +780,38 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Create credential handle, if not yet */ if (!SecIsValidHandle(&sch_ssock->cred_handle)) { - PCCERT_CONTEXT cert_context; SCH_CREDENTIALS creds = { 0 }; creds.dwVersion = SCH_CREDENTIALS_VERSION; creds.dwFlags = SCH_USE_STRONG_CRYPTO; - if (ssock->is_server && create_self_signed_cert()) { - cert_context = create_self_signed_cert(); - creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; - creds.cCreds = 1; - creds.paCred = &cert_context; + if (ssock->param.cert_subject.slen && !sch_ssock->cert_ctx) { + /* Search certificate from stores */ + sch_ssock->cert_ctx = + find_cert_in_stores(&ssock->param.cert_subject); + } + + if (ssock->is_server) { + if (!ssock->param.cert_subject.slen) { + /* No certificate subject to search, use self-signed cert */ + sch_ssock->cert_ctx = create_self_signed_cert(); + + // -- test code -- + //pj_str_t test = {"test.pjsip.org", 14}; + //sch_ssock->cert_ctx = + //find_cert_in_stores(&test); + } } else { creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION | // SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS; } + + /* Use the certificate, if specified */ + if (sch_ssock->cert_ctx) { + creds.cCreds = 1; + creds.paCred = &sch_ssock->cert_ctx; + } ss = AcquireCredentialsHandle(NULL, UNISP_NAME, ssock->is_server? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index c902765eeb..856dc309b1 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -188,6 +188,17 @@ typedef struct pjsip_tls_setting */ pj_ssl_cert_buffer privkey_buf; + /** + * For Windows SSPI Schannel backend. This specifies the subject keyword + * used for searching certificate in OS certificate stores. The search + * will be performed in local machine and user account stores. + * + * The certificate will be used as client-side certificate for outgoing + * TLS connection, and server-side certificate for incoming TLS + * connection. + */ + pj_str_t cert_subject; + /** * Password to open private key. */ @@ -466,6 +477,7 @@ PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, pj_strdup_with_null(pool, &dst->ca_list_path, &src->ca_list_path); pj_strdup_with_null(pool, &dst->cert_file, &src->cert_file); pj_strdup_with_null(pool, &dst->privkey_file, &src->privkey_file); + pj_strdup_with_null(pool, &dst->cert_subject, &src->cert_subject); pj_strdup_with_null(pool, &dst->password, &src->password); pj_strdup_with_null(pool, &dst->sigalgs, &src->sigalgs); pj_strdup_with_null(pool, &dst->entropy_path, &src->entropy_path); diff --git a/pjsip/include/pjsua2/siptypes.hpp b/pjsip/include/pjsua2/siptypes.hpp index 7b7c079a46..6d40f41fbe 100644 --- a/pjsip/include/pjsua2/siptypes.hpp +++ b/pjsip/include/pjsua2/siptypes.hpp @@ -174,6 +174,17 @@ struct TlsConfig : public PersistentObject */ string privKeyBuf; + /** + * For Windows SSPI Schannel backend. This specifies the subject keyword + * used for searching certificate in OS certificate stores. The search + * will be performed in local machine and user account stores. + * + * The certificate will be used as client-side certificate for outgoing + * TLS connection, and server-side certificate for incoming TLS + * connection. + */ + string certSubject; + /** * TLS protocol method from #pjsip_ssl_method. In the future, this field * might be deprecated in favor of proto field. For now, this field diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c index 50accc9191..ade4924842 100644 --- a/pjsip/src/pjsip/sip_transport_tls.c +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -351,6 +351,7 @@ static void set_ssock_param(pj_ssl_sock_param *ssock_param, sip_ssl_method = listener->tls_setting.method; sip_ssl_proto = listener->tls_setting.proto; ssock_param->proto = ssl_get_proto(sip_ssl_method, sip_ssl_proto); + ssock_param->cert_subject = listener->tls_setting.cert_subject; } static void update_bound_addr(struct tls_listener *listener, diff --git a/pjsip/src/pjsua2/siptypes.cpp b/pjsip/src/pjsua2/siptypes.cpp index 331e3245b8..606a3039c4 100644 --- a/pjsip/src/pjsua2/siptypes.cpp +++ b/pjsip/src/pjsua2/siptypes.cpp @@ -192,6 +192,7 @@ pjsip_tls_setting TlsConfig::toPj() const ts.ca_buf = str2Pj(this->CaBuf); ts.cert_buf = str2Pj(this->certBuf); ts.privkey_buf = str2Pj(this->privKeyBuf); + ts.cert_subject = str2Pj(this->certSubject); ts.method = this->method; ts.ciphers_num = (unsigned)this->ciphers.size(); ts.proto = this->proto; @@ -221,6 +222,7 @@ void TlsConfig::fromPj(const pjsip_tls_setting &prm) this->CaBuf = pj2Str(prm.ca_buf); this->certBuf = pj2Str(prm.cert_buf); this->privKeyBuf = pj2Str(prm.privkey_buf); + this->certSubject = pj2Str(prm.cert_subject); this->method = (pjsip_ssl_method)prm.method; this->proto = prm.proto; // The following will only work if sizeof(enum)==sizeof(int) @@ -256,6 +258,7 @@ void TlsConfig::readObject(const ContainerNode &node) PJSUA2_THROW(Error) NODE_READ_NUM_T ( this_node, pj_qos_type, qosType); readQosParams ( this_node, qosParams); NODE_READ_BOOL ( this_node, qosIgnoreError); + NODE_READ_STRING ( this_node, certSubject); } void TlsConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error) @@ -278,6 +281,7 @@ void TlsConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error) NODE_WRITE_NUM_T ( this_node, pj_qos_type, qosType); writeQosParams ( this_node, qosParams); NODE_WRITE_BOOL ( this_node, qosIgnoreError); + NODE_WRITE_STRING ( this_node, certSubject); } /////////////////////////////////////////////////////////////////////////////// From 3245fed5296cdb5ba646615e4fcad618a2da2086 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 19 Feb 2024 17:25:50 +0700 Subject: [PATCH 05/20] Enable TLS protocol version setting, handling error code mapping & printing --- pjlib/src/pj/ssl_sock_schannel.c | 279 ++++++++++++++++++++++--------- 1 file changed, 199 insertions(+), 80 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 318f891e84..fcd3483ca6 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -135,48 +135,44 @@ typedef struct sch_ssl_sock_t /* === Helper functions === */ -static void sch_deinit(void) +#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \ + PJ_ERRNO_SPACE_SIZE*6) + +static pj_status_t sec_err_to_pj(SECURITY_STATUS ss) { - if (sch_ssl.self_signed_cert) - CertFreeCertificateContext(sch_ssl.self_signed_cert); + DWORD err = ss & 0xFFFF; - if (sch_ssl.pool) { - pj_pool_secure_release(&sch_ssl.pool); - pj_caching_pool_destroy(&sch_ssl.cp); - } + /* Make sure it does not exceed PJ_ERRNO_SPACE_SIZE */ + if (err >= PJ_ERRNO_SPACE_SIZE) + return PJ_EUNKNOWN; - pj_bzero(&sch_ssl, sizeof(sch_ssl)); + return PJ_SSL_ERRNO_START + err; } -static void sch_inc() +static SECURITY_STATUS sec_err_from_pj(pj_status_t status) { - if (++sch_ssl.init_cnt == 1 && sch_ssl.pool == NULL) { - pj_caching_pool_init(&sch_ssl.cp, NULL, 0); - sch_ssl.pool = pj_pool_create(&sch_ssl.cp.factory, "sch%p", - 512, 512, NULL); - if (pj_atexit(&sch_deinit) != PJ_SUCCESS) { - PJ_LOG(1,(SENDER, "Failed to register atexit() for Schannel.")); - } + /* Make sure it is within SSL error space */ + if (status < PJ_SSL_ERRNO_START || + status >= PJ_SSL_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + { + return ERROR_INVALID_DATA; } -} -static void sch_dec() -{ - pj_assert(sch_ssl.init_cnt > 0); - --sch_ssl.init_cnt; + return 0x80090000 + (status - PJ_SSL_ERRNO_START); } /* Print Schannel error to log */ -void log_sec_err(int log_level, const char* title, SECURITY_STATUS ss) +static void log_sec_err(int log_level, const char* title, SECURITY_STATUS ss) { - char *str; + char *str = NULL; DWORD len; len = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, ss, 0, (LPTSTR)&str, 0, NULL); + NULL, ss, 0, (LPSTR)&str, 0, NULL); /* Trim new line chars */ - while (str[len-1] == '\r' || str[len-1] == '\n') str[--len] = 0; + while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n')) + str[--len] = 0; switch (log_level) { case 1: @@ -202,6 +198,59 @@ void log_sec_err(int log_level, const char* title, SECURITY_STATUS ss) LocalFree(str); } +static pj_str_t sch_err_print(pj_status_t e, char *msg, pj_size_t max) +{ + DWORD len; + SECURITY_STATUS ss = sec_err_from_pj(e); + pj_str_t pjstr = {0}; + + len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ss, 0, (LPSTR)msg, (DWORD)max, NULL); + + /* Trim new line chars */ + while (len > 0 && (msg[len-1] == '\r' || msg[len-1] == '\n')) + msg[--len] = 0; + + pj_strset(&pjstr, msg, len); + + return pjstr; +} + +static void sch_deinit(void) +{ + if (sch_ssl.self_signed_cert) + CertFreeCertificateContext(sch_ssl.self_signed_cert); + + if (sch_ssl.pool) { + pj_pool_secure_release(&sch_ssl.pool); + pj_caching_pool_destroy(&sch_ssl.cp); + } + + pj_bzero(&sch_ssl, sizeof(sch_ssl)); +} + +static void sch_inc() +{ + if (++sch_ssl.init_cnt == 1 && sch_ssl.pool == NULL) { + pj_caching_pool_init(&sch_ssl.cp, NULL, 0); + sch_ssl.pool = pj_pool_create(&sch_ssl.cp.factory, "sch%p", + 512, 512, NULL); + if (pj_atexit(&sch_deinit) != PJ_SUCCESS) { + PJ_LOG(1,(SENDER, "Failed to register atexit() for Schannel.")); + } + + pj_register_strerror(PJ_SSL_ERRNO_START, PJ_ERRNO_SPACE_SIZE, + &sch_err_print); + } +} + +static void sch_dec() +{ + pj_assert(sch_ssl.init_cnt > 0); + --sch_ssl.init_cnt; +} + /* === SSL socket implementations === */ @@ -408,10 +457,10 @@ static void ssl_ciphers_populate() tmp_buf, sizeof(tmp_buf)); pj_strdup2_with_null(sch_ssl.pool, &tmp_st, tmp_buf); - /* Unfortunately we cannot get the ID, set ID to 0 for now, - * may be updated later. + /* Unfortunately we do not get the ID here. + * Let's just set ID to (0x8000000 + i) for now. */ - ssl_ciphers[ssl_cipher_num].id = 0; + ssl_ciphers[ssl_cipher_num].id = 0x8000000 + i; ssl_ciphers[ssl_cipher_num].name = tmp_st.ptr; ++ssl_cipher_num; } @@ -445,15 +494,17 @@ static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) pj_unicode_to_ansi(ci.szCipherSuite, SZ_ALG_MAX_SIZE, tmp_buf, sizeof(tmp_buf)); - /* If cipher is in the list but ID is 0, update it - * (we init'd cipher list without ID) + /* If cipher is actually in the list and: + * - if ID is 0, update it, or + * - if ID does not match, return ID from our list. */ for (i = 0; i < ssl_cipher_num; ++i) { if (!pj_ansi_stricmp(ssl_ciphers[i].name, tmp_buf)) { - if (ssl_ciphers[i].id == 0) { + if (ssl_ciphers[i].id == 0) ssl_ciphers[i].id = c; - break; - } + else + c = ssl_ciphers[i].id; + break; } } @@ -673,6 +724,7 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) log_sec_err(1, "Failed to retrieve remote certificate", ss); } else { cert_parse_info(ssock->pool, &ssock->remote_cert_info, cert_ctx); + CertFreeCertificateContext(cert_ctx); } ss = QueryContextAttributes(&sch_ssock->ctx_handle, @@ -685,6 +737,7 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) } else { cert_parse_info(ssock->pool, &ssock->local_cert_info, cert_ctx); + CertFreeCertificateContext(cert_ctx); } } @@ -725,6 +778,7 @@ static PCCERT_CONTEXT create_self_signed_cert() &cert_ext); } + /* May return NULL on any failure */ return CertDuplicateCertificateContext(sch_ssl.self_signed_cert); } @@ -767,6 +821,105 @@ static PCCERT_CONTEXT find_cert_in_stores(const pj_str_t *subject) } +static pj_status_t init_creds(pj_ssl_sock_t* ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + SCH_CREDENTIALS creds = { 0 }; + unsigned param_cnt = 0; + TLS_PARAMETERS params[1] = {{0}}; + SECURITY_STATUS ss; + + creds.dwVersion = SCH_CREDENTIALS_VERSION; + creds.dwFlags = SCH_USE_STRONG_CRYPTO; + + /* Setup Protocol version */ + if (ssock->param.proto != PJ_SSL_SOCK_PROTO_DEFAULT && + ssock->param.proto != PJ_SSL_SOCK_PROTO_ALL) + { + DWORD tmp = 0; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) { + tmp |= ssock->is_server ? SP_PROT_TLS1_3_SERVER : + SP_PROT_TLS1_3_CLIENT; + } + if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) == 0) { + tmp |= ssock->is_server ? SP_PROT_TLS1_2_SERVER : + SP_PROT_TLS1_2_CLIENT; + } + if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) == 0) { + tmp |= ssock->is_server ? SP_PROT_TLS1_1_SERVER : + SP_PROT_TLS1_1_CLIENT; + } + if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) == 0) { + tmp |= ssock->is_server ? SP_PROT_TLS1_0_SERVER : + SP_PROT_TLS1_0_CLIENT; + } + if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) == 0) { + tmp |= ssock->is_server ? SP_PROT_SSL3_SERVER : + SP_PROT_SSL3_CLIENT; + } + if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL2) == 0) { + tmp |= ssock->is_server ? SP_PROT_SSL2_SERVER : + SP_PROT_SSL2_CLIENT; + } + if (tmp) { + param_cnt = 1; + params[0].grbitDisabledProtocols = SP_PROT_ALL & ~tmp; + } + } + + /* Setup the TLS certificate */ + if (ssock->param.cert_subject.slen && !sch_ssock->cert_ctx) { + /* Search certificate from stores */ + sch_ssock->cert_ctx = + find_cert_in_stores(&ssock->param.cert_subject); + } + + if (ssock->is_server) { + if (!ssock->param.cert_subject.slen) { + /* No certificate subject specified, use self-signed cert */ + sch_ssock->cert_ctx = create_self_signed_cert(); + + // -- test code -- + //pj_str_t test = {"test.pjsip.org", 14}; + //sch_ssock->cert_ctx = + //find_cert_in_stores(&test); + } + } else { + creds.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; + } + + /* Verification */ + if (!ssock->is_server) { + if (ssock->param.verify_peer) + creds.dwFlags |= SCH_CRED_AUTO_CRED_VALIDATION; + else + creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; + } + + if (sch_ssock->cert_ctx) { + creds.cCreds = 1; + creds.paCred = &sch_ssock->cert_ctx; + } + + /* Now init the credentials */ + if (param_cnt) { + creds.cTlsParameters = param_cnt; + creds.pTlsParameters = params; + } + + ss = AcquireCredentialsHandle(NULL, UNISP_NAME, + ssock->is_server? SECPKG_CRED_INBOUND : + SECPKG_CRED_OUTBOUND, + NULL, &creds, NULL, NULL, + &sch_ssock->cred_handle, NULL); + if (ss < 0) { + log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); + return sec_err_to_pj(ss); + } + + return PJ_SUCCESS; +} + static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) { sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; @@ -780,46 +933,9 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Create credential handle, if not yet */ if (!SecIsValidHandle(&sch_ssock->cred_handle)) { - SCH_CREDENTIALS creds = { 0 }; - - creds.dwVersion = SCH_CREDENTIALS_VERSION; - creds.dwFlags = SCH_USE_STRONG_CRYPTO; - - if (ssock->param.cert_subject.slen && !sch_ssock->cert_ctx) { - /* Search certificate from stores */ - sch_ssock->cert_ctx = - find_cert_in_stores(&ssock->param.cert_subject); - } - - if (ssock->is_server) { - if (!ssock->param.cert_subject.slen) { - /* No certificate subject to search, use self-signed cert */ - sch_ssock->cert_ctx = create_self_signed_cert(); - - // -- test code -- - //pj_str_t test = {"test.pjsip.org", 14}; - //sch_ssock->cert_ctx = - //find_cert_in_stores(&test); - } - } else { - creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION | - // SCH_CRED_AUTO_CRED_VALIDATION | - SCH_CRED_NO_DEFAULT_CREDS; - } - - /* Use the certificate, if specified */ - if (sch_ssock->cert_ctx) { - creds.cCreds = 1; - creds.paCred = &sch_ssock->cert_ctx; - } - - ss = AcquireCredentialsHandle(NULL, UNISP_NAME, - ssock->is_server? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, - NULL, &creds, NULL, NULL, - &sch_ssock->cred_handle, NULL); - if (ss < 0) { - log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); - status = PJ_EUNKNOWN; + status2 = init_creds(ssock); + if (status2 != PJ_SUCCESS) { + status = status2; goto on_return; } } @@ -884,6 +1000,9 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) ASC_REQ_SEQUENCE_DETECT | ASC_REQ_STREAM; + if (ssock->param.require_client_cert) + flags |= ASC_REQ_MUTUAL_AUTH; + ss = AcceptSecurityContext( &sch_ssock->cred_handle, SecIsValidHandle(&sch_ssock->ctx_handle) ? @@ -913,7 +1032,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) if (ss != SEC_E_OK) { log_sec_err(1, "Failed to query stream sizes", ss); ssl_reset_sock_state(ssock); - status = PJ_EUNKNOWN; + status = sec_err_to_pj(ss); } /* Adjust maximum message size to our allocated buffer size */ @@ -938,7 +1057,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) ss = CompleteAuthToken(&sch_ssock->ctx_handle, &buf_desc_out); if (ss != SEC_E_OK) { log_sec_err(1, "Handshake error in CompleteAuthToken()", ss); - status = PJ_EUNKNOWN; + status = sec_err_to_pj(ss); } } @@ -958,7 +1077,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) else { /* Handshake failed */ log_sec_err(1, "Handshake failed!", ss); - status = PJ_EUNKNOWN; + status = sec_err_to_pj(ss); } pj_lock_release(ssock->circ_buf_input_mutex); @@ -1083,14 +1202,14 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) len -= need; p += need; - /* Store any excess to the decrypted buffer */ + /* Store any excess in the decrypted buffer */ if (len) circ_write(&sch_ssock->decrypted_buf, p, len); LOG_DEBUG2("Read %d: after decrypt, excess=%d", requested, len); } else { - /* Not enough, give everyting */ + /* Not enough, just give everything */ pj_memcpy((pj_uint8_t*)data + *size, p, len); *size += (int)len; LOG_DEBUG2("Read %d: after decrypt, only got %d", @@ -1130,7 +1249,7 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) else { log_sec_err(1, "Decrypt error", ss); - status = PJ_EUNKNOWN; + status = sec_err_to_pj(ss); } pj_lock_release(ssock->circ_buf_input_mutex); @@ -1186,7 +1305,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, if (ss != SEC_E_OK) { log_sec_err(1, "Encrypt error", ss); - status = (ss==SEC_E_CONTEXT_EXPIRED)? PJ_EEOF : PJ_EUNKNOWN; + status = (ss==SEC_E_CONTEXT_EXPIRED)? PJ_EEOF : sec_err_to_pj(ss); break; } From 0095be770652b5ab79a8bf67a42691a1bda71bae Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Tue, 20 Feb 2024 18:23:36 +0700 Subject: [PATCH 06/20] Add manual verification result, fix bug in setting up TLS protocol version --- pjlib/include/pj/ssl_sock.h | 5 + pjlib/src/pj/ssl_sock_common.c | 4 + pjlib/src/pj/ssl_sock_schannel.c | 200 +++++++++++++++++++++++-------- 3 files changed, 158 insertions(+), 51 deletions(-) diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h index d284703db1..714b025ea7 100644 --- a/pjlib/include/pj/ssl_sock.h +++ b/pjlib/include/pj/ssl_sock.h @@ -117,6 +117,11 @@ typedef enum pj_ssl_cert_verify_flag_t */ PJ_SSL_CERT_ECHAIN_TOO_LONG = (1 << 8), + /** + * The certificate signature is created using a weak hashing algorithm. + */ + PJ_SSL_CERT_EWEAK_SIGNATURE = (1 << 9), + /** * The server identity does not match to any identities specified in * the certificate, e.g: subjectAltName extension, subject common name. diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c index d741895ea0..65aba8ddab 100644 --- a/pjlib/src/pj/ssl_sock_common.c +++ b/pjlib/src/pj/ssl_sock_common.c @@ -173,6 +173,10 @@ PJ_DEF(pj_status_t) pj_ssl_cert_get_verify_status_strings( case PJ_SSL_CERT_ECHAIN_TOO_LONG: p = "The certificate chain length is too long"; break; + case PJ_SSL_CERT_EWEAK_SIGNATURE: + p = "The certificate signature is created using a weak hashing " + "algorithm"; + break; case PJ_SSL_CERT_EIDENTITY_NOT_MATCH: p = "The server identity does not match to any identities " "specified in the certificate"; diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index fcd3483ca6..77046782ce 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -140,7 +140,7 @@ typedef struct sch_ssl_sock_t static pj_status_t sec_err_to_pj(SECURITY_STATUS ss) { - DWORD err = ss & 0xFFFF; + DWORD err = ((ss & 0x7FFF) << 1) | ((ss & 0x80000000) >> 31); /* Make sure it does not exceed PJ_ERRNO_SPACE_SIZE */ if (err >= PJ_ERRNO_SPACE_SIZE) @@ -151,6 +151,8 @@ static pj_status_t sec_err_to_pj(SECURITY_STATUS ss) static SECURITY_STATUS sec_err_from_pj(pj_status_t status) { + SECURITY_STATUS ss; + /* Make sure it is within SSL error space */ if (status < PJ_SSL_ERRNO_START || status >= PJ_SSL_ERRNO_START + PJ_ERRNO_SPACE_SIZE) @@ -158,7 +160,9 @@ static SECURITY_STATUS sec_err_from_pj(pj_status_t status) return ERROR_INVALID_DATA; } - return 0x80090000 + (status - PJ_SSL_ERRNO_START); + ss = status - PJ_SSL_ERRNO_START; + ss = (ss >> 1) | ((ss & 1) << 31) | 0x00090000; + return ss; } /* Print Schannel error to log */ @@ -837,30 +841,18 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) ssock->param.proto != PJ_SSL_SOCK_PROTO_ALL) { DWORD tmp = 0; - if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) { - tmp |= ssock->is_server ? SP_PROT_TLS1_3_SERVER : - SP_PROT_TLS1_3_CLIENT; - } - if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) == 0) { - tmp |= ssock->is_server ? SP_PROT_TLS1_2_SERVER : - SP_PROT_TLS1_2_CLIENT; - } - if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) == 0) { - tmp |= ssock->is_server ? SP_PROT_TLS1_1_SERVER : - SP_PROT_TLS1_1_CLIENT; - } - if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) == 0) { - tmp |= ssock->is_server ? SP_PROT_TLS1_0_SERVER : - SP_PROT_TLS1_0_CLIENT; - } - if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) == 0) { - tmp |= ssock->is_server ? SP_PROT_SSL3_SERVER : - SP_PROT_SSL3_CLIENT; - } - if ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL2) == 0) { - tmp |= ssock->is_server ? SP_PROT_SSL2_SERVER : - SP_PROT_SSL2_CLIENT; - } + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) + tmp |= SP_PROT_TLS1_3; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) + tmp |= SP_PROT_TLS1_2; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) + tmp |= SP_PROT_TLS1_1; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) + tmp |= SP_PROT_TLS1_0; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) + tmp |= SP_PROT_SSL3; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL2) + tmp |= SP_PROT_SSL2; if (tmp) { param_cnt = 1; params[0].grbitDisabledProtocols = SP_PROT_ALL & ~tmp; @@ -878,29 +870,34 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) if (!ssock->param.cert_subject.slen) { /* No certificate subject specified, use self-signed cert */ sch_ssock->cert_ctx = create_self_signed_cert(); + PJ_LOG(2,(SENDER, "Warning: TLS server does not specify a " + "certificate, use a self-signed certificate")); // -- test code -- //pj_str_t test = {"test.pjsip.org", 14}; - //sch_ssock->cert_ctx = - //find_cert_in_stores(&test); + //sch_ssock->cert_ctx = find_cert_in_stores(&test); } } else { creds.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; } + if (sch_ssock->cert_ctx) { + creds.cCreds = 1; + creds.paCred = &sch_ssock->cert_ctx; + } + /* Verification */ if (!ssock->is_server) { if (ssock->param.verify_peer) - creds.dwFlags |= SCH_CRED_AUTO_CRED_VALIDATION; + creds.dwFlags |= SCH_CRED_AUTO_CRED_VALIDATION | + SCH_CRED_REVOCATION_CHECK_CHAIN; else creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; + } else { + if (ssock->param.verify_peer) + creds.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN; } - if (sch_ssock->cert_ctx) { - creds.cCreds = 1; - creds.paCred = &sch_ssock->cert_ctx; - } - /* Now init the credentials */ if (param_cnt) { creds.cTlsParameters = param_cnt; @@ -914,12 +911,113 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) &sch_ssock->cred_handle, NULL); if (ss < 0) { log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); + return sec_err_to_pj(ss); } return PJ_SUCCESS; } + +static void verify_remote_cert(pj_ssl_sock_t* ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + CERT_CONTEXT *cert_ctx = NULL; + CERT_CHAIN_CONTEXT *chain_ctx = NULL; + CERT_CHAIN_PARA chain_para = {0}; + DWORD info, err; + SECURITY_STATUS ss; + + ss = QueryContextAttributes(&sch_ssock->ctx_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &cert_ctx); + if (ss != SEC_E_OK || !cert_ctx) { + if (!ssock->is_server || + (ssock->is_server && ssock->param.require_client_cert)) + { + log_sec_err(1, "Error querying remote cert", ss); + } + goto on_return; + } + + chain_para.cbSize = sizeof(chain_para); + chain_para.cbSize = sizeof(chain_para); + if (!CertGetCertificateChain(HCCE_CURRENT_USER, + (PCCERT_CONTEXT)cert_ctx, + NULL, NULL, &chain_para, 0, 0, + &chain_ctx)) + { + log_sec_err(1, "Failed to get remote cert chain for verification", + GetLastError()); + goto on_return; + } + + info = chain_ctx->TrustStatus.dwInfoStatus; + err = chain_ctx->TrustStatus.dwErrorStatus; + + if (err == CERT_TRUST_NO_ERROR) { + ssock->verify_status = PJ_SUCCESS; + return; + } + + if (err & CERT_TRUST_IS_NOT_TIME_VALID) + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + if (err & CERT_TRUST_IS_REVOKED) + ssock->verify_status |= PJ_SSL_CERT_EREVOKED; + if (err & CERT_TRUST_IS_NOT_SIGNATURE_VALID) + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + if (err & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) + ssock->verify_status |= PJ_SSL_CERT_EINVALID_PURPOSE; + if (err & CERT_TRUST_IS_UNTRUSTED_ROOT) + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + if (err & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) + ssock->verify_status |= PJ_SSL_CERT_ECRL_FAILURE; + if (err & CERT_TRUST_IS_CYCLIC) + ssock->verify_status |= PJ_SSL_CERT_ECHAIN_TOO_LONG; + if (err & CERT_TRUST_INVALID_EXTENSION || + err & CERT_TRUST_INVALID_POLICY_CONSTRAINTS || + err & CERT_TRUST_INVALID_BASIC_CONSTRAINTS || + err & CERT_TRUST_INVALID_NAME_CONSTRAINTS || + err & CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT || + err & CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT || + err & CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT || + err & CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT + ) + { + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + } + if (err & CERT_TRUST_IS_OFFLINE_REVOCATION) + ssock->verify_status |= PJ_SSL_CERT_ECRL_FAILURE; + if (err & CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY) + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + if (err & CERT_TRUST_IS_EXPLICIT_DISTRUST) + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + if (err & CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT) + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + if (err & CERT_TRUST_HAS_WEAK_SIGNATURE) + ssock->verify_status |= PJ_SSL_CERT_EWEAK_SIGNATURE; + + if (err & CERT_TRUST_IS_PARTIAL_CHAIN) + ssock->verify_status |= PJ_SSL_CERT_ECHAIN_TOO_LONG; + if (err & CERT_TRUST_CTL_IS_NOT_TIME_VALID) + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + if (err & CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID) + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + if (err & CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE) + ssock->verify_status |= PJ_SSL_CERT_EINVALID_PURPOSE; + + /* Some unknown error */ + if (ssock->verify_status == PJ_SUCCESS) + ssock->verify_status = PJ_SSL_CERT_EUNKNOWN; + +on_return: + if (chain_ctx) + CertFreeCertificateChain(chain_ctx); + if (cert_ctx) + CertFreeCertificateContext(cert_ctx); +} + + static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) { sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; @@ -973,7 +1071,6 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* As client */ if (!ssock->is_server) { DWORD flags = - ISC_REQ_USE_SUPPLIED_CREDS | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | @@ -1020,19 +1117,21 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) } if (ss == SEC_E_OK) { + SECURITY_STATUS ss2; + /* Handshake completed! */ ssock->ssl_state = SSL_STATE_ESTABLISHED; status = PJ_SUCCESS; PJ_LOG(3, (SENDER, "TLS handshake completed!")); /* Get stream sizes */ - ss = QueryContextAttributes(&sch_ssock->ctx_handle, - SECPKG_ATTR_STREAM_SIZES, - &sch_ssock->strm_sizes); - if (ss != SEC_E_OK) { - log_sec_err(1, "Failed to query stream sizes", ss); + ss2 = QueryContextAttributes(&sch_ssock->ctx_handle, + SECPKG_ATTR_STREAM_SIZES, + &sch_ssock->strm_sizes); + if (ss2 != SEC_E_OK) { + log_sec_err(1, "Failed to query stream sizes", ss2); ssl_reset_sock_state(ssock); - status = sec_err_to_pj(ss); + status = sec_err_to_pj(ss2); } /* Adjust maximum message size to our allocated buffer size */ @@ -1045,6 +1144,10 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) PJ_MIN((ULONG)max_msg, sch_ssock->strm_sizes.cbMaximumMessage); } + + /* Manually verify remote cert */ + if (!ssock->param.verify_peer) + verify_remote_cert(ssock); } else if (ss == SEC_I_COMPLETE_NEEDED || @@ -1103,9 +1206,6 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) } on_return: - if (status != PJ_SUCCESS && status != PJ_EPENDING) - ssl_reset_sock_state(ssock); - pj_lock_release(ssock->write_mutex); return status; @@ -1114,6 +1214,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) { PJ_TODO(implement_this); + PJ_UNUSED_ARG(ssock); return PJ_ENOTSUP; } @@ -1155,7 +1256,7 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) LOG_DEBUG2("Read %d: %d from decrypted buffer..", requested, size_); circ_read(&sch_ssock->decrypted_buf, data, size_); *size = (int)size_; - need -= (int)size_; + need -= (int)size_; /* Decrypt data of network input buffer */ if (!circ_empty(&ssock->circ_buf_input)) { @@ -1244,7 +1345,8 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) else if (ss == SEC_I_CONTEXT_EXPIRED) { PJ_LOG(3, (SENDER, "TLS connection closed")); - ssock->ssl_state = SSL_STATE_ERROR; + //status = sec_err_to_pj(ss); + status = PJ_ECANCELLED; } else { @@ -1254,10 +1356,6 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) pj_lock_release(ssock->circ_buf_input_mutex); - /* Reset SSL if it is not renegotiating */ - if (status != PJ_SUCCESS && ssock->ssl_state != SSL_STATE_HANDSHAKING) - ssl_reset_sock_state(ssock); - LOG_DEBUG2("Read %d: returned=%d.", requested, *size); return status; } @@ -1305,7 +1403,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, if (ss != SEC_E_OK) { log_sec_err(1, "Encrypt error", ss); - status = (ss==SEC_E_CONTEXT_EXPIRED)? PJ_EEOF : sec_err_to_pj(ss); + status = sec_err_to_pj(ss); break; } From 68e565077c7945b1c1b6427e9dbc1f5de98bb990 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Wed, 21 Feb 2024 18:39:55 +0700 Subject: [PATCH 07/20] Minors: add comments about pj_status_t & SECURITY_STATUS mapping, cosmetics (aligning equal signs, etc) --- pjlib/src/pj/ssl_sock_schannel.c | 150 ++++++++++++++++--------------- 1 file changed, 80 insertions(+), 70 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 77046782ce..7670763a21 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -69,25 +69,15 @@ /* For using SCH_CREDENTIALS */ #define SCHANNEL_USE_BLACKLISTS -//#define UNICODE_STRING -//#define PUNICODE_STRING -//#include // error: many redefinitions -//#include #include - #include #include - #include // for enumerating ciphers -//#include // for enumerating ciphers -//#include // for enumerating ciphers - #pragma comment (lib, "secur32.lib") #pragma comment (lib, "shlwapi.lib") #pragma comment (lib, "Crypt32.lib") // for creating & manipulating certs #pragma comment (lib, "Bcrypt.lib") // for enumerating ciphers -//#pragma comment (lib, "Ncrypt.lib") // for enumerating ciphers /* SSL sock implementation API */ @@ -138,6 +128,25 @@ typedef struct sch_ssl_sock_t #define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \ PJ_ERRNO_SPACE_SIZE*6) +/* Map SECURITY_STATUS to pj_status_t. + * + * SECURITY_STATUS/Windows-error 32 bit structure (from winerror.h): + * + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +---+-+-+-----------------------+-------------------------------+ + * |Sev|C|R| Facility | Code | + * +---+-+-+-----------------------+-------------------------------+ + * + * For this mapping, we only save one severity bit & 15 bit error code. + * The facility value for security/SSPI is 9, which needs to be inserted + * when converting back from pj_status_t. + * + * So in pj_status_t it is stored as: + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +---+-+-+-----------------------+-------------------------------+ + * | PJLIB internal | Code |S| + * +---+-+-+-----------------------+-------------------------------+ + */ static pj_status_t sec_err_to_pj(SECURITY_STATUS ss) { DWORD err = ((ss & 0x7FFF) << 1) | ((ss & 0x80000000) >> 31); @@ -149,6 +158,7 @@ static pj_status_t sec_err_to_pj(SECURITY_STATUS ss) return PJ_SSL_ERRNO_START + err; } +/* Get SECURITY_STATUS from pj_status_t. */ static SECURITY_STATUS sec_err_from_pj(pj_status_t status) { SECURITY_STATUS ss; @@ -362,24 +372,24 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) { DWORD type = SCHANNEL_SHUTDOWN; - SecBuffer buf_in[1] = { {0} }; - buf_in[0].BufferType = SECBUFFER_TOKEN; - buf_in[0].pvBuffer = &type; - buf_in[0].cbBuffer = sizeof(type); + SecBuffer buf_in[1] = { {0} }; + buf_in[0].BufferType = SECBUFFER_TOKEN; + buf_in[0].pvBuffer = &type; + buf_in[0].cbBuffer = sizeof(type); SecBufferDesc buf_desc_in = { 0 }; - buf_desc_in.ulVersion = SECBUFFER_VERSION; - buf_desc_in.cBuffers = ARRAYSIZE(buf_in); - buf_desc_in.pBuffers = buf_in; + buf_desc_in.ulVersion = SECBUFFER_VERSION; + buf_desc_in.cBuffers = ARRAYSIZE(buf_in); + buf_desc_in.pBuffers = buf_in; ApplyControlToken(&sch_ssock->ctx_handle, &buf_desc_in); - SecBuffer buf_out[1] = { {0} }; - buf_out[0].BufferType = SECBUFFER_TOKEN; - buf_out[0].pvBuffer = sch_ssock->write_buf; - buf_out[0].cbBuffer = (ULONG)sch_ssock->write_buf_cap; + SecBuffer buf_out[1] = { {0} }; + buf_out[0].BufferType = SECBUFFER_TOKEN; + buf_out[0].pvBuffer = sch_ssock->write_buf; + buf_out[0].cbBuffer = (ULONG)sch_ssock->write_buf_cap; SecBufferDesc buf_desc_out = { 0 }; - buf_desc_out.ulVersion = SECBUFFER_VERSION; - buf_desc_out.cBuffers = ARRAYSIZE(buf_out); - buf_desc_out.pBuffers = buf_out; + buf_desc_out.ulVersion = SECBUFFER_VERSION; + buf_desc_out.cBuffers = ARRAYSIZE(buf_out); + buf_desc_out.pBuffers = buf_out; DWORD flags = ISC_REQ_CONFIDENTIALITY | @@ -556,13 +566,13 @@ static pj_status_t file_time_to_time_val(const FILETIME* file_time, pj_bzero(&pt, sizeof(pt)); pt.year = localTime.wYear; - pt.mon = localTime.wMonth - 1; - pt.day = localTime.wDay; + pt.mon = localTime.wMonth - 1; + pt.day = localTime.wDay; pt.wday = localTime.wDayOfWeek; pt.hour = localTime.wHour; - pt.min = localTime.wMinute; - pt.sec = localTime.wSecond; + pt.min = localTime.wMinute; + pt.sec = localTime.wSecond; pt.msec = localTime.wMilliseconds; return pj_time_encode(&pt, time_val); @@ -981,8 +991,7 @@ static void verify_remote_cert(pj_ssl_sock_t* ssock) err & CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT || err & CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT || err & CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT || - err & CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT - ) + err & CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT) { ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; } @@ -1049,28 +1058,29 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) circ_read(&ssock->circ_buf_input, data_in, data_in_size); } - SecBuffer buf_in[2] = { {0} }; - buf_in[0].BufferType = SECBUFFER_TOKEN; - buf_in[0].pvBuffer = data_in; - buf_in[0].cbBuffer = (ULONG)data_in_size; - buf_in[1].BufferType = SECBUFFER_EMPTY; + SecBuffer buf_in[2] = { {0} }; + buf_in[0].BufferType = SECBUFFER_TOKEN; + buf_in[0].pvBuffer = data_in; + buf_in[0].cbBuffer = (ULONG)data_in_size; + buf_in[1].BufferType = SECBUFFER_EMPTY; SecBufferDesc buf_desc_in = { 0 }; - buf_desc_in.ulVersion = SECBUFFER_VERSION; - buf_desc_in.cBuffers = ARRAYSIZE(buf_in); - buf_desc_in.pBuffers = buf_in; - - SecBuffer buf_out[1] = { {0} }; - buf_out[0].BufferType = SECBUFFER_TOKEN; - buf_out[0].pvBuffer = sch_ssock->write_buf; - buf_out[0].cbBuffer = (ULONG)sch_ssock->write_buf_cap; + buf_desc_in.ulVersion = SECBUFFER_VERSION; + buf_desc_in.cBuffers = ARRAYSIZE(buf_in); + buf_desc_in.pBuffers = buf_in; + + SecBuffer buf_out[1] = { {0} }; + buf_out[0].BufferType = SECBUFFER_TOKEN; + buf_out[0].pvBuffer = sch_ssock->write_buf; + buf_out[0].cbBuffer = (ULONG)sch_ssock->write_buf_cap; SecBufferDesc buf_desc_out = { 0 }; - buf_desc_out.ulVersion = SECBUFFER_VERSION; - buf_desc_out.cBuffers = ARRAYSIZE(buf_out); - buf_desc_out.pBuffers = buf_out; + buf_desc_out.ulVersion = SECBUFFER_VERSION; + buf_desc_out.cBuffers = ARRAYSIZE(buf_out); + buf_desc_out.pBuffers = buf_out; /* As client */ if (!ssock->is_server) { DWORD flags = + ISC_REQ_USE_SUPPLIED_CREDS | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | @@ -1271,17 +1281,17 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) return PJ_SUCCESS; } - SecBuffer buf[4] = { {0} }; - buf[0].BufferType = SECBUFFER_DATA; - buf[0].pvBuffer = data_; - buf[0].cbBuffer = (ULONG)size_; - buf[1].BufferType = SECBUFFER_EMPTY; - buf[2].BufferType = SECBUFFER_EMPTY; - buf[3].BufferType = SECBUFFER_EMPTY; + SecBuffer buf[4] = { {0} }; + buf[0].BufferType = SECBUFFER_DATA; + buf[0].pvBuffer = data_; + buf[0].cbBuffer = (ULONG)size_; + buf[1].BufferType = SECBUFFER_EMPTY; + buf[2].BufferType = SECBUFFER_EMPTY; + buf[3].BufferType = SECBUFFER_EMPTY; SecBufferDesc buf_desc = { 0 }; - buf_desc.ulVersion = SECBUFFER_VERSION; - buf_desc.cBuffers = ARRAYSIZE(buf); - buf_desc.pBuffers = buf; + buf_desc.ulVersion = SECBUFFER_VERSION; + buf_desc.cBuffers = ARRAYSIZE(buf); + buf_desc.pBuffers = buf; ss = DecryptMessage(&sch_ssock->ctx_handle, &buf_desc, 0, NULL); @@ -1382,22 +1392,22 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, pj_memcpy(p_data, (pj_uint8_t*)data + total, write_len); - SecBuffer buf[4] = { {0} }; - buf[0].BufferType = SECBUFFER_STREAM_HEADER; - buf[0].pvBuffer = p_header; - buf[0].cbBuffer = sch_ssock->strm_sizes.cbHeader; - buf[1].BufferType = SECBUFFER_DATA; - buf[1].pvBuffer = p_data; - buf[1].cbBuffer = (ULONG)write_len; - buf[2].BufferType = SECBUFFER_STREAM_TRAILER; - buf[2].pvBuffer = p_trailer; - buf[2].cbBuffer = sch_ssock->strm_sizes.cbTrailer; - buf[3].BufferType = SECBUFFER_EMPTY; + SecBuffer buf[4] = { {0} }; + buf[0].BufferType = SECBUFFER_STREAM_HEADER; + buf[0].pvBuffer = p_header; + buf[0].cbBuffer = sch_ssock->strm_sizes.cbHeader; + buf[1].BufferType = SECBUFFER_DATA; + buf[1].pvBuffer = p_data; + buf[1].cbBuffer = (ULONG)write_len; + buf[2].BufferType = SECBUFFER_STREAM_TRAILER; + buf[2].pvBuffer = p_trailer; + buf[2].cbBuffer = sch_ssock->strm_sizes.cbTrailer; + buf[3].BufferType = SECBUFFER_EMPTY; SecBufferDesc buf_desc = { 0 }; - buf_desc.ulVersion = SECBUFFER_VERSION; - buf_desc.cBuffers = ARRAYSIZE(buf); - buf_desc.pBuffers = buf; + buf_desc.ulVersion = SECBUFFER_VERSION; + buf_desc.cBuffers = ARRAYSIZE(buf); + buf_desc.pBuffers = buf; ss = EncryptMessage(&sch_ssock->ctx_handle, 0, &buf_desc, 0); From 76fe133ac63c2ae4d3f5507c4b4173638bd80718 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 23 Feb 2024 09:48:58 +0700 Subject: [PATCH 08/20] Update certificate settings, fix setting TLS protocol version --- pjlib/include/pj/ssl_sock.h | 83 +++++++++++-- pjlib/src/pj/ssl_sock_imp_common.c | 62 +++++++++- pjlib/src/pj/ssl_sock_imp_common.h | 4 + pjlib/src/pj/ssl_sock_schannel.c | 156 +++++++++++++++++------- pjnath/include/pjnath/turn_sock.h | 20 +++ pjnath/src/pjnath/turn_sock.c | 10 ++ pjsip-apps/src/swig/symbols.i | 8 ++ pjsip-apps/src/swig/symbols.lst | 2 +- pjsip/include/pjsip/sip_transport_tls.h | 27 ++-- pjsip/include/pjsua2/siptypes.hpp | 23 ++-- pjsip/src/pjsip/sip_transport_tls.c | 11 +- pjsip/src/pjsua2/siptypes.cpp | 12 +- 12 files changed, 341 insertions(+), 77 deletions(-) diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h index 714b025ea7..bc7d5f0326 100644 --- a/pjlib/include/pj/ssl_sock.h +++ b/pjlib/include/pj/ssl_sock.h @@ -150,6 +150,59 @@ typedef enum pj_ssl_cert_name_type PJ_SSL_CERT_NAME_IP } pj_ssl_cert_name_type; +/** + * Field type for looking up SSL certificate in the certificate stores. + */ +typedef enum pj_ssl_cert_lookup_type +{ + /** + * No certificate to be looked up. + */ + PJ_SSL_CERT_LOOKUP_NONE, + + /** + * Lookup by subject, this will lookup any first certificate whose + * subject containing the specified keyword. Note that subject may not + * be unique in the store, the lookup may end up selecting a wrong + * certificate. + */ + PJ_SSL_CERT_LOOKUP_SUBJECT, + + /** + * Lookup by fingerprint/thumbprint (SHA1 hash), this will lookup + * any first certificate whose fingerprint matching the specified + * keyword. The keyword is an array of hash octets. + */ + PJ_SSL_CERT_LOOKUP_FINGERPRINT, + + /** + * Lookup by friendly name, this will lookup any first certificate + * whose friendly name containing the specified keyword. Note that + * friendly name may not be unique in the store, the lookup may end up + * selecting a wrong certificate. + */ + PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME + +} pj_ssl_cert_lookup_type; + +/** + * Describe structure of certificate lookup criteria. + */ +typedef struct pj_ssl_cert_lookup_criteria +{ + /** + * Certificate field type to look. + */ + pj_ssl_cert_lookup_type type; + + /* + * Keyword to match. + */ + pj_str_t keyword; + +} pj_ssl_cert_lookup_criteria; + + /** * Describe structure of certificate info. */ @@ -278,6 +331,25 @@ PJ_DECL(pj_status_t) pj_ssl_cert_load_from_buffer(pj_pool_t *pool, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert); +/** + * Create credential from OS certificate store, this function will lookup + * certificate using the specified criterias. + * + * Currently this is used by Windows Schannel backend only, it will lookup + * in the Current User store first, if not found it will lookup in the + * Local Machine store. + * + * @param pool The pool. + * @param criteria The lookup criteria. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_cert_load_from_store( + pj_pool_t *pool, + const pj_ssl_cert_lookup_criteria *criteria, + pj_ssl_cert_t **p_cert); + /** * Dump SSL certificate info. * @@ -1046,17 +1118,6 @@ typedef struct pj_ssl_sock_param */ pj_str_t server_name; - /** - * For Windows SSPI Schannel backend. This specifies the subject keyword - * used for searching certificate in OS certificate stores. The search - * will be performed in local machine and user account stores. - * - * The certificate will be used as client-side certificate for outgoing - * TLS connection, and server-side certificate for incoming TLS - * connection. - */ - pj_str_t cert_subject; - /** * Specify if SO_REUSEADDR should be used for listening socket. This * option will only be used with accept() operation. diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c index a272376217..daec711a32 100644 --- a/pjlib/src/pj/ssl_sock_imp_common.c +++ b/pjlib/src/pj/ssl_sock_imp_common.c @@ -2275,8 +2275,9 @@ static void wipe_buf(pj_str_t *buf) } PJ_DEF(void) pj_ssl_cert_wipe_keys(pj_ssl_cert_t *cert) -{ +{ if (cert) { +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) wipe_buf(&cert->CA_file); wipe_buf(&cert->CA_path); wipe_buf(&cert->cert_file); @@ -2285,6 +2286,10 @@ PJ_DEF(void) pj_ssl_cert_wipe_keys(pj_ssl_cert_t *cert) wipe_buf(&cert->CA_buf); wipe_buf(&cert->cert_buf); wipe_buf(&cert->privkey_buf); +#else + cert->criteria.type = PJ_SSL_CERT_LOOKUP_NONE; + wipe_buf(&cert->criteria.keyword); +#endif } } @@ -2308,6 +2313,7 @@ PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files2(pj_pool_t *pool, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert) { +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) pj_ssl_cert_t *cert; PJ_ASSERT_RETURN(pool && (CA_file || CA_path) && cert_file && @@ -2328,6 +2334,16 @@ PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files2(pj_pool_t *pool, *p_cert = cert; return PJ_SUCCESS; +#else + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(CA_file); + PJ_UNUSED_ARG(CA_path); + PJ_UNUSED_ARG(cert_file); + PJ_UNUSED_ARG(privkey_file); + PJ_UNUSED_ARG(privkey_pass); + PJ_UNUSED_ARG(p_cert); + return PJ_ENOTSUP; +#endif } PJ_DEF(pj_status_t) pj_ssl_cert_load_from_buffer(pj_pool_t *pool, @@ -2337,6 +2353,7 @@ PJ_DEF(pj_status_t) pj_ssl_cert_load_from_buffer(pj_pool_t *pool, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert) { +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) pj_ssl_cert_t *cert; PJ_ASSERT_RETURN(pool && CA_buf && cert_buf && privkey_buf, PJ_EINVAL); @@ -2350,8 +2367,45 @@ PJ_DEF(pj_status_t) pj_ssl_cert_load_from_buffer(pj_pool_t *pool, *p_cert = cert; return PJ_SUCCESS; +#else + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(CA_buf); + PJ_UNUSED_ARG(cert_buf); + PJ_UNUSED_ARG(privkey_buf); + PJ_UNUSED_ARG(privkey_pass); + PJ_UNUSED_ARG(p_cert); + return PJ_ENOTSUP; +#endif } + +PJ_DEF(pj_status_t) pj_ssl_cert_load_from_store( + pj_pool_t *pool, + const pj_ssl_cert_lookup_criteria *criteria, + pj_ssl_cert_t **p_cert) +{ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) + pj_ssl_cert_t *cert; + + PJ_ASSERT_RETURN(pool && criteria && p_cert, PJ_EINVAL); + + cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + pj_memcpy(&cert->criteria, criteria, sizeof(*criteria)); + pj_strdup_with_null(pool, &cert->criteria.keyword, &criteria->keyword); + + *p_cert = cert; + + return PJ_SUCCESS; +#else + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(type); + PJ_UNUSED_ARG(keyword); + PJ_UNUSED_ARG(p_cert); + return PJ_ENOTSUP; +#endif +} + + /* Set SSL socket credentials. */ PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate( pj_ssl_sock_t *ssock, @@ -2364,6 +2418,8 @@ PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate( cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); pj_memcpy(cert_, cert, sizeof(pj_ssl_cert_t)); + +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file); pj_strdup_with_null(pool, &cert_->CA_path, &cert->CA_path); pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file); @@ -2373,6 +2429,10 @@ PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate( pj_strdup(pool, &cert_->CA_buf, &cert->CA_buf); pj_strdup(pool, &cert_->cert_buf, &cert->cert_buf); pj_strdup(pool, &cert_->privkey_buf, &cert->privkey_buf); +#else + pj_strdup_with_null(pool, &cert_->criteria.keyword, + &cert->criteria.keyword); +#endif ssock->cert = cert_; diff --git a/pjlib/src/pj/ssl_sock_imp_common.h b/pjlib/src/pj/ssl_sock_imp_common.h index e7b8ec61fb..e50dc16dc9 100644 --- a/pjlib/src/pj/ssl_sock_imp_common.h +++ b/pjlib/src/pj/ssl_sock_imp_common.h @@ -152,6 +152,7 @@ struct pj_ssl_sock_t */ struct pj_ssl_cert_t { +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) pj_str_t CA_file; pj_str_t CA_path; pj_str_t cert_file; @@ -162,6 +163,9 @@ struct pj_ssl_cert_t pj_ssl_cert_buffer CA_buf; pj_ssl_cert_buffer cert_buf; pj_ssl_cert_buffer privkey_buf; +#else + pj_ssl_cert_lookup_criteria criteria; +#endif }; /* ssl available ciphers */ diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 7670763a21..fa2c6b7928 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -49,17 +49,19 @@ #define SENDER "ssl_schannel" /* Debugging */ -#define DEBUG_SCHANNEL 0 +#define DEBUG_SCHANNEL 1 #if DEBUG_SCHANNEL -# define LOG_DEBUG(title) PJ_LOG(4,(SENDER, title)) -# define LOG_DEBUG1(title, p1) PJ_LOG(4,(SENDER, title, p1)) -# define LOG_DEBUG2(title, p1, p2) PJ_LOG(4,(SENDER, title, p1, p2)) +# define LOG_DEBUG(title) PJ_LOG(4,(SENDER,title)) +# define LOG_DEBUG1(title,p1) PJ_LOG(4,(SENDER,title,p1)) +# define LOG_DEBUG2(title,p1,p2) PJ_LOG(4,(SENDER,title,p1,p2)) +# define LOG_DEBUG3(title,p1,p2,p3) PJ_LOG(4,(SENDER,title,p1,p2,p3)) # define LOG_DEBUG_ERR(title, sec_status) log_sec_err(4, title, sec_status) #else # define LOG_DEBUG(s) -# define LOG_DEBUG1(title, p1) -# define LOG_DEBUG2(title, p1, p2) +# define LOG_DEBUG1(title,p1) +# define LOG_DEBUG2(title,p1,p2) +# define LOG_DEBUG3(title,p1,p2,p3) # define LOG_DEBUG_ERR(title, sec_status) #endif @@ -796,42 +798,91 @@ static PCCERT_CONTEXT create_self_signed_cert() return CertDuplicateCertificateContext(sch_ssl.self_signed_cert); } -static PCCERT_CONTEXT find_cert_in_stores(const pj_str_t *subject) +static PCCERT_CONTEXT find_cert_in_stores(pj_ssl_cert_lookup_type type, + const pj_str_t *keyword) { - HCERTSTORE store = NULL; PCCERT_CONTEXT cert = NULL; - /* Find in Current User store */ - store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING, 0, - CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); - if (store) { - cert = CertFindCertificateInStore(store, X509_ASN_ENCODING, - 0, CERT_FIND_SUBJECT_STR_A, subject->ptr, NULL); - CertCloseStore(store, 0); - if (cert) - return cert; - } else { - log_sec_err(1, "Error opening current user cert store.", - GetLastError()); - } + LOG_DEBUG3("Looking up certificate with criteria: type=%d keyword=%.*s", + type, (type==PJ_SSL_CERT_LOOKUP_FINGERPRINT? 4:keyword->slen), + (type==PJ_SSL_CERT_LOOKUP_FINGERPRINT?"[..]":keyword->ptr)); + + /* Find in Current User & Local Machine stores */ + DWORD flags[2] = { CERT_SYSTEM_STORE_CURRENT_USER, + CERT_SYSTEM_STORE_LOCAL_MACHINE }; + + for (int i = 0; (!cert && i < PJ_ARRAY_SIZE(flags)); ++i) { + HCERTSTORE store = NULL; + + /* Open the store */ + store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING, + 0, flags[i], L"MY"); + if (!store) { + log_sec_err(1, "Error opening store", GetLastError()); + continue; + } + + /* Lookup based on type */ + + if (type == PJ_SSL_CERT_LOOKUP_SUBJECT) { + cert = CertFindCertificateInStore( + store, X509_ASN_ENCODING, 0, + CERT_FIND_SUBJECT_STR_A, keyword->ptr, NULL); + + } else if (type == PJ_SSL_CERT_LOOKUP_FINGERPRINT) { + CRYPT_HASH_BLOB hash = {0}; + hash.cbData = (DWORD)keyword->slen; + hash.pbData = (BYTE*)keyword->ptr; + cert = CertFindCertificateInStore( + store, X509_ASN_ENCODING, + 0, CERT_FIND_SHA1_HASH, &hash, NULL); + + } else if (type == PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME) { + WCHAR buf[256]; + DWORD buf_size; + + if (keyword->slen >= sizeof(buf)) { + PJ_LOG(1,(SENDER,"Cannot lookup certificate, friendly name " + "keyword is too long (max=%d)",sizeof(buf))); + } else { + cert = NULL; + while (1) { + cert = CertEnumCertificatesInStore(store, cert); + if (!cert) + break; + + buf_size = sizeof(buf); + if (CertGetCertificateContextProperty( + cert, CERT_FRIENDLY_NAME_PROP_ID, buf, &buf_size)) + { + char buf2[256]; + pj_ssize_t buf2_len; + + /* The output buf is null-terminated */ + pj_unicode_to_ansi(buf, -1, buf2, sizeof(buf2)); + buf2_len = pj_ansi_strlen(buf2); + if (keyword->slen == buf2_len && + !pj_memcmp(buf2, keyword->ptr, buf2_len)) + { + /* Found it */ + break; + } + } + } + } + } - /* Find in Local Machine store */ - store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING, 0, - CERT_SYSTEM_STORE_LOCAL_MACHINE, L"MY"); - if (store) { - cert = CertFindCertificateInStore(store, X509_ASN_ENCODING, - 0, CERT_FIND_SUBJECT_STR_A, subject->ptr, NULL); CertCloseStore(store, 0); - if (cert) - return cert; - } else { - log_sec_err(1, "Error opening local machine cert store.", - GetLastError()); } - PJ_LOG(1,(SENDER, "Cannot find certificate with specified subject: %.*s", - subject->slen, subject->ptr)); - return NULL; + if (!cert) { + PJ_LOG(1,(SENDER, + "Cannot find certificate with criteria: " + "type=%d keyword=%.*s", type, + (type==PJ_SSL_CERT_LOOKUP_FINGERPRINT? 4:keyword->slen), + (type==PJ_SSL_CERT_LOOKUP_FINGERPRINT?"[..]":keyword->ptr))); + } + return cert; } @@ -840,7 +891,7 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; SCH_CREDENTIALS creds = { 0 }; unsigned param_cnt = 0; - TLS_PARAMETERS params[1] = {{0}}; + TLS_PARAMETERS param = {0}; SECURITY_STATUS ss; creds.dwVersion = SCH_CREDENTIALS_VERSION; @@ -865,27 +916,44 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) tmp |= SP_PROT_SSL2; if (tmp) { param_cnt = 1; - params[0].grbitDisabledProtocols = SP_PROT_ALL & ~tmp; + param.grbitDisabledProtocols = ~tmp; + LOG_DEBUG1("grbitDisabledProtocols=0x%x", (~tmp)); } } /* Setup the TLS certificate */ - if (ssock->param.cert_subject.slen && !sch_ssock->cert_ctx) { + if (ssock->cert && + ssock->cert->criteria.type != PJ_SSL_CERT_LOOKUP_NONE && + !sch_ssock->cert_ctx) + { /* Search certificate from stores */ sch_ssock->cert_ctx = - find_cert_in_stores(&ssock->param.cert_subject); + find_cert_in_stores(ssock->cert->criteria.type, + &ssock->cert->criteria.keyword); } if (ssock->is_server) { - if (!ssock->param.cert_subject.slen) { - /* No certificate subject specified, use self-signed cert */ + if (!ssock->cert || + ssock->cert->criteria.type == PJ_SSL_CERT_LOOKUP_NONE) + { + /* No certificate specified, use self-signed cert */ sch_ssock->cert_ctx = create_self_signed_cert(); PJ_LOG(2,(SENDER, "Warning: TLS server does not specify a " "certificate, use a self-signed certificate")); // -- test code -- - //pj_str_t test = {"test.pjsip.org", 14}; - //sch_ssock->cert_ctx = find_cert_in_stores(&test); + //pj_str_t keyword = {"test.pjsip.org", 14}; + //pj_ssl_cert_lookup_type type = PJ_SSL_CERT_LOOKUP_SUBJECT; + + //pj_str_t keyword = {"schannel-test", 13}; + //pj_ssl_cert_lookup_type type = PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME; + + //pj_str_t keyword = {"\x08\x3a\x6c\xdc\xd0\x19\x59\xec\x28\xc3" + // "\x81\xb8\xc0\x21\x09\xe9\xd5\xf6\x57\x7d", + // 20}; + //pj_ssl_cert_lookup_type type = PJ_SSL_CERT_LOOKUP_FINGERPRINT; + + //sch_ssock->cert_ctx = find_cert_in_stores( type, &keyword); } } else { creds.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; @@ -911,7 +979,7 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) /* Now init the credentials */ if (param_cnt) { creds.cTlsParameters = param_cnt; - creds.pTlsParameters = params; + creds.pTlsParameters = ¶m; } ss = AcquireCredentialsHandle(NULL, UNISP_NAME, diff --git a/pjnath/include/pjnath/turn_sock.h b/pjnath/include/pjnath/turn_sock.h index 46f39e4eb9..b7149eda41 100644 --- a/pjnath/include/pjnath/turn_sock.h +++ b/pjnath/include/pjnath/turn_sock.h @@ -223,6 +223,26 @@ typedef struct pj_turn_sock_tls_cfg */ pj_str_t password; + /** + * Lookup certificate from OS certificate store, this setting will + * specify the field type to lookup. + * + * Currently only TLS backend Windows Schannel support this and this + * backend only support this type of certificate settings (settings via + * files or buffers are not supported). The lookup will be performed in + * the Current User store, if not found, it will try Local Machine store. + * Note that in manual verification (e.g: when verify_server is disabled), + * the backend will provide pre-verification result against trusted + * CA certificates in Current User store. + */ + pj_ssl_cert_lookup_type cert_lookup_type; + + /** + * Lookup certificate from OS certificate store, this setting will + * specify the keyword to lookup. + */ + pj_str_t cert_lookup_keyword; + /** * The ssl socket parameter. * These fields are used by TURN TLS: diff --git a/pjnath/src/pjnath/turn_sock.c b/pjnath/src/pjnath/turn_sock.c index e5b73d4299..b1fcc27bd8 100644 --- a/pjnath/src/pjnath/turn_sock.c +++ b/pjnath/src/pjnath/turn_sock.c @@ -1389,12 +1389,22 @@ static void turn_on_state(pj_turn_session *sess, &turn_sock->setting.tls_cfg.privkey_buf, &turn_sock->setting.tls_cfg.password, &turn_sock->cert); + } else if (turn_sock->setting.tls_cfg.cert_lookup_type != + PJ_SSL_CERT_LOOKUP_NONE && + turn_sock->setting.tls_cfg.cert_lookup_keyword.slen) + { + pj_ssl_cert_lookup_criteria crit = {0}; + crit.type = turn_sock->setting.tls_cfg.cert_lookup_type; + crit.keyword = turn_sock->setting.tls_cfg.cert_lookup_keyword; + status = pj_ssl_cert_load_from_store(turn_sock->pool, &crit, + &turn_sock->cert); } if (status != PJ_SUCCESS) { turn_sock_destroy(turn_sock, status); pj_grp_lock_release(turn_sock->grp_lock); return; } + if (turn_sock->cert) { pj_turn_sock_tls_cfg_wipe_keys(&turn_sock->setting.tls_cfg); } diff --git a/pjsip-apps/src/swig/symbols.i b/pjsip-apps/src/swig/symbols.i index bf0965b0d7..5d49c4c06e 100644 --- a/pjsip-apps/src/swig/symbols.i +++ b/pjsip-apps/src/swig/symbols.i @@ -184,6 +184,14 @@ typedef enum pj_ssl_cert_verify_flag_t PJ_SSL_CERT_EUNKNOWN = 1 << 31 } pj_ssl_cert_verify_flag_t; +typedef enum pj_ssl_cert_lookup_type +{ + PJ_SSL_CERT_LOOKUP_NONE, + PJ_SSL_CERT_LOOKUP_SUBJECT, + PJ_SSL_CERT_LOOKUP_FINGERPRINT, + PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME +} pj_ssl_cert_lookup_type; + typedef enum pj_ice_sess_trickle { PJ_ICE_SESS_TRICKLE_DISABLED, diff --git a/pjsip-apps/src/swig/symbols.lst b/pjsip-apps/src/swig/symbols.lst index 6e2f4257e7..3170fc5a1b 100644 --- a/pjsip-apps/src/swig/symbols.lst +++ b/pjsip-apps/src/swig/symbols.lst @@ -2,7 +2,7 @@ pj/types.h pj_status_t pj_constants_ pj_uint8_t pj_int32_t pj_uint32_t pj_uint pj/file_io.h pj_file_access pj/log.h pj_log_decoration pj/sock_qos.h pj_qos_type pj_qos_flag pj_qos_wmm_prio pj_qos_params -pj/ssl_sock.h pj_ssl_cipher pj_ssl_sock_proto pj_ssl_cert_name_type pj_ssl_cert_verify_flag_t +pj/ssl_sock.h pj_ssl_cipher pj_ssl_sock_proto pj_ssl_cert_name_type pj_ssl_cert_verify_flag_t pj_ssl_cert_lookup_type pjnath/ice_session.h pj_ice_sess_trickle pjnath/nat_detect.h pj_stun_nat_type diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index 856dc309b1..f25fc09e20 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -189,15 +189,24 @@ typedef struct pjsip_tls_setting pj_ssl_cert_buffer privkey_buf; /** - * For Windows SSPI Schannel backend. This specifies the subject keyword - * used for searching certificate in OS certificate stores. The search - * will be performed in local machine and user account stores. + * Lookup certificate from OS certificate store, this setting will + * specify the field type to lookup. * - * The certificate will be used as client-side certificate for outgoing - * TLS connection, and server-side certificate for incoming TLS - * connection. + * Currently only TLS backend Windows Schannel support this and this + * backend only support this type of certificate settings (settings via + * files or buffers are not supported). The lookup will be performed in + * the Current User store, if not found, it will try Local Machine store. + * Note that in manual verification (e.g: when verify_server is disabled), + * the backend will provide pre-verification result against trusted + * CA certificates in Current User store. */ - pj_str_t cert_subject; + pj_ssl_cert_lookup_type cert_lookup_type; + + /** + * Lookup certificate from OS certificate store, this setting will + * specify the keyword to lookup. + */ + pj_str_t cert_lookup_keyword; /** * Password to open private key. @@ -477,7 +486,6 @@ PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, pj_strdup_with_null(pool, &dst->ca_list_path, &src->ca_list_path); pj_strdup_with_null(pool, &dst->cert_file, &src->cert_file); pj_strdup_with_null(pool, &dst->privkey_file, &src->privkey_file); - pj_strdup_with_null(pool, &dst->cert_subject, &src->cert_subject); pj_strdup_with_null(pool, &dst->password, &src->password); pj_strdup_with_null(pool, &dst->sigalgs, &src->sigalgs); pj_strdup_with_null(pool, &dst->entropy_path, &src->entropy_path); @@ -486,6 +494,9 @@ PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, pj_strdup(pool, &dst->cert_buf, &src->cert_buf); pj_strdup(pool, &dst->privkey_buf, &src->privkey_buf); + pj_strdup_with_null(pool, &dst->cert_lookup_keyword, + &src->cert_lookup_keyword); + if (src->ciphers_num) { unsigned i; dst->ciphers = (pj_ssl_cipher*) pj_pool_calloc(pool, src->ciphers_num, diff --git a/pjsip/include/pjsua2/siptypes.hpp b/pjsip/include/pjsua2/siptypes.hpp index 6d40f41fbe..4f5c09c14e 100644 --- a/pjsip/include/pjsua2/siptypes.hpp +++ b/pjsip/include/pjsua2/siptypes.hpp @@ -175,15 +175,24 @@ struct TlsConfig : public PersistentObject string privKeyBuf; /** - * For Windows SSPI Schannel backend. This specifies the subject keyword - * used for searching certificate in OS certificate stores. The search - * will be performed in local machine and user account stores. + * Lookup certificate from OS certificate store, this setting will + * specify the field type to lookup. * - * The certificate will be used as client-side certificate for outgoing - * TLS connection, and server-side certificate for incoming TLS - * connection. + * Currently only TLS backend Windows Schannel support this and this + * backend only support this type of certificate settings (settings via + * files or buffers are not supported). The lookup will be performed in + * the Current User store, if not found, it will try Local Machine store. + * Note that in manual verification (e.g: when verifyServer is disabled), + * the backend will provide pre-verification result against trusted + * CA certificates in Current User store. */ - string certSubject; + pj_ssl_cert_lookup_type certLookupType; + + /** + * Lookup certificate from OS certificate store, this setting will + * specify the keyword to lookup. + */ + string certLookupKeyword; /** * TLS protocol method from #pjsip_ssl_method. In the future, this field diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c index ade4924842..1625ef9e39 100644 --- a/pjsip/src/pjsip/sip_transport_tls.c +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -351,7 +351,6 @@ static void set_ssock_param(pj_ssl_sock_param *ssock_param, sip_ssl_method = listener->tls_setting.method; sip_ssl_proto = listener->tls_setting.proto; ssock_param->proto = ssl_get_proto(sip_ssl_method, sip_ssl_proto); - ssock_param->cert_subject = listener->tls_setting.cert_subject; } static void update_bound_addr(struct tls_listener *listener, @@ -648,6 +647,16 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start2( pjsip_endpoint *endpt, &listener->cert); if (status != PJ_SUCCESS) goto on_error; + } else if (listener->tls_setting.cert_lookup_type != + PJ_SSL_CERT_LOOKUP_NONE && + listener->tls_setting.cert_lookup_keyword.slen) + { + pj_ssl_cert_lookup_criteria crit = {0}; + crit.type = listener->tls_setting.cert_lookup_type; + crit.keyword = listener->tls_setting.cert_lookup_keyword; + status = pj_ssl_cert_load_from_store(pool, &crit, &listener->cert); + if (status != PJ_SUCCESS) + goto on_error; } /* Register to transport manager */ diff --git a/pjsip/src/pjsua2/siptypes.cpp b/pjsip/src/pjsua2/siptypes.cpp index 606a3039c4..ed652d1758 100644 --- a/pjsip/src/pjsua2/siptypes.cpp +++ b/pjsip/src/pjsua2/siptypes.cpp @@ -192,7 +192,8 @@ pjsip_tls_setting TlsConfig::toPj() const ts.ca_buf = str2Pj(this->CaBuf); ts.cert_buf = str2Pj(this->certBuf); ts.privkey_buf = str2Pj(this->privKeyBuf); - ts.cert_subject = str2Pj(this->certSubject); + ts.cert_lookup_type = this->certLookupType; + ts.cert_lookup_keyword = str2Pj(this->certLookupKeyword); ts.method = this->method; ts.ciphers_num = (unsigned)this->ciphers.size(); ts.proto = this->proto; @@ -222,7 +223,8 @@ void TlsConfig::fromPj(const pjsip_tls_setting &prm) this->CaBuf = pj2Str(prm.ca_buf); this->certBuf = pj2Str(prm.cert_buf); this->privKeyBuf = pj2Str(prm.privkey_buf); - this->certSubject = pj2Str(prm.cert_subject); + this->certLookupType= prm.cert_lookup_type; + this->certLookupKeyword = pj2Str(prm.cert_lookup_keyword); this->method = (pjsip_ssl_method)prm.method; this->proto = prm.proto; // The following will only work if sizeof(enum)==sizeof(int) @@ -258,7 +260,8 @@ void TlsConfig::readObject(const ContainerNode &node) PJSUA2_THROW(Error) NODE_READ_NUM_T ( this_node, pj_qos_type, qosType); readQosParams ( this_node, qosParams); NODE_READ_BOOL ( this_node, qosIgnoreError); - NODE_READ_STRING ( this_node, certSubject); + NODE_READ_NUM_T ( this_node, pj_ssl_cert_lookup_type, certLookupType); + NODE_READ_STRING ( this_node, certLookupKeyword); } void TlsConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error) @@ -281,7 +284,8 @@ void TlsConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error) NODE_WRITE_NUM_T ( this_node, pj_qos_type, qosType); writeQosParams ( this_node, qosParams); NODE_WRITE_BOOL ( this_node, qosIgnoreError); - NODE_WRITE_STRING ( this_node, certSubject); + NODE_WRITE_NUM_T ( this_node, pj_ssl_cert_lookup_type, certLookupType); + NODE_WRITE_STRING ( this_node, certLookupKeyword); } /////////////////////////////////////////////////////////////////////////////// From aed522b58111741585792200733dad6d93ad9b51 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 23 Feb 2024 09:58:16 +0700 Subject: [PATCH 09/20] Fix compile error on non-Windows --- pjlib/src/pj/ssl_sock_imp_common.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c index daec711a32..7f24d086b2 100644 --- a/pjlib/src/pj/ssl_sock_imp_common.c +++ b/pjlib/src/pj/ssl_sock_imp_common.c @@ -2398,8 +2398,7 @@ PJ_DEF(pj_status_t) pj_ssl_cert_load_from_store( return PJ_SUCCESS; #else PJ_UNUSED_ARG(pool); - PJ_UNUSED_ARG(type); - PJ_UNUSED_ARG(keyword); + PJ_UNUSED_ARG(criteria); PJ_UNUSED_ARG(p_cert); return PJ_ENOTSUP; #endif From 7e87d3c117f32871e0516311fde7ae5fcaeadc15 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 23 Feb 2024 12:53:28 +0700 Subject: [PATCH 10/20] Slight modifications in TLS cert settings in PJSIP TLS transport & PJNATH TURN socket. --- pjnath/include/pjnath/turn_sock.h | 11 ++--------- pjnath/src/pjnath/turn_sock.c | 13 ++++++------- pjsip/include/pjsip/sip_transport_tls.h | 15 ++++----------- pjsip/include/pjsua2/siptypes.hpp | 2 +- pjsip/src/pjsip/sip_transport_tls.c | 12 ++++++------ pjsip/src/pjsua2/siptypes.cpp | 8 ++++---- 6 files changed, 23 insertions(+), 38 deletions(-) diff --git a/pjnath/include/pjnath/turn_sock.h b/pjnath/include/pjnath/turn_sock.h index b7149eda41..5de2f39bbe 100644 --- a/pjnath/include/pjnath/turn_sock.h +++ b/pjnath/include/pjnath/turn_sock.h @@ -224,8 +224,7 @@ typedef struct pj_turn_sock_tls_cfg pj_str_t password; /** - * Lookup certificate from OS certificate store, this setting will - * specify the field type to lookup. + * Lookup certificate from OS certificate store with specified criteria. * * Currently only TLS backend Windows Schannel support this and this * backend only support this type of certificate settings (settings via @@ -235,13 +234,7 @@ typedef struct pj_turn_sock_tls_cfg * the backend will provide pre-verification result against trusted * CA certificates in Current User store. */ - pj_ssl_cert_lookup_type cert_lookup_type; - - /** - * Lookup certificate from OS certificate store, this setting will - * specify the keyword to lookup. - */ - pj_str_t cert_lookup_keyword; + pj_ssl_cert_lookup_criteria cert_lookup; /** * The ssl socket parameter. diff --git a/pjnath/src/pjnath/turn_sock.c b/pjnath/src/pjnath/turn_sock.c index b1fcc27bd8..e273e6e283 100644 --- a/pjnath/src/pjnath/turn_sock.c +++ b/pjnath/src/pjnath/turn_sock.c @@ -1389,15 +1389,14 @@ static void turn_on_state(pj_turn_session *sess, &turn_sock->setting.tls_cfg.privkey_buf, &turn_sock->setting.tls_cfg.password, &turn_sock->cert); - } else if (turn_sock->setting.tls_cfg.cert_lookup_type != + } else if (turn_sock->setting.tls_cfg.cert_lookup.type != PJ_SSL_CERT_LOOKUP_NONE && - turn_sock->setting.tls_cfg.cert_lookup_keyword.slen) + turn_sock->setting.tls_cfg.cert_lookup.keyword.slen) { - pj_ssl_cert_lookup_criteria crit = {0}; - crit.type = turn_sock->setting.tls_cfg.cert_lookup_type; - crit.keyword = turn_sock->setting.tls_cfg.cert_lookup_keyword; - status = pj_ssl_cert_load_from_store(turn_sock->pool, &crit, - &turn_sock->cert); + status = pj_ssl_cert_load_from_store( + turn_sock->pool, + &turn_sock->setting.tls_cfg.cert_lookup, + &turn_sock->cert); } if (status != PJ_SUCCESS) { turn_sock_destroy(turn_sock, status); diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index f25fc09e20..8febf392a4 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -189,8 +189,7 @@ typedef struct pjsip_tls_setting pj_ssl_cert_buffer privkey_buf; /** - * Lookup certificate from OS certificate store, this setting will - * specify the field type to lookup. + * Lookup certificate from OS certificate store with specified criteria. * * Currently only TLS backend Windows Schannel support this and this * backend only support this type of certificate settings (settings via @@ -200,13 +199,7 @@ typedef struct pjsip_tls_setting * the backend will provide pre-verification result against trusted * CA certificates in Current User store. */ - pj_ssl_cert_lookup_type cert_lookup_type; - - /** - * Lookup certificate from OS certificate store, this setting will - * specify the keyword to lookup. - */ - pj_str_t cert_lookup_keyword; + pj_ssl_cert_lookup_criteria cert_lookup; /** * Password to open private key. @@ -494,8 +487,8 @@ PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, pj_strdup(pool, &dst->cert_buf, &src->cert_buf); pj_strdup(pool, &dst->privkey_buf, &src->privkey_buf); - pj_strdup_with_null(pool, &dst->cert_lookup_keyword, - &src->cert_lookup_keyword); + pj_strdup_with_null(pool, &dst->cert_lookup.keyword, + &src->cert_lookup.keyword); if (src->ciphers_num) { unsigned i; diff --git a/pjsip/include/pjsua2/siptypes.hpp b/pjsip/include/pjsua2/siptypes.hpp index 4f5c09c14e..9a7a03af50 100644 --- a/pjsip/include/pjsua2/siptypes.hpp +++ b/pjsip/include/pjsua2/siptypes.hpp @@ -192,7 +192,7 @@ struct TlsConfig : public PersistentObject * Lookup certificate from OS certificate store, this setting will * specify the keyword to lookup. */ - string certLookupKeyword; + string certLookupKeyword; /** * TLS protocol method from #pjsip_ssl_method. In the future, this field diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c index 1625ef9e39..818256432b 100644 --- a/pjsip/src/pjsip/sip_transport_tls.c +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -647,14 +647,14 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start2( pjsip_endpoint *endpt, &listener->cert); if (status != PJ_SUCCESS) goto on_error; - } else if (listener->tls_setting.cert_lookup_type != + } else if (listener->tls_setting.cert_lookup.type != PJ_SSL_CERT_LOOKUP_NONE && - listener->tls_setting.cert_lookup_keyword.slen) + listener->tls_setting.cert_lookup.keyword.slen) { - pj_ssl_cert_lookup_criteria crit = {0}; - crit.type = listener->tls_setting.cert_lookup_type; - crit.keyword = listener->tls_setting.cert_lookup_keyword; - status = pj_ssl_cert_load_from_store(pool, &crit, &listener->cert); + status = pj_ssl_cert_load_from_store( + pool, + &listener->tls_setting.cert_lookup, + &listener->cert); if (status != PJ_SUCCESS) goto on_error; } diff --git a/pjsip/src/pjsua2/siptypes.cpp b/pjsip/src/pjsua2/siptypes.cpp index ed652d1758..dd5716c5d4 100644 --- a/pjsip/src/pjsua2/siptypes.cpp +++ b/pjsip/src/pjsua2/siptypes.cpp @@ -192,8 +192,8 @@ pjsip_tls_setting TlsConfig::toPj() const ts.ca_buf = str2Pj(this->CaBuf); ts.cert_buf = str2Pj(this->certBuf); ts.privkey_buf = str2Pj(this->privKeyBuf); - ts.cert_lookup_type = this->certLookupType; - ts.cert_lookup_keyword = str2Pj(this->certLookupKeyword); + ts.cert_lookup.type = this->certLookupType; + ts.cert_lookup.keyword = str2Pj(this->certLookupKeyword); ts.method = this->method; ts.ciphers_num = (unsigned)this->ciphers.size(); ts.proto = this->proto; @@ -223,8 +223,8 @@ void TlsConfig::fromPj(const pjsip_tls_setting &prm) this->CaBuf = pj2Str(prm.ca_buf); this->certBuf = pj2Str(prm.cert_buf); this->privKeyBuf = pj2Str(prm.privkey_buf); - this->certLookupType= prm.cert_lookup_type; - this->certLookupKeyword = pj2Str(prm.cert_lookup_keyword); + this->certLookupType= prm.cert_lookup.type; + this->certLookupKeyword = pj2Str(prm.cert_lookup.keyword); this->method = (pjsip_ssl_method)prm.method; this->proto = prm.proto; // The following will only work if sizeof(enum)==sizeof(int) From fcb55f5960d33e7251295003ba8fcdfa4e39955c Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 23 Feb 2024 16:06:30 +0700 Subject: [PATCH 11/20] Retry using SCHANNEL_CRED when using SCH_CREDENTIALS fails. --- pjlib/src/pj/ssl_sock_schannel.c | 131 +++++++++++++++++++++++++++---- 1 file changed, 114 insertions(+), 17 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index fa2c6b7928..657569115a 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -996,6 +996,94 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) return PJ_SUCCESS; } +static pj_status_t init_creds_old(pj_ssl_sock_t* ssock) +{ + sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + SCHANNEL_CRED creds = { 0 }; + SECURITY_STATUS ss; + + creds.dwVersion = SCHANNEL_CRED_VERSION; + creds.dwFlags = SCH_USE_STRONG_CRYPTO; + + /* Setup Protocol version */ + if (ssock->param.proto != PJ_SSL_SOCK_PROTO_DEFAULT && + ssock->param.proto != PJ_SSL_SOCK_PROTO_ALL) + { + DWORD tmp = 0; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) + tmp |= SP_PROT_TLS1_3; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) + tmp |= SP_PROT_TLS1_2; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) + tmp |= SP_PROT_TLS1_1; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) + tmp |= SP_PROT_TLS1_0; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) + tmp |= SP_PROT_SSL3; + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL2) + tmp |= SP_PROT_SSL2; + if (tmp) { + creds.grbitEnabledProtocols = tmp; + LOG_DEBUG1("grbitEnabledProtocols=0x%x", tmp); + } + } + + /* Setup the TLS certificate */ + if (ssock->cert && + ssock->cert->criteria.type != PJ_SSL_CERT_LOOKUP_NONE && + !sch_ssock->cert_ctx) + { + /* Search certificate from stores */ + sch_ssock->cert_ctx = + find_cert_in_stores(ssock->cert->criteria.type, + &ssock->cert->criteria.keyword); + } + + if (ssock->is_server) { + if (!ssock->cert || + ssock->cert->criteria.type == PJ_SSL_CERT_LOOKUP_NONE) + { + /* No certificate specified, use self-signed cert */ + sch_ssock->cert_ctx = create_self_signed_cert(); + PJ_LOG(2,(SENDER, "Warning: TLS server does not specify a " + "certificate, use a self-signed certificate")); + } + } else { + creds.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; + } + + if (sch_ssock->cert_ctx) { + creds.cCreds = 1; + creds.paCred = &sch_ssock->cert_ctx; + } + + /* Verification */ + if (!ssock->is_server) { + if (ssock->param.verify_peer) + creds.dwFlags |= SCH_CRED_AUTO_CRED_VALIDATION | + SCH_CRED_REVOCATION_CHECK_CHAIN; + else + creds.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; + } else { + if (ssock->param.verify_peer) + creds.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN; + } + + ss = AcquireCredentialsHandle(NULL, UNISP_NAME, + ssock->is_server? SECPKG_CRED_INBOUND : + SECPKG_CRED_OUTBOUND, + NULL, &creds, NULL, NULL, + &sch_ssock->cred_handle, NULL); + if (ss < 0) { + log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); + + return sec_err_to_pj(ss); + } + + return PJ_SUCCESS; +} + + static void verify_remote_cert(pj_ssl_sock_t* ssock) { @@ -1109,6 +1197,10 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Create credential handle, if not yet */ if (!SecIsValidHandle(&sch_ssock->cred_handle)) { status2 = init_creds(ssock); + if (status2 != PJ_SUCCESS) { + /* On error, retry using older version of credential */ + status2 = init_creds_old(ssock); + } if (status2 != PJ_SUCCESS) { status = status2; goto on_return; @@ -1363,13 +1455,14 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) ss = DecryptMessage(&sch_ssock->ctx_handle, &buf_desc, 0, NULL); - /* Check for any unprocessed input data, put it back to buffer */ - i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_EXTRA); - if (i >= 0) { - circ_read_cancel(&ssock->circ_buf_input, buf[i].cbBuffer); - } - if (ss == SEC_E_OK) { + /* Check for any unprocessed input data, put it back to buffer */ + i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_EXTRA); + if (i >= 0) { + circ_read_cancel(&ssock->circ_buf_input, buf[i].cbBuffer); + } + + /* Process any decrypted data */ i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_DATA); if (i >= 0) { pj_uint8_t *p = buf[i].pvBuffer; @@ -1404,20 +1497,24 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) } else if (ss == SEC_I_RENEGOTIATE) { - PJ_LOG(3, (SENDER, "Remote signals renegotiation")); - - if (SecIsValidHandle(&sch_ssock->ctx_handle)) { - DeleteSecurityContext(&sch_ssock->ctx_handle); - SecInvalidateHandle(&sch_ssock->ctx_handle); - } + /* Proceed renegotiation (initiated by local or remote) */ + PJ_LOG(3, (SENDER, "Renegotiation on progress")); + + /* Check for any token for renegotiation */ + i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_EXTRA); + if (i >= 0 && buf[i].pvBuffer && buf[i].cbBuffer) { + /* Queue the token as input in the handshake */ + circ_write(&ssock->circ_buf_input, buf[i].pvBuffer, + buf[i].cbBuffer); + } + /* Proceed as though creating a new connection */ + if (SecIsValidHandle(&sch_ssock->ctx_handle)) { + DeleteSecurityContext(&sch_ssock->ctx_handle); + SecInvalidateHandle(&sch_ssock->ctx_handle); + } ssock->ssl_state = SSL_STATE_HANDSHAKING; status = PJ_EEOF; - - /* Any unprocessed data should have been returned via buffer type - * SECBUFFER_EXTRA above (docs seems to say so). - */ - //circ_read_cancel(&ssock->circ_buf_input, size_); } else if (ss == SEC_I_CONTEXT_EXPIRED) From 5b31f80326944647fb10c24e1151323c7e805f97 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 7 Mar 2024 14:52:29 +0700 Subject: [PATCH 12/20] Implement TLS renego (experimental). --- pjlib/src/pj/ssl_sock_schannel.c | 140 +++++++++++++++++-------------- 1 file changed, 78 insertions(+), 62 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 657569115a..9cb2f0a648 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -52,11 +52,12 @@ #define DEBUG_SCHANNEL 1 #if DEBUG_SCHANNEL -# define LOG_DEBUG(title) PJ_LOG(4,(SENDER,title)) -# define LOG_DEBUG1(title,p1) PJ_LOG(4,(SENDER,title,p1)) -# define LOG_DEBUG2(title,p1,p2) PJ_LOG(4,(SENDER,title,p1,p2)) -# define LOG_DEBUG3(title,p1,p2,p3) PJ_LOG(4,(SENDER,title,p1,p2,p3)) -# define LOG_DEBUG_ERR(title, sec_status) log_sec_err(4, title, sec_status) +# define LOG_DEBUG(sender,title) PJ_LOG(4,(sender,title)) +# define LOG_DEBUG1(sender,title,p1) PJ_LOG(4,(sender,title,p1)) +# define LOG_DEBUG2(sender,title,p1,p2) PJ_LOG(4,(sender,title,p1,p2)) +# define LOG_DEBUG3(sender,title,p1,p2,p3) PJ_LOG(4,(sender,title,p1,p2,p3)) +# define LOG_DEBUG_ERR(sender,title,sec_status) \ + log_sec_err(4,sender,title,sec_status) #else # define LOG_DEBUG(s) # define LOG_DEBUG1(title,p1) @@ -127,8 +128,8 @@ typedef struct sch_ssl_sock_t /* === Helper functions === */ -#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \ - PJ_ERRNO_SPACE_SIZE*6) +#define SNAME(ssock) ((ssock)->pool->obj_name) +#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*6) /* Map SECURITY_STATUS to pj_status_t. * @@ -178,7 +179,8 @@ static SECURITY_STATUS sec_err_from_pj(pj_status_t status) } /* Print Schannel error to log */ -static void log_sec_err(int log_level, const char* title, SECURITY_STATUS ss) +static void log_sec_err(int log_level, const char* sender, + const char* title, SECURITY_STATUS ss) { char *str = NULL; DWORD len; @@ -192,22 +194,22 @@ static void log_sec_err(int log_level, const char* title, SECURITY_STATUS ss) switch (log_level) { case 1: - PJ_LOG(1, (SENDER, "%s: 0x%x-%s", title, ss, str)); + PJ_LOG(1, (sender, "%s: 0x%x-%s", title, ss, str)); break; case 2: - PJ_LOG(2, (SENDER, "%s: 0x%x-%s", title, ss, str)); + PJ_LOG(2, (sender, "%s: 0x%x-%s", title, ss, str)); break; case 3: - PJ_LOG(3, (SENDER, "%s: 0x%x-%s", title, ss, str)); + PJ_LOG(3, (sender, "%s: 0x%x-%s", title, ss, str)); break; case 4: - PJ_LOG(4, (SENDER, "%s: 0x%x-%s", title, ss, str)); + PJ_LOG(4, (sender, "%s: 0x%x-%s", title, ss, str)); break; case 5: - PJ_LOG(5, (SENDER, "%s: 0x%x-%s", title, ss, str)); + PJ_LOG(5, (sender, "%s: 0x%x-%s", title, ss, str)); break; default: - PJ_LOG(6, (SENDER, "%s: 0x%x-%s", title, ss, str)); + PJ_LOG(6, (sender, "%s: 0x%x-%s", title, ss, str)); break; } @@ -364,7 +366,7 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; SECURITY_STATUS ss; - LOG_DEBUG("SSL reset"); + LOG_DEBUG(SNAME(ssock), "SSL reset"); pj_lock_acquire(ssock->write_mutex); @@ -414,7 +416,7 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) buf_out[0].pvBuffer, buf_out[0].cbBuffer); if (status != PJ_SUCCESS) { - PJ_PERROR(1, (SENDER, status, + PJ_PERROR(1, (SNAME(ssock), status, "Failed to queuehandshake packets")); } else { flush_circ_buf_output(ssock, &ssock->shutdown_op_key, @@ -422,7 +424,7 @@ static void ssl_reset_sock_state(pj_ssl_sock_t* ssock) } } } else { - log_sec_err(1, "Error in shutting down SSL", ss); + log_sec_err(1, SNAME(ssock), "Error in shutting down SSL", ss); } } @@ -737,7 +739,8 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) if (ss != SEC_E_OK || !cert_ctx) { if (!ssock->is_server) - log_sec_err(1, "Failed to retrieve remote certificate", ss); + log_sec_err(1, SNAME(ssock), + "Failed to retrieve remote certificate", ss); } else { cert_parse_info(ssock->pool, &ssock->remote_cert_info, cert_ctx); CertFreeCertificateContext(cert_ctx); @@ -749,7 +752,8 @@ static void ssl_update_certs_info(pj_ssl_sock_t *ssock) if (ss != SEC_E_OK || !cert_ctx) { if (ssock->is_server) - log_sec_err(3, "Failed to retrieve local certificate", ss); + log_sec_err(3, SNAME(ssock), + "Failed to retrieve local certificate", ss); } else { cert_parse_info(ssock->pool, &ssock->local_cert_info, cert_ctx); @@ -803,7 +807,8 @@ static PCCERT_CONTEXT find_cert_in_stores(pj_ssl_cert_lookup_type type, { PCCERT_CONTEXT cert = NULL; - LOG_DEBUG3("Looking up certificate with criteria: type=%d keyword=%.*s", + LOG_DEBUG3(SENDER, + "Looking up certificate with criteria: type=%d keyword=%.*s", type, (type==PJ_SSL_CERT_LOOKUP_FINGERPRINT? 4:keyword->slen), (type==PJ_SSL_CERT_LOOKUP_FINGERPRINT?"[..]":keyword->ptr)); @@ -818,7 +823,7 @@ static PCCERT_CONTEXT find_cert_in_stores(pj_ssl_cert_lookup_type type, store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING, 0, flags[i], L"MY"); if (!store) { - log_sec_err(1, "Error opening store", GetLastError()); + log_sec_err(1, SENDER, "Error opening store", GetLastError()); continue; } @@ -917,7 +922,7 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) if (tmp) { param_cnt = 1; param.grbitDisabledProtocols = ~tmp; - LOG_DEBUG1("grbitDisabledProtocols=0x%x", (~tmp)); + LOG_DEBUG1(SNAME(ssock), "grbitDisabledProtocols=0x%x", (~tmp)); } } @@ -938,8 +943,9 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) { /* No certificate specified, use self-signed cert */ sch_ssock->cert_ctx = create_self_signed_cert(); - PJ_LOG(2,(SENDER, "Warning: TLS server does not specify a " - "certificate, use a self-signed certificate")); + PJ_LOG(2,(SNAME(ssock), + "Warning: certificate is not specified for " + "TLS server, use a self-signed certificate.")); // -- test code -- //pj_str_t keyword = {"test.pjsip.org", 14}; @@ -988,7 +994,8 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) NULL, &creds, NULL, NULL, &sch_ssock->cred_handle, NULL); if (ss < 0) { - log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); + log_sec_err(1, SNAME(ssock), + "Failed in AcquireCredentialsHandle()", ss); return sec_err_to_pj(ss); } @@ -1024,7 +1031,7 @@ static pj_status_t init_creds_old(pj_ssl_sock_t* ssock) tmp |= SP_PROT_SSL2; if (tmp) { creds.grbitEnabledProtocols = tmp; - LOG_DEBUG1("grbitEnabledProtocols=0x%x", tmp); + LOG_DEBUG1(SNAME(ssock), "grbitEnabledProtocols=0x%x", tmp); } } @@ -1045,8 +1052,9 @@ static pj_status_t init_creds_old(pj_ssl_sock_t* ssock) { /* No certificate specified, use self-signed cert */ sch_ssock->cert_ctx = create_self_signed_cert(); - PJ_LOG(2,(SENDER, "Warning: TLS server does not specify a " - "certificate, use a self-signed certificate")); + PJ_LOG(2,(SNAME(ssock), + "Warning: TLS server does not specify a " + "certificate, use a self-signed certificate")); } } else { creds.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; @@ -1075,7 +1083,8 @@ static pj_status_t init_creds_old(pj_ssl_sock_t* ssock) NULL, &creds, NULL, NULL, &sch_ssock->cred_handle, NULL); if (ss < 0) { - log_sec_err(1, "Failed in AcquireCredentialsHandle()", ss); + log_sec_err(1, SNAME(ssock), + "Failed in AcquireCredentialsHandle()", ss); return sec_err_to_pj(ss); } @@ -1101,7 +1110,7 @@ static void verify_remote_cert(pj_ssl_sock_t* ssock) if (!ssock->is_server || (ssock->is_server && ssock->param.require_client_cert)) { - log_sec_err(1, "Error querying remote cert", ss); + log_sec_err(1, SNAME(ssock), "Error querying remote cert", ss); } goto on_return; } @@ -1113,7 +1122,8 @@ static void verify_remote_cert(pj_ssl_sock_t* ssock) NULL, NULL, &chain_para, 0, 0, &chain_ctx)) { - log_sec_err(1, "Failed to get remote cert chain for verification", + log_sec_err(1, SNAME(ssock), + "Failed to get remote cert chain for verification", GetLastError()); goto on_return; } @@ -1186,6 +1196,7 @@ static void verify_remote_cert(pj_ssl_sock_t* ssock) static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) { sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; + pj_bool_t renego_req; pj_size_t data_in_size = 0; pj_uint8_t* data_in = NULL; SECURITY_STATUS ss; @@ -1207,11 +1218,14 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) } } + /* Is this a renegotiation request? */ + renego_req = (ssock->ssl_state == SSL_STATE_ESTABLISHED); + /* Start handshake iteration */ pj_lock_acquire(ssock->circ_buf_input_mutex); - if (!circ_empty(&ssock->circ_buf_input)) { + if (!circ_empty(&ssock->circ_buf_input) && !renego_req) { data_in = sch_ssock->read_buf; data_in_size = PJ_MIN(sch_ssock->read_buf_cap, circ_size(&ssock->circ_buf_input)); @@ -1286,20 +1300,20 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) circ_read_cancel(&ssock->circ_buf_input, buf_in[1].cbBuffer); } - if (ss == SEC_E_OK) { + if (ss == SEC_E_OK && !renego_req) { SECURITY_STATUS ss2; /* Handshake completed! */ ssock->ssl_state = SSL_STATE_ESTABLISHED; status = PJ_SUCCESS; - PJ_LOG(3, (SENDER, "TLS handshake completed!")); + PJ_LOG(3, (SNAME(ssock), "TLS handshake completed!")); /* Get stream sizes */ ss2 = QueryContextAttributes(&sch_ssock->ctx_handle, SECPKG_ATTR_STREAM_SIZES, &sch_ssock->strm_sizes); if (ss2 != SEC_E_OK) { - log_sec_err(1, "Failed to query stream sizes", ss2); + log_sec_err(1, SNAME(ssock), "Failed to query stream sizes", ss2); ssl_reset_sock_state(ssock); status = sec_err_to_pj(ss2); } @@ -1326,30 +1340,31 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Perhaps CompleteAuthToken() is unnecessary for Schannel, but * the sample code seems to call it. */ - LOG_DEBUG_ERR("Handshake progress", ss); + LOG_DEBUG_ERR(SNAME(ssock), "Handshake progress", ss); ss = CompleteAuthToken(&sch_ssock->ctx_handle, &buf_desc_out); if (ss != SEC_E_OK) { - log_sec_err(1, "Handshake error in CompleteAuthToken()", ss); + log_sec_err(1, SNAME(ssock), + "Handshake error in CompleteAuthToken()", ss); status = sec_err_to_pj(ss); } } else if (ss == SEC_I_CONTINUE_NEEDED) { - LOG_DEBUG_ERR("Handshake progress", ss); + LOG_DEBUG_ERR(SNAME(ssock), "Handshake progress", ss); } else if (ss == SEC_E_INCOMPLETE_MESSAGE) { - LOG_DEBUG_ERR("Handshake progress", ss); + LOG_DEBUG_ERR(SNAME(ssock), "Handshake progress", ss); /* Put back the incomplete message */ circ_read_cancel(&ssock->circ_buf_input, data_in_size); } - else { + else if (!renego_req) { /* Handshake failed */ - log_sec_err(1, "Handshake failed!", ss); + log_sec_err(1, SNAME(ssock), "Handshake failed!", ss); status = sec_err_to_pj(ss); } @@ -1362,7 +1377,7 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) status2 = circ_write(&ssock->circ_buf_output, buf_out[0].pvBuffer, buf_out[0].cbBuffer); if (status2 != PJ_SUCCESS) { - PJ_PERROR(1,(SENDER, status2, + PJ_PERROR(1,(SNAME(ssock), status2, "Failed to queue handshake packets")); status = status2; } @@ -1371,7 +1386,8 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) /* Send handshake packets to wire */ status2 = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); if (status2 != PJ_SUCCESS && status2 != PJ_EPENDING) { - PJ_PERROR(1,(SENDER, status2, "Failed to send handshake packets")); + PJ_PERROR(1,(SNAME(ssock), status2, + "Failed to send handshake packets")); status = status2; } @@ -1383,9 +1399,10 @@ static pj_status_t ssl_do_handshake(pj_ssl_sock_t* ssock) static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) { - PJ_TODO(implement_this); - PJ_UNUSED_ARG(ssock); - return PJ_ENOTSUP; + PJ_LOG(3, (SNAME(ssock), "App requested renegotiation..")); + + /* Nothing to do, SSL sock common will invoke ssl_do_handshake() */ + return PJ_SUCCESS; } static int find_sec_buffer(const SecBuffer* buf, int buf_cnt, @@ -1417,13 +1434,15 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) /* Got all from the decrypted buffer */ circ_read(&sch_ssock->decrypted_buf, data, need); *size = need; - LOG_DEBUG1("Read %d: returned all from decrypted buffer.", requested); + LOG_DEBUG1(SNAME(ssock), + "Read %d: returned all from decrypted buffer.", requested); pj_lock_release(ssock->circ_buf_input_mutex); return PJ_SUCCESS; } /* Get all data of the decrypted buffer, then decrypt more */ - LOG_DEBUG2("Read %d: %d from decrypted buffer..", requested, size_); + LOG_DEBUG2(SNAME(ssock), + "Read %d: %d from decrypted buffer..", requested, size_); circ_read(&sch_ssock->decrypted_buf, data, size_); *size = (int)size_; need -= (int)size_; @@ -1435,7 +1454,7 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) circ_size(&ssock->circ_buf_input)); circ_read(&ssock->circ_buf_input, data_, size_); } else { - LOG_DEBUG2("Read %d: no data to decrypt, returned %d.", + LOG_DEBUG2(SNAME(ssock), "Read %d: no data to decrypt, returned %d.", requested, *size); pj_lock_release(ssock->circ_buf_input_mutex); return PJ_SUCCESS; @@ -1478,13 +1497,13 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) if (len) circ_write(&sch_ssock->decrypted_buf, p, len); - LOG_DEBUG2("Read %d: after decrypt, excess=%d", + LOG_DEBUG2(SNAME(ssock), "Read %d: after decrypt, excess=%d", requested, len); } else { /* Not enough, just give everything */ pj_memcpy((pj_uint8_t*)data + *size, p, len); *size += (int)len; - LOG_DEBUG2("Read %d: after decrypt, only got %d", + LOG_DEBUG2(SNAME(ssock),"Read %d: after decrypt, only got %d", requested, len); } } @@ -1498,7 +1517,7 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) else if (ss == SEC_I_RENEGOTIATE) { /* Proceed renegotiation (initiated by local or remote) */ - PJ_LOG(3, (SENDER, "Renegotiation on progress")); + PJ_LOG(3, (SNAME(ssock), "Renegotiation on progress")); /* Check for any token for renegotiation */ i = find_sec_buffer(buf, ARRAYSIZE(buf), SECBUFFER_EXTRA); @@ -1508,30 +1527,27 @@ static pj_status_t ssl_read(pj_ssl_sock_t* ssock, void* data, int* size) buf[i].cbBuffer); } - /* Proceed as though creating a new connection */ - if (SecIsValidHandle(&sch_ssock->ctx_handle)) { - DeleteSecurityContext(&sch_ssock->ctx_handle); - SecInvalidateHandle(&sch_ssock->ctx_handle); - } + /* Set SSL state as handshaking & reset handshake status */ ssock->ssl_state = SSL_STATE_HANDSHAKING; + ssock->handshake_status = PJ_EUNKNOWN; status = PJ_EEOF; } else if (ss == SEC_I_CONTEXT_EXPIRED) { - PJ_LOG(3, (SENDER, "TLS connection closed")); + PJ_LOG(3, (SNAME(ssock), "TLS connection closed")); //status = sec_err_to_pj(ss); status = PJ_ECANCELLED; } else { - log_sec_err(1, "Decrypt error", ss); + log_sec_err(1, SNAME(ssock), "Decrypt error", ss); status = sec_err_to_pj(ss); } pj_lock_release(ssock->circ_buf_input_mutex); - LOG_DEBUG2("Read %d: returned=%d.", requested, *size); + LOG_DEBUG2(SNAME(ssock), "Read %d: returned=%d.", requested, *size); return status; } @@ -1577,7 +1593,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, ss = EncryptMessage(&sch_ssock->ctx_handle, 0, &buf_desc, 0); if (ss != SEC_E_OK) { - log_sec_err(1, "Encrypt error", ss); + log_sec_err(1, SNAME(ssock), "Encrypt error", ss); status = sec_err_to_pj(ss); break; } @@ -1587,7 +1603,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t* ssock, const void* data, status = circ_write(&ssock->circ_buf_output, sch_ssock->write_buf, out_size); if (status != PJ_SUCCESS) { - PJ_PERROR(1, (SENDER, status, + PJ_PERROR(1, (SNAME(ssock), status, "Failed to queue outgoing packets")); break; } From 8fec1902c50d4b37600aac78066dc04ed101e316 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 14 Mar 2024 15:16:42 +0700 Subject: [PATCH 13/20] Update PJLIB SSL socket test for Schannel backend. --- pjlib/src/pj/ssl_sock_schannel.c | 24 +++-- pjlib/src/pjlib-test/ssl_sock.c | 145 ++++++++++++++++++++----------- 2 files changed, 111 insertions(+), 58 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 9cb2f0a648..6c5c612477 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -49,7 +49,7 @@ #define SENDER "ssl_schannel" /* Debugging */ -#define DEBUG_SCHANNEL 1 +#define DEBUG_SCHANNEL 0 #if DEBUG_SCHANNEL # define LOG_DEBUG(sender,title) PJ_LOG(4,(sender,title)) @@ -59,11 +59,11 @@ # define LOG_DEBUG_ERR(sender,title,sec_status) \ log_sec_err(4,sender,title,sec_status) #else -# define LOG_DEBUG(s) -# define LOG_DEBUG1(title,p1) -# define LOG_DEBUG2(title,p1,p2) -# define LOG_DEBUG3(title,p1,p2,p3) -# define LOG_DEBUG_ERR(title, sec_status) +# define LOG_DEBUG(sender,title) +# define LOG_DEBUG1(sender,title,p1) +# define LOG_DEBUG2(sender,title,p1,p2) +# define LOG_DEBUG3(sender,title,p1,p2,p3) +# define LOG_DEBUG_ERR(sender,title,sec_status) #endif @@ -298,6 +298,12 @@ static pj_status_t ssl_create(pj_ssl_sock_t *ssock) pj_ssize_t read_cap, write_cap; pj_status_t status = PJ_SUCCESS; + /* Ciphers & curves settings should be set via OS/registry */ + if (ssock->param.ciphers_num || ssock->param.curves_num) { + PJ_LOG(3,(SNAME(ssock), "Ciphers and curves settings are ignored, " + "they should be set via OS/registry")); + } + read_cap = PJ_MAX(MIN_READ_BUF_CAP, ssock->param.read_buffer_size); write_cap = PJ_MAX(MIN_WRITE_BUF_CAP, ssock->param.send_buffer_size); @@ -467,6 +473,10 @@ static void ssl_ciphers_populate() return; } + /* Make sure schannel's pool is created */ + sch_inc(); + sch_dec(); + for (ULONG i = 0; i < fn->cFunctions; i++) { char tmp_buf[SZ_ALG_MAX_SIZE]; pj_str_t tmp_st; @@ -891,6 +901,7 @@ static PCCERT_CONTEXT find_cert_in_stores(pj_ssl_cert_lookup_type type, } +/* Initialize credentials */ static pj_status_t init_creds(pj_ssl_sock_t* ssock) { sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; @@ -1003,6 +1014,7 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) return PJ_SUCCESS; } +/* Initialize credentials using older data type SCHANNEL_CRED */ static pj_status_t init_creds_old(pj_ssl_sock_t* ssock) { sch_ssl_sock_t* sch_ssock = (sch_ssl_sock_t*)ssock; diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c index 39ec158b0a..4b07aaaf5c 100644 --- a/pjlib/src/pjlib-test/ssl_sock.c +++ b/pjlib/src/pjlib-test/ssl_sock.c @@ -602,6 +602,13 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, } /* Set server cert */ + /* Schannel backend currently can only load certificates from + * OS cert store. It will create a self-signed cert if none is specified. + */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) + PJ_UNUSED_ARG(cert); + PJ_UNUSED_ARG(client_provide_cert); +#else { pj_str_t ca_file = pj_str(CERT_CA_FILE); pj_str_t cert_file = pj_str(CERT_FILE); @@ -643,6 +650,7 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, goto on_return; } } +#endif status = pj_ssl_sock_start_accept(ssock_serv, pool, &addr, pj_sockaddr_get_len(&addr)); if (status != PJ_SUCCESS) { @@ -687,6 +695,10 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, } /* Set cert for client */ + /* Schannel backend currently can only load certificates from + * OS cert store. + */ +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) { if (!client_provide_cert) { @@ -720,6 +732,7 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, goto on_return; } } +#endif status = pj_ssl_sock_start_connect(ssock_cli, pool, &addr, &listen_addr, pj_sockaddr_get_len(&addr)); if (status == PJ_SUCCESS) { @@ -926,7 +939,34 @@ static int client_non_ssl(unsigned ms_timeout) goto on_return; } + pj_ssl_sock_param_default(¶m); + param.cb.on_accept_complete2 = &ssl_on_accept_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.timer_heap = timer; + param.timeout.sec = 0; + param.timeout.msec = ms_timeout; + pj_time_val_normalize(¶m.timeout); + + /* SERVER */ + param.user_data = &state_serv; + state_serv.pool = pool; + state_serv.is_server = PJ_TRUE; + state_serv.is_verbose = PJ_TRUE; + + status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); + if (status != PJ_SUCCESS) { + goto on_return; + } + /* Set cert */ + /* Schannel backend currently can only load certificates from + * OS cert store. It will create a self-signed cert if none is specified. + */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) + PJ_UNUSED_ARG(cert); +#else { pj_str_t ca_file = pj_str(CERT_CA_FILE); pj_str_t cert_file = pj_str(CERT_FILE); @@ -964,31 +1004,11 @@ static int client_non_ssl(unsigned ms_timeout) } } - pj_ssl_sock_param_default(¶m); - param.cb.on_accept_complete2 = &ssl_on_accept_complete; - param.cb.on_data_read = &ssl_on_data_read; - param.cb.on_data_sent = &ssl_on_data_sent; - param.ioqueue = ioqueue; - param.timer_heap = timer; - param.timeout.sec = 0; - param.timeout.msec = ms_timeout; - pj_time_val_normalize(¶m.timeout); - - /* SERVER */ - param.user_data = &state_serv; - state_serv.pool = pool; - state_serv.is_server = PJ_TRUE; - state_serv.is_verbose = PJ_TRUE; - - status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); - if (status != PJ_SUCCESS) { - goto on_return; - } - status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); if (status != PJ_SUCCESS) { goto on_return; } +#endif /* Init bind address */ { @@ -1261,7 +1281,42 @@ static int perf_test(unsigned clients, unsigned ms_handshake_timeout) goto on_return; } + pj_ssl_sock_param_default(¶m); + param.cb.on_accept_complete2 = &ssl_on_accept_complete; + param.cb.on_connect_complete = &ssl_on_connect_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.timer_heap = timer; + param.timeout.sec = 0; + param.timeout.msec = ms_handshake_timeout; + pj_time_val_normalize(¶m.timeout); + + /* Init default bind address */ + { + pj_str_t tmp_st; + pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); + } + + /* SERVER */ + param.user_data = &state_serv; + + state_serv.pool = pool; + state_serv.echo = PJ_TRUE; + state_serv.is_server = PJ_TRUE; + + status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); + if (status != PJ_SUCCESS) { + goto on_return; + } + /* Set cert */ + /* Schannel backend currently can only load certificates from + * OS cert store. It will create a self-signed cert if none is specified. + */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) + PJ_UNUSED_ARG(cert); +#else { pj_str_t ca_file = pj_str(CERT_CA_FILE); pj_str_t cert_file = pj_str(CERT_FILE); @@ -1299,39 +1354,11 @@ static int perf_test(unsigned clients, unsigned ms_handshake_timeout) } } - pj_ssl_sock_param_default(¶m); - param.cb.on_accept_complete2 = &ssl_on_accept_complete; - param.cb.on_connect_complete = &ssl_on_connect_complete; - param.cb.on_data_read = &ssl_on_data_read; - param.cb.on_data_sent = &ssl_on_data_sent; - param.ioqueue = ioqueue; - param.timer_heap = timer; - param.timeout.sec = 0; - param.timeout.msec = ms_handshake_timeout; - pj_time_val_normalize(¶m.timeout); - - /* Init default bind address */ - { - pj_str_t tmp_st; - pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); - } - - /* SERVER */ - param.user_data = &state_serv; - - state_serv.pool = pool; - state_serv.echo = PJ_TRUE; - state_serv.is_server = PJ_TRUE; - - status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); - if (status != PJ_SUCCESS) { - goto on_return; - } - status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); if (status != PJ_SUCCESS) { goto on_return; } +#endif status = pj_ssl_sock_start_accept(ssock_serv, pool, &addr, pj_sockaddr_get_len(&addr)); if (status != PJ_SUCCESS) { @@ -1535,6 +1562,15 @@ int ssl_sock_test(void) * which require SSL server, for now. */ + /* Schannel backend notes: + * - currently it does not support ciphers settings, so we exclude tests + * whose ciphers setting is specified. + * - TLS protocol older than 1.0 is not supported, TLS 1.0 & 1.1 will be + * disabled soon, TLS 1.3 is supported since Windows 11, so for now + * we only include tests with TLS 1.2. + */ + +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) PJ_LOG(3,("", "..echo test w/ TLSv1 and PJ_TLS_RSA_WITH_AES_256_CBC_SHA cipher")); ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_TLS1, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, @@ -1548,6 +1584,7 @@ int ssl_sock_test(void) PJ_FALSE, PJ_FALSE); if (ret != 0) return ret; +#endif PJ_LOG(3,("", "..echo test w/ compatible proto: server TLSv1.2 vs client TLSv1.2")); ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1_2, PJ_SSL_SOCK_PROTO_TLS1_2, @@ -1556,6 +1593,7 @@ int ssl_sock_test(void) if (ret != 0) return ret; +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) PJ_LOG(3,("", "..echo test w/ compatible proto: server TLSv1.2+1.3 vs client TLSv1.3")); ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1_2 | PJ_SSL_SOCK_PROTO_TLS1_3, PJ_SSL_SOCK_PROTO_TLS1_3, -1, -1, @@ -1569,9 +1607,10 @@ int ssl_sock_test(void) PJ_FALSE, PJ_FALSE); if (ret == 0) return PJ_EBUG; +#endif /* We can't set min/max proto for TLS protocol higher than 1.0. */ -#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_DARWIN) +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_DARWIN && PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) PJ_LOG(3,("", "..echo test w/ incompatible proto: server TLSv1.2 vs client TLSv1.3")); ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1_2, PJ_SSL_SOCK_PROTO_TLS1_3, -1, -1, @@ -1584,7 +1623,7 @@ int ssl_sock_test(void) * deprecated and we only have sec_protocol_options_append_tls_ciphersuite(), * but there's no API to remove certain or all ciphers. */ -#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_APPLE) +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_APPLE && PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) PJ_LOG(3,("", "..echo test w/ incompatible ciphers")); ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, PJ_TLS_RSA_WITH_DES_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, @@ -1593,6 +1632,7 @@ int ssl_sock_test(void) return PJ_EBUG; #endif +#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) 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, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, @@ -1606,6 +1646,7 @@ int ssl_sock_test(void) PJ_TRUE, PJ_TRUE); if (ret != 0) return ret; +#endif #if WITH_BENCHMARK PJ_LOG(3,("", "..performance test")); From f044db6d77bdcd90c6f7235e4687de15e60ffc9d Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 14 Mar 2024 15:35:14 +0700 Subject: [PATCH 14/20] Add ssl_sock_schannel.c to PJLIB project (vs14) --- pjlib/build/pjlib.vcxproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pjlib/build/pjlib.vcxproj b/pjlib/build/pjlib.vcxproj index 227c3f0455..90ffe174f7 100644 --- a/pjlib/build/pjlib.vcxproj +++ b/pjlib/build/pjlib.vcxproj @@ -1,4 +1,4 @@ - + @@ -998,6 +998,7 @@ + true From 51379b9288e62b9439a345939bbf8c54ad677846 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Fri, 15 Mar 2024 17:33:51 +0700 Subject: [PATCH 15/20] Modifications based on comments --- pjlib/include/pj/ssl_sock.h | 15 ++++-- pjlib/src/pj/ssl_sock_imp_common.c | 13 ++--- pjlib/src/pj/ssl_sock_schannel.c | 33 ++++++------- pjlib/src/pjlib-test/ssl_sock.c | 77 ++++++++++++++++++++++++------ 4 files changed, 92 insertions(+), 46 deletions(-) diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h index bc7d5f0326..d24be89211 100644 --- a/pjlib/include/pj/ssl_sock.h +++ b/pjlib/include/pj/ssl_sock.h @@ -163,7 +163,7 @@ typedef enum pj_ssl_cert_lookup_type /** * Lookup by subject, this will lookup any first certificate whose * subject containing the specified keyword. Note that subject may not - * be unique in the store, the lookup may end up selecting a wrong + * be unique in the store, so the lookup may end up selecting a wrong * certificate. */ PJ_SSL_CERT_LOOKUP_SUBJECT, @@ -178,7 +178,7 @@ typedef enum pj_ssl_cert_lookup_type /** * Lookup by friendly name, this will lookup any first certificate * whose friendly name containing the specified keyword. Note that - * friendly name may not be unique in the store, the lookup may end up + * friendly name may not be unique in the store, so the lookup may end up * selecting a wrong certificate. */ PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME @@ -196,7 +196,7 @@ typedef struct pj_ssl_cert_lookup_criteria pj_ssl_cert_lookup_type type; /* - * Keyword to match. + * Keyword to match on the field specified in \a type. */ pj_str_t keyword; @@ -336,8 +336,13 @@ PJ_DECL(pj_status_t) pj_ssl_cert_load_from_buffer(pj_pool_t *pool, * certificate using the specified criterias. * * Currently this is used by Windows Schannel backend only, it will lookup - * in the Current User store first, if not found it will lookup in the - * Local Machine store. + * in the Current User store first, if no certificate with the specified + * criteria is not found, it will lookup in the Local Machine store. + * + * Note that for manual verification (e.g: when pj_ssl_sock_param.verify_peer + * is disabled), the backend will provide pre-verification result against + * trusted CA certificates in Current User store only (will not check CA + * certificates in the Local Machine store). * * @param pool The pool. * @param criteria The lookup criteria. diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c index 7f24d086b2..f1d1db63dd 100644 --- a/pjlib/src/pj/ssl_sock_imp_common.c +++ b/pjlib/src/pj/ssl_sock_imp_common.c @@ -55,14 +55,11 @@ static pj_bool_t asock_on_data_sent (pj_activesock_t *asock, static pj_size_t next_pow2(pj_size_t n) { /* Next 32-bit power of two */ - n--; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n++; - return n; + pj_size_t power = 1; + while (power < n && power < 0x8000000) { + power <<= 1; + } + return power; } static pj_status_t circ_init(pj_pool_factory *factory, diff --git a/pjlib/src/pj/ssl_sock_schannel.c b/pjlib/src/pj/ssl_sock_schannel.c index 6c5c612477..0341c0fd93 100644 --- a/pjlib/src/pj/ssl_sock_schannel.c +++ b/pjlib/src/pj/ssl_sock_schannel.c @@ -37,7 +37,7 @@ * * Note: * - Older Windows SDK versions are not supported (some APIs deprecated, - * further more they must be missing newer/safer TLS protocol versions). + * also missing newer/safer TLS protocol versions). */ #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) && \ @@ -48,6 +48,9 @@ /* Sender string in logging */ #define SENDER "ssl_schannel" +/* Subject for self-signed certificate */ +#define SELF_SIGNED_CERT_SUBJECT "CN=lab.pjsip.org" + /* Debugging */ #define DEBUG_SCHANNEL 0 @@ -155,8 +158,11 @@ static pj_status_t sec_err_to_pj(SECURITY_STATUS ss) DWORD err = ((ss & 0x7FFF) << 1) | ((ss & 0x80000000) >> 31); /* Make sure it does not exceed PJ_ERRNO_SPACE_SIZE */ - if (err >= PJ_ERRNO_SPACE_SIZE) + if (err >= PJ_ERRNO_SPACE_SIZE) { + LOG_DEBUG1(SENDER,"sec_err_to_pj() failed mapping error code %d", + err); return PJ_EUNKNOWN; + } return PJ_SSL_ERRNO_START + err; } @@ -170,6 +176,8 @@ static SECURITY_STATUS sec_err_from_pj(pj_status_t status) if (status < PJ_SSL_ERRNO_START || status >= PJ_SSL_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + LOG_DEBUG1(SENDER,"sec_err_from_pj() failed mapping status code %d", + status); return ERROR_INVALID_DATA; } @@ -795,7 +803,7 @@ static PCCERT_CONTEXT create_self_signed_cert() cert_name.pbData = cert_name_buffer; cert_name.cbData = ARRAYSIZE(cert_name_buffer); - if (!CertStrToNameA(X509_ASN_ENCODING, "CN=lab.pjsip.org", + if (!CertStrToNameA(X509_ASN_ENCODING, SELF_SIGNED_CERT_SUBJECT, CERT_X500_NAME_STR, NULL, cert_name.pbData, &cert_name.cbData, NULL)) { @@ -837,6 +845,9 @@ static PCCERT_CONTEXT find_cert_in_stores(pj_ssl_cert_lookup_type type, continue; } + LOG_DEBUG1(SENDER, "Looking up certificate in store: %s", + (i==0? "Current User" : "Local Machine")); + /* Lookup based on type */ if (type == PJ_SSL_CERT_LOOKUP_SUBJECT) { @@ -956,21 +967,7 @@ static pj_status_t init_creds(pj_ssl_sock_t* ssock) sch_ssock->cert_ctx = create_self_signed_cert(); PJ_LOG(2,(SNAME(ssock), "Warning: certificate is not specified for " - "TLS server, use a self-signed certificate.")); - - // -- test code -- - //pj_str_t keyword = {"test.pjsip.org", 14}; - //pj_ssl_cert_lookup_type type = PJ_SSL_CERT_LOOKUP_SUBJECT; - - //pj_str_t keyword = {"schannel-test", 13}; - //pj_ssl_cert_lookup_type type = PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME; - - //pj_str_t keyword = {"\x08\x3a\x6c\xdc\xd0\x19\x59\xec\x28\xc3" - // "\x81\xb8\xc0\x21\x09\xe9\xd5\xf6\x57\x7d", - // 20}; - //pj_ssl_cert_lookup_type type = PJ_SSL_CERT_LOOKUP_FINGERPRINT; - - //sch_ssock->cert_ctx = find_cert_in_stores( type, &keyword); + "TLS server, using a self-signed certificate.")); } } else { creds.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c index 4b07aaaf5c..89bbf1ba86 100644 --- a/pjlib/src/pjlib-test/ssl_sock.c +++ b/pjlib/src/pjlib-test/ssl_sock.c @@ -540,6 +540,24 @@ static pj_status_t load_cert_to_buf(pj_pool_t *pool, const pj_str_t *file_name, } #endif +static pj_status_t load_cert_from_store(pj_pool_t *pool, + pj_ssl_cert_t **p_cert) +{ + pj_ssl_cert_lookup_criteria crit = {0}; + + crit.type = PJ_SSL_CERT_LOOKUP_SUBJECT; + pj_cstr(&crit.keyword, "test.pjsip.org"); + + //crit.type = PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME; + //pj_cstr(&crit.keyword, "schannel-test"); + + //crit.type = PJ_SSL_CERT_LOOKUP_FINGERPRINT; + //pj_cstr(&crit.keyword, "\x08\x3a\x6c\xdc\xd0\x19\x59\xec\x28\xc3" + // "\x81\xb8\xc0\x21\x09\xe9\xd5\xf6\x57\x7d"); + + return pj_ssl_cert_load_from_store(pool, &crit, p_cert); +} + 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_bool_t req_client_cert, pj_bool_t client_provide_cert) @@ -602,12 +620,18 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, } /* Set server cert */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) /* Schannel backend currently can only load certificates from - * OS cert store. It will create a self-signed cert if none is specified. + * OS cert store. If the certificate loading fails, we'll skip setting + * certificate, so the SSL socket will create & use a self-signed cert. */ -#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) - PJ_UNUSED_ARG(cert); - PJ_UNUSED_ARG(client_provide_cert); + status = load_cert_from_store(pool, &cert); + if (status == PJ_SUCCESS) { + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } #else { pj_str_t ca_file = pj_str(CERT_CA_FILE); @@ -694,13 +718,22 @@ static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, goto on_return; } - /* Set cert for client */ - /* Schannel backend currently can only load certificates from - * OS cert store. + /* Set cert for client. + * Reusing certificate for server above, but if client_provide_cert + * is not set, override the certificate with CA certificate. */ -#if (PJ_SSL_SOCK_IMP != PJ_SSL_SOCK_IMP_SCHANNEL) - { +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) + if (client_provide_cert) { + status = load_cert_from_store(pool, &cert); + if (status == PJ_SUCCESS) + status = pj_ssl_sock_set_certificate(ssock_cli, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } +#else + { if (!client_provide_cert) { pj_str_t ca_file = pj_str(CERT_CA_FILE); pj_str_t null_str = pj_str(""); @@ -961,11 +994,18 @@ static int client_non_ssl(unsigned ms_timeout) } /* Set cert */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) /* Schannel backend currently can only load certificates from - * OS cert store. It will create a self-signed cert if none is specified. + * OS cert store. If the certificate loading fails, we'll skip setting + * certificate, so the SSL socket will create & use a self-signed cert. */ -#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) - PJ_UNUSED_ARG(cert); + status = load_cert_from_store(pool, &cert); + if (status == PJ_SUCCESS) { + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } #else { pj_str_t ca_file = pj_str(CERT_CA_FILE); @@ -1311,11 +1351,18 @@ static int perf_test(unsigned clients, unsigned ms_handshake_timeout) } /* Set cert */ +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) /* Schannel backend currently can only load certificates from - * OS cert store. It will create a self-signed cert if none is specified. + * OS cert store. If the certificate loading fails, we'll skip setting + * certificate, so the SSL socket will create & use a self-signed cert. */ -#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_SCHANNEL) - PJ_UNUSED_ARG(cert); + status = load_cert_from_store(pool, &cert); + if (status == PJ_SUCCESS) { + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } #else { pj_str_t ca_file = pj_str(CERT_CA_FILE); From 2d55fbac5225138fc0c17260cc720c632691224d Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 18 Mar 2024 12:58:37 +0700 Subject: [PATCH 16/20] Update ci-win.yml Replace OpenSSL to Schannel in `windows-with-video-libvpx-unit-test-1` --- .github/workflows/ci-win.yml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index b6263e721c..7d37d4cfff 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -182,21 +182,6 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@master - - name: get openssl - run: Invoke-WebRequest -Uri "https://github.com/pjsip/third_party_libs/raw/main/openssl-1.1.1s-win.zip" -OutFile ".\openssl.zip" - shell: powershell - - name: expand openssl - run: | - Expand-Archive -LiteralPath .\openssl.zip -DestinationPath .; pwd - cd openssl_build - Add-Content ..\openssl_dir.txt $pwd.Path - shell: powershell - - name: check openssl folder - run: | - set /P OPENSSL_DIR= Date: Mon, 18 Mar 2024 13:08:59 +0700 Subject: [PATCH 17/20] Update ci-win.yml Disabled DTLS for Schannel test --- .github/workflows/ci-win.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index 7d37d4cfff..eac25f8a92 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -217,6 +217,7 @@ jobs: cd pjlib/include/pj; cp config_site_test.h config_site.h Add-Content config_site.h "#define PJ_HAS_SSL_SOCK 1" Add-Content config_site.h "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_SCHANNEL" + Add-Content config_site.h "#undef PJMEDIA_SRTP_HAS_DTLS" Add-Content config_site.h "#define PJMEDIA_HAS_VIDEO 1" Add-Content config_site.h "#define PJMEDIA_VIDEO_DEV_HAS_DSHOW 1" Add-Content config_site.h "#define PJMEDIA_HAS_LIBYUV 1" From 30a23a5b59588402f1c07fa4d6667f015108a55b Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 18 Mar 2024 13:55:29 +0700 Subject: [PATCH 18/20] Update SSL socket test: disable cert lookup by default. --- pjlib/src/pjlib-test/ssl_sock.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c index 89bbf1ba86..a4e8570fea 100644 --- a/pjlib/src/pjlib-test/ssl_sock.c +++ b/pjlib/src/pjlib-test/ssl_sock.c @@ -543,19 +543,34 @@ static pj_status_t load_cert_to_buf(pj_pool_t *pool, const pj_str_t *file_name, static pj_status_t load_cert_from_store(pj_pool_t *pool, pj_ssl_cert_t **p_cert) { +#if 0 + /* To test loading certificate from the store, follow these steps: + * 1. Install the certificate & the private-key pair to the store, + * and optionally set the friendly-name for it. + * 2. Update the lookup criteria below (field & keyword). + */ pj_ssl_cert_lookup_criteria crit = {0}; + /* Lookup by subject */ crit.type = PJ_SSL_CERT_LOOKUP_SUBJECT; pj_cstr(&crit.keyword, "test.pjsip.org"); + /* Lookup by friendly-name */ //crit.type = PJ_SSL_CERT_LOOKUP_FRIENDLY_NAME; //pj_cstr(&crit.keyword, "schannel-test"); + /* Lookup by fingerprint */ //crit.type = PJ_SSL_CERT_LOOKUP_FINGERPRINT; //pj_cstr(&crit.keyword, "\x08\x3a\x6c\xdc\xd0\x19\x59\xec\x28\xc3" // "\x81\xb8\xc0\x21\x09\xe9\xd5\xf6\x57\x7d"); return pj_ssl_cert_load_from_store(pool, &crit, p_cert); +#else + /* Set no certificate, Schannel will use self-signed cert */ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(p_cert); + return PJ_ENOTFOUND; +#endif } static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, From 43cc47af90a5954711cb1c693e8232b95f6fe902 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 18 Mar 2024 15:50:26 +0700 Subject: [PATCH 19/20] Update ci-win.yml insert 'schannel' into libvpx unit test --- .github/workflows/ci-win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index eac25f8a92..bff96d083c 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -178,7 +178,7 @@ jobs: msbuild pjproject-vs14.sln /p:PlatformToolset=v143 /p:Configuration=Release /p:Platform=win32 /p:UseEnv=true shell: cmd - windows-with-video-libvpx-unit-test-1: + windows-with-video-libvpx-schannel-unit-test-1: runs-on: windows-latest steps: - uses: actions/checkout@master From ae1c9ecb9b0e182db7daa1ae8cce51fdce6e6180 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Wed, 20 Mar 2024 10:24:10 +0700 Subject: [PATCH 20/20] Shorten docs of certificate lookup on various places (missing commit). --- pjnath/include/pjnath/turn_sock.h | 9 ++------- pjsip/include/pjsip/sip_transport_tls.h | 9 ++------- pjsip/include/pjsua2/siptypes.hpp | 15 +++++++-------- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/pjnath/include/pjnath/turn_sock.h b/pjnath/include/pjnath/turn_sock.h index 5de2f39bbe..4d7b6029a3 100644 --- a/pjnath/include/pjnath/turn_sock.h +++ b/pjnath/include/pjnath/turn_sock.h @@ -226,13 +226,8 @@ typedef struct pj_turn_sock_tls_cfg /** * Lookup certificate from OS certificate store with specified criteria. * - * Currently only TLS backend Windows Schannel support this and this - * backend only support this type of certificate settings (settings via - * files or buffers are not supported). The lookup will be performed in - * the Current User store, if not found, it will try Local Machine store. - * Note that in manual verification (e.g: when verify_server is disabled), - * the backend will provide pre-verification result against trusted - * CA certificates in Current User store. + * Currently only used by TLS backend Windows Schannel, please check + * pj_ssl_cert_load_from_store() for more info. */ pj_ssl_cert_lookup_criteria cert_lookup; diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index 8febf392a4..ce9aab95ca 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -191,13 +191,8 @@ typedef struct pjsip_tls_setting /** * Lookup certificate from OS certificate store with specified criteria. * - * Currently only TLS backend Windows Schannel support this and this - * backend only support this type of certificate settings (settings via - * files or buffers are not supported). The lookup will be performed in - * the Current User store, if not found, it will try Local Machine store. - * Note that in manual verification (e.g: when verify_server is disabled), - * the backend will provide pre-verification result against trusted - * CA certificates in Current User store. + * Currently only used by TLS backend Windows Schannel, please check + * pj_ssl_cert_load_from_store() for more info. */ pj_ssl_cert_lookup_criteria cert_lookup; diff --git a/pjsip/include/pjsua2/siptypes.hpp b/pjsip/include/pjsua2/siptypes.hpp index 9a7a03af50..5838b598d4 100644 --- a/pjsip/include/pjsua2/siptypes.hpp +++ b/pjsip/include/pjsua2/siptypes.hpp @@ -178,19 +178,18 @@ struct TlsConfig : public PersistentObject * Lookup certificate from OS certificate store, this setting will * specify the field type to lookup. * - * Currently only TLS backend Windows Schannel support this and this - * backend only support this type of certificate settings (settings via - * files or buffers are not supported). The lookup will be performed in - * the Current User store, if not found, it will try Local Machine store. - * Note that in manual verification (e.g: when verifyServer is disabled), - * the backend will provide pre-verification result against trusted - * CA certificates in Current User store. + * Currently only used by Windows Schannel backend, see also + * \a pj_ssl_cert_load_from_store() for more info. */ pj_ssl_cert_lookup_type certLookupType; /** * Lookup certificate from OS certificate store, this setting will - * specify the keyword to lookup. + * specify the keyword to match on the field specified in + * \a certLookupType above. + * + * Currently only used by Windows Schannel backend, see also + * \a pj_ssl_cert_load_from_store() for more info. */ string certLookupKeyword;