Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TLS/SSL backend: Windows Schannel #3867

Merged
merged 22 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e7e4ace
Initial version (Working: connect, send/recv. Not working: param sett…
nanangizz Feb 15, 2024
bb5853a
Add getting cipher & cert info
nanangizz Feb 15, 2024
e57d4a8
Mem management: use preallocated send buffer instead of relying on sy…
nanangizz Feb 16, 2024
8cafc33
Implement TLS certificate setting: read cert from OS cert store.
nanangizz Feb 16, 2024
3245fed
Enable TLS protocol version setting, handling error code mapping & pr…
nanangizz Feb 19, 2024
0095be7
Add manual verification result, fix bug in setting up TLS protocol ve…
nanangizz Feb 20, 2024
68e5650
Minors: add comments about pj_status_t & SECURITY_STATUS mapping, cos…
nanangizz Feb 21, 2024
76fe133
Update certificate settings, fix setting TLS protocol version
nanangizz Feb 23, 2024
aed522b
Fix compile error on non-Windows
nanangizz Feb 23, 2024
7e87d3c
Slight modifications in TLS cert settings in PJSIP TLS transport & PJ…
nanangizz Feb 23, 2024
fcb55f5
Retry using SCHANNEL_CRED when using SCH_CREDENTIALS fails.
nanangizz Feb 23, 2024
5b31f80
Implement TLS renego (experimental).
nanangizz Mar 7, 2024
8fec190
Update PJLIB SSL socket test for Schannel backend.
nanangizz Mar 14, 2024
f044db6
Add ssl_sock_schannel.c to PJLIB project (vs14)
nanangizz Mar 14, 2024
51379b9
Modifications based on comments
nanangizz Mar 15, 2024
2d55fba
Update ci-win.yml
nanangizz Mar 18, 2024
9f50c22
Update ci-win.yml
nanangizz Mar 18, 2024
30a23a5
Update SSL socket test: disable cert lookup by default.
nanangizz Mar 18, 2024
e9d470b
Merge branch 'schannel' of /~https://github.com/pjsip/pjproject into sc…
nanangizz Mar 18, 2024
43cc47a
Update ci-win.yml
nanangizz Mar 18, 2024
ae1c9ec
Shorten docs of certificate lookup on various places (missing commit).
nanangizz Mar 20, 2024
d9e2552
Merge branch 'schannel' of /~https://github.com/pjsip/pjproject into sc…
nanangizz Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pjlib/build/pjlib.vcxproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug-Dynamic|ARM">
Expand Down Expand Up @@ -998,6 +998,7 @@
</ClCompile>
<ClCompile Include="..\src\pj\ssl_sock_ossl.c" />
<ClCompile Include="..\src\pj\ssl_sock_gtls.c" />
<ClCompile Include="..\src\pj\ssl_sock_schannel.c" />
<ClCompile Include="..\src\pj\string.c" />
<ClCompile Include="..\src\pj\symbols.c">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-Dynamic|Win32'">true</ExcludedFromBuild>
Expand Down
3 changes: 3 additions & 0 deletions pjlib/include/pj/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
77 changes: 77 additions & 0 deletions pjlib/include/pj/ssl_sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -145,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.
*/
Expand Down Expand Up @@ -273,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this can potentially be used for iOS and Android in the future, perhaps have a quick look at their key stores as well, to make sure that our API and struct are compatible for future extension (in other words, so that we don't need to create a new API such as pj_ssl_cert_load_from_store2()).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, had a thought about this a bit, that's why pj_ssl_cert_load_from_store() uses an 'expandable' param pj_ssl_cert_lookup_criteria for future use.

Nevertheless, tried to do a bit research for Android. From here, it seems that a cert (& its private key) cannot be extracted out to be used by third-party security lib (such as OpenSSL), so the keystore seems to be for native SSL backend only. And from here, it looks like accessing cert in the keystore system is done via UI prompt dialog for the user selecting the cert to use, then application can assign an "alias" (perhaps some kind of "friendly-name" on Windows) for the cert for future use (so app don't need to show the UI prompt dialog again). So if in the future we have a native Android SSL backend, perhaps pj_ssl_cert_load_from_store() can be used to lookup TLS cert using the "alias". The pj_ssl_cert_lookup_criteria & pj_ssl_cert_load_from_store() may not be able to handle any access auth though, which can be password, PIN, biometrics, etc.

*
* @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.
*
Expand Down
6 changes: 4 additions & 2 deletions pjlib/src/pj/activesock.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()."));
Expand Down Expand Up @@ -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()."));
Expand Down
4 changes: 4 additions & 0 deletions pjlib/src/pj/ssl_sock_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
119 changes: 106 additions & 13 deletions pjlib/src/pj/ssl_sock_imp_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -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);

Expand All @@ -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)
{
Expand All @@ -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",
Expand All @@ -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;
Expand Down Expand Up @@ -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-
Expand Down Expand Up @@ -2241,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);
Expand All @@ -2251,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
}
}

Expand All @@ -2274,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 &&
Expand All @@ -2294,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,
Expand All @@ -2303,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);
Expand All @@ -2316,8 +2367,44 @@ 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(criteria);
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,
Expand All @@ -2330,6 +2417,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);
Expand All @@ -2339,6 +2428,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_;

Expand Down
Loading
Loading