{"schema":"libjg2-1",
"vpath":"/git/",
"avatar":"/git/avatar/",
"alang":"",
"gen_ut":1766021946,
"reponame":"libwebsockets",
"desc":"libwebsockets lightweight C networking library",
"owner": { "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },"url":"https://libwebsockets.org/repo/libwebsockets",
"f":3,
"items": [
{"schema":"libjg2-1",
"cid":"ee8a51d2144dab2d4b5a48f468a6f94b",
"commit": {"type":"commit",
"time": 1765876544,
"time_ofs": 0,
"oid_tree": { "oid": "55b7fff6f7e7c054d1e5c19bb1585c60bcf58c7d", "alias": []},
"oid":{ "oid": "5acb375a17b2d1707b94765db2f58a6317e23c94", "alias": [ "refs/heads/main"]},
"msg": "tls: schannel tls phase 3",
"sig_commit": { "git_time": { "time": 1765876544, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },
"sig_author": { "git_time": { "time": 1765739953, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }},
"body": "tls: schannel tls phase 3\n\nTesting the client side tls stuff, this works for lws-minimal-http-client,\nand the -multi variant of that too.\n\nTesting the server side tls stuff, this works for lws-minimal-http-server,\nand lws-minimal-http-server-tls as well, for RSA and EC certs created by\nopenssl at least.\n\nCo-developed-by: Gemini 3.0 Pro\n"
,
"diff": "diff --git a/README.md b/README.md\nindex c466365..9f782a5 100644\n--- a/README.md\n+++ b/README.md\n@@ -2,6 +2,8 @@\n \n # Libwebsockets\n \n+** NEW on main: Windows tls + gencrypto apis without OpenSSL: -DLWS_WITH_SCHANNEL\u003d1 **\n+\n ** v4.5 is released, you can follow it on v4.5-stable **\n \n Libwebsockets is a simple-to-use, MIT-license, pure C library providing client and server\n@@ -145,7 +147,7 @@ In 2025, writing actual code with AI is quite scary while at the same time offer\n forward for the thankless and lonely task of maintaining FOSS code. I have been using Google's\n Gemini 2.5 and now 3.0, while it's very good at looking at the code and what I am asking\n and producing something sane (much better than a year ago or self-hosted generic models),\n-it falls down badly on being able to complete the scope of the patch that it figured out it\n+it can fall down badly on being able to complete the scope of the patch that it figured out it\n wants to do, and simply stops too early and drops the rest on the floor.\n \n It deserves praise for being able to work with quite complicated apis in lws like `lws_struct`\n@@ -159,7 +161,7 @@ It's also suffering from being strong with its mental model of what's going on a\n change does, but very weak when it has to be told that its patch doesn't do what it expected.\n Where a human would 'trap' the difference between its mental model and reality so they can\n see where the model broke, they will often avoid adding logging and instead go down very\n-unlikely rabbit holes for hours.\n+unlikely rabbit holes for hours. (Gemini 3.0 has gotten better at this).\n \n At the same time, it knows that maintainability and security are supposed to be desirable\n traits. But it knows it in the same way it knows layered patches are desirable, it can't\ndiff --git a/READMEs/README.build-windows.md b/READMEs/README.build-windows.md\nindex 4d91472..9800d0c 100644\n--- a/READMEs/README.build-windows.md\n+++ b/READMEs/README.build-windows.md\n@@ -1,4 +1,16 @@\n-# Some notes for the windows jungle\n+# Building with schannel / bcrypt on Windows\n+\n+Windows has some built in crypto library functions which lws now knows how to\n+use, with `-DLWS_WITH_SCHANNEL\u003d1`\n+\n+This means you don't need openssl on windows and can build it out of the box\n+on windows.\n+\n+lws generic crypto stuff is supported, as is tls client and tls server\n+as normal. lws brings in trusted CAs using windows' certificate store,\n+but for serving, it uses cert and key from files as does lws on unix.\n+\n+# Some notes for building openssl on windows\n \n This was how I compiled libwebsockets starting from a blank windows install\n in June 2025. Doing this on a linux distro is way simpler and quicker\ndiff --git a/lib/core/context.c b/lib/core/context.c\nindex 6e914c1..7b805e5 100644\n--- a/lib/core/context.c\n+++ b/lib/core/context.c\n@@ -856,8 +856,16 @@ lws_create_context(const struct lws_context_creation_info *info)\n \t\tlwsl_cx_notice(context, \u0022LWS: %s, MbedTLS-%s %s%s\u0022, library_version, mbedtls_version, opts_str, s);\n \t}\n #else\n+#if defined(LWS_WITH_SCHANNEL)\n+\tlwsl_cx_notice(context, \u0022LWS: %s, SChannel, %s%s\u0022, library_version, opts_str, s);\n+#else\n+#if defined(LWS_WITH_SSL)\n+\tlwsl_cx_notice(context, \u0022LWS: %s, OpenSSL, %s%s\u0022, library_version, opts_str, s);\n+#else\n \tlwsl_cx_notice(context, \u0022LWS: %s, %s%s\u0022, library_version, opts_str, s);\n #endif\n+#endif\n+#endif\n \n #if defined(LWS_WITH_NETWORK)\n \tlwsl_cx_info(context, \u0022Event loop: %s\u0022, plev-\u003eops-\u003ename);\ndiff --git a/lib/misc/base64-decode.c b/lib/misc/base64-decode.c\nindex c67029e..84f2dd6 100644\n--- a/lib/misc/base64-decode.c\n+++ b/lib/misc/base64-decode.c\n@@ -124,7 +124,7 @@ lws_b64_decode_stateful(struct lws_b64state *s, const char *in, size_t *in_len,\n \t\t\twhile (in \u003c end_in \u0026\u0026 *in \u0026\u0026 !v) {\n \t\t\t\ts-\u003ec \u003d v \u003d (unsigned char)*in++;\n \n-\t\t\t\tif (v \u003d\u003d '\u005cx0a') {\n+\t\t\t\tif (v \u003d\u003d '\u005cx0a' || v \u003d\u003d '\u005cx0d') {\n \t\t\t\t\tv \u003d 0;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\ndiff --git a/lib/tls/CMakeLists.txt b/lib/tls/CMakeLists.txt\nindex 6fd4e6c..3357674 100644\n--- a/lib/tls/CMakeLists.txt\n+++ b/lib/tls/CMakeLists.txt\n@@ -161,7 +161,7 @@ if (LWS_WITH_SSL)\n \t\t\ttls/schannel/schannel-ssl.c\n \t\t\ttls/schannel/schannel-session.c\n \t\t\ttls/schannel/schannel-x509.c)\n-\t\tlist(APPEND LIB_LIST \u0022ncrypt\u0022 \u0022bcrypt\u0022 \u0022crypt32\u0022)\n+\t\tlist(APPEND LIB_LIST \u0022ncrypt\u0022 \u0022bcrypt\u0022 \u0022crypt32\u0022 \u0022secur32\u0022)\n \t\tif (LWS_WITH_GENCRYPTO)\n \t\t\tlist(APPEND SOURCES\n \t\t\t\ttls/schannel/lws-genhash.c\ndiff --git a/lib/tls/schannel/private.h b/lib/tls/schannel/private.h\nnew file mode 100644\nindex 0000000..c152e15\n--- /dev/null\n+++ b/lib/tls/schannel/private.h\n@@ -0,0 +1,63 @@\n+#ifndef _LWS_TLS_SCHANNEL_PRIVATE_H_\n+#define _LWS_TLS_SCHANNEL_PRIVATE_H_\n+\n+#ifndef SECURITY_WIN32\n+#define SECURITY_WIN32\n+#endif\n+#include \u003csecurity.h\u003e\n+#include \u003cschannel.h\u003e\n+#include \u003cbcrypt.h\u003e\n+#include \u003cncrypt.h\u003e\n+\n+struct lws_tls_schannel_ctx {\n+\tCredHandle cred;\n+\tHCERTSTORE store;\n+ union {\n+ HCRYPTPROV key_prov; /* CAPI */\n+ NCRYPT_KEY_HANDLE key_cng; /* CNG */\n+ } u;\n+ int key_type; /* 0 \u003d CAPI, 1 \u003d CNG */\n+\tchar key_container_name[64];\n+\tint initialized;\n+};\n+\n+struct lws_tls_schannel_conn {\n+\tCtxtHandle ctxt;\n+\tSecPkgContext_StreamSizes stream_sizes;\n+\n+\t/* Buffers for partial data */\n+\tuint8_t *rx_buf;\n+\tsize_t rx_len; /* Data currently in rx_buf */\n+\tsize_t rx_alloc; /* Total allocated size of rx_buf */\n+\n+\tuint8_t *tx_buf; /* Pending data to be written to socket (e.g. handshake tokens OR encrypted app data) */\n+\tsize_t tx_len;\n+\tsize_t tx_pos; /* How much we have written so far */\n+\n+\t/* Buffer for decrypted data pending read by user */\n+ struct lws_buflist *decrypted_list;\n+\n+\tint f_context_init; /* 1 if context initialized (handshake started) */\n+\tint f_handshake_finished; /* 1 if handshake complete */\n+\tint f_allow_self_signed;\n+\tint f_socket_is_blocking; /* 1 if recv returned EWOULDBLOCK, so rx_buf might be incomplete */\n+\n+\tchar alpn[64];\n+ char hostname[128];\n+};\n+\n+struct lws_tls_schannel_x509 {\n+\tPCCERT_CONTEXT cert;\n+};\n+\n+/* Certificate loader prototype */\n+int\n+lws_tls_schannel_cert_info_load(struct lws_context *context,\n+ const char *cert, const char *private_key,\n+ const char *mem_cert, size_t len_mem_cert,\n+ const char *mem_privkey, size_t mem_privkey_len,\n+ PCCERT_CONTEXT *pcert, HCERTSTORE *phStore,\n+ void **phKey, int *pKeyType,\n+ const char *container_name);\n+\n+#endif\ndiff --git a/lib/tls/schannel/schannel-ssl.c b/lib/tls/schannel/schannel-ssl.c\nindex 8f92d09..e8cd5b0 100644\n--- a/lib/tls/schannel/schannel-ssl.c\n+++ b/lib/tls/schannel/schannel-ssl.c\n@@ -24,102 +24,837 @@\n \n #include \u0022private-lib-core.h\u0022\n #include \u0022private-lib-tls.h\u0022\n+#include \u0022private.h\u0022\n \n-int\n-lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n+/* Helper to manage SChannel buffers */\n+\tstatic int\n+lws_tls_schannel_realloc_buffer(struct lws_tls_schannel_conn *conn, size_t new_size)\n {\n-\t/* stub */\n-\treturn -1;\n-}\n+\tif (new_size \u003c\u003d conn-\u003erx_alloc)\n+\t\treturn 0;\n \n-int\n-lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n-{\n-\t/* stub */\n-\treturn -1;\n-}\n+\tuint8_t *new_buf \u003d lws_realloc(conn-\u003erx_buf, new_size, \u0022schannel_rx\u0022);\n+\tif (!new_buf)\n+\t\treturn 1;\n \n-int\n-lws_ssl_pending(struct lws *wsi)\n-{\n-\t/* stub */\n+\tconn-\u003erx_buf \u003d new_buf;\n+\tconn-\u003erx_alloc \u003d new_size;\n \treturn 0;\n }\n \n-int\n-lws_ssl_close(struct lws *wsi)\n-{\n-\t/* stub */\n-\treturn 0;\n-}\n-\n-int\n+\tint\n lws_ssl_client_bio_create(struct lws *wsi)\n {\n-\t/* stub */\n+\tstruct lws_tls_schannel_conn *conn;\n+\tchar hostname[128], *p;\n+\n+\tconn \u003d lws_zalloc(sizeof(*conn), \u0022schannel_conn\u0022);\n+\tif (!conn) return -1;\n+\n+\twsi-\u003etls.ssl \u003d conn;\n+\n+\tif (wsi-\u003estash) {\n+\t\tlws_strncpy(hostname, wsi-\u003estash-\u003ecis[CIS_HOST], sizeof(hostname));\n+\t} else {\n+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n+\t\tif (lws_hdr_copy(wsi, hostname, sizeof(hostname),\n+\t\t\t\t\t_WSI_TOKEN_CLIENT_HOST) \u003c\u003d 0)\n+#endif\n+\t\t{\n+\t\t\tlwsl_err(\u0022%s: Unable to get hostname\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\t/* Handle port stripping */\n+\tp \u003d hostname;\n+\twhile (*p) {\n+\t\tif (*p \u003d\u003d ':') {\n+\t\t\t*p \u003d '\u005c0';\n+\t\t\tbreak;\n+\t\t}\n+\t\tp++;\n+\t}\n+\n+\tlws_strncpy(conn-\u003ehostname, hostname, sizeof(conn-\u003ehostname));\n+\n+\t/* ALPN */\n+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n+\tif (wsi-\u003ea.vhost-\u003etls.alpn)\n+\t\tlws_strncpy(conn-\u003ealpn, wsi-\u003ea.vhost-\u003etls.alpn, sizeof(conn-\u003ealpn));\n+#endif\n+\n+\tif (wsi-\u003etls.use_ssl \u0026 LCCSCF_ALLOW_SELFSIGNED)\n+\t\tconn-\u003ef_allow_self_signed \u003d 1;\n+\n \treturn 0;\n }\n \n-void\n-lws_ssl_bind_passphrase(lws_tls_ctx *ssl_ctx, int is_client,\n-\t\t\tconst struct lws_context_creation_info *info)\n+\tenum lws_ssl_capable_status\n+lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t len)\n {\n-\t/* stub */\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\tstruct lws_tls_schannel_ctx *ctx \u003d wsi-\u003ea.vhost-\u003etls.ssl_client_ctx;\n+\tSecBufferDesc out_desc, in_desc;\n+\tSecBuffer out_buf[1], in_buf[2];\n+\tULONG req_attrs, ret_attrs;\n+\tSECURITY_STATUS status;\n+\tssize_t n;\n+\n+\tif (!ctx || !conn)\n+\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\n+\treq_attrs \u003d ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |\n+\t\t ISC_REQ_CONFIDENTIALITY | ISC_REQ_STREAM |\n+\t\t ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_MANUAL_CRED_VALIDATION |\n+\t\t ISC_REQ_USE_SUPPLIED_CREDS;\n+\n+\tif (conn-\u003ef_handshake_finished)\n+\t\treturn LWS_SSL_CAPABLE_DONE;\n+\n+\t/* If we have pending output from previous step, try to send it */\n+\tif (conn-\u003etx_buf \u0026\u0026 conn-\u003etx_pos \u003c conn-\u003etx_len) {\n+\t\tn \u003d send(wsi-\u003edesc.sockfd, (char *)conn-\u003etx_buf + conn-\u003etx_pos, (int)(conn-\u003etx_len - conn-\u003etx_pos), 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003etx_pos +\u003d n;\n+\t\tif (conn-\u003etx_pos \u003c conn-\u003etx_len)\n+\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\n+\t\tlws_free_set_NULL(conn-\u003etx_buf);\n+\t\tconn-\u003etx_len \u003d 0;\n+\t\tconn-\u003etx_pos \u003d 0;\n+\t}\n+\n+\tif (!conn-\u003ef_context_init) {\n+\t\t/* Initial call */\n+\t\tSecBuffer in_bufs[1];\n+\t\tSecBufferDesc in_desc_initial;\n+\t\tuint8_t alpn_buf[256];\n+\n+\t\tout_buf[0].BufferType \u003d SECBUFFER_TOKEN;\n+\t\tout_buf[0].cbBuffer \u003d 0;\n+\t\tout_buf[0].pvBuffer \u003d NULL;\n+\t\tout_desc.cBuffers \u003d 1;\n+\t\tout_desc.pBuffers \u003d out_buf;\n+\t\tout_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\t\tin_desc_initial.cBuffers \u003d 0;\n+\t\tin_desc_initial.pBuffers \u003d NULL;\n+\t\tin_desc_initial.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\t\t/* ALPN */\n+\t\tif (conn-\u003ealpn[0]) {\n+\t\t\t/* Construct APPLICATION_PROTOCOLS buffer */\n+\t\t\t/* Structure: SecApplicationProtocolNegotiationExt_ALPN */\n+\t\t\t/* unsigned long Status;\n+\t\t\t unsigned long ProtoIdType;\n+\t\t\t unsigned long ProtocolListSize;\n+\t\t\t unsigned char ProtocolList[ANYSIZE_ARRAY];\n+\t\t\t */\n+\n+\t\t\tuint32_t status \u003d 0;\n+\t\t\tuint32_t proto_id_type \u003d 1; /* ALPN */\n+\t\t\tuint32_t list_size \u003d 0;\n+\n+\t\t\tuint8_t *pData \u003d alpn_buf + 12; /* Skip 12 bytes header */\n+\n+\t\t\t/* Parse comma separated list */\n+\t\t\tchar temp[64];\n+\t\t\tlws_strncpy(temp, conn-\u003ealpn, sizeof(temp));\n+\t\t\tchar *p \u003d temp;\n+\t\t\tchar *end \u003d p + strlen(p);\n+\n+\t\t\twhile (p \u003c end) {\n+\t\t\t\tchar *comma \u003d strchr(p, ',');\n+\t\t\t\tsize_t item_len;\n+\t\t\t\tif (comma) item_len \u003d comma - p;\n+\t\t\t\telse item_len \u003d strlen(p);\n+\n+\t\t\t\tif (item_len \u003e 0 \u0026\u0026 item_len \u003c 256) {\n+\t\t\t\t\tif (pData + 1 + item_len \u003e alpn_buf + sizeof(alpn_buf)) break;\n+\t\t\t\t\t*pData++ \u003d (uint8_t)item_len;\n+\t\t\t\t\tmemcpy(pData, p, item_len);\n+\t\t\t\t\tpData +\u003d item_len;\n+\t\t\t\t}\n+\n+\t\t\t\tif (comma) p \u003d comma + 1;\n+\t\t\t\telse break;\n+\t\t\t}\n+\n+\t\t\tlist_size \u003d (uint32_t)(pData - (alpn_buf + 12));\n+\n+\t\t\tmemcpy(alpn_buf, \u0026status, 4);\n+\t\t\tmemcpy(alpn_buf + 4, \u0026proto_id_type, 4);\n+\t\t\tmemcpy(alpn_buf + 8, \u0026list_size, 4);\n+\n+\t\t\tin_bufs[0].BufferType \u003d SECBUFFER_APPLICATION_PROTOCOLS;\n+\t\t\tin_bufs[0].pvBuffer \u003d alpn_buf;\n+\t\t\tin_bufs[0].cbBuffer \u003d (unsigned long)(pData - alpn_buf);\n+\n+\t\t\tin_desc_initial.cBuffers \u003d 1;\n+\t\t\tin_desc_initial.pBuffers \u003d in_bufs;\n+\t\t}\n+\n+\t\tstatus \u003d InitializeSecurityContextA(\u0026ctx-\u003ecred, NULL, conn-\u003ehostname, req_attrs, 0, 0,\n+\t\t\t\t(in_desc_initial.cBuffers \u003e 0) ? \u0026in_desc_initial : NULL,\n+\t\t\t\t0, \u0026conn-\u003ectxt, \u0026out_desc, \u0026ret_attrs, NULL);\n+\n+\t\tconn-\u003ef_context_init \u003d 1;\n+\t} else {\n+\t\t/* Continuation */\n+\t\tif (conn-\u003erx_len \u003d\u003d 0) {\n+\t\t\tif (conn-\u003erx_alloc \u003c 4096) lws_tls_schannel_realloc_buffer(conn, 4096);\n+\n+\t\t\tn \u003d recv(wsi-\u003edesc.sockfd, (char *)conn-\u003erx_buf, (int)conn-\u003erx_alloc, 0);\n+\t\t\tif (n \u003c 0) {\n+\t\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t\t} else if (n \u003d\u003d 0) {\n+\t\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t\t}\n+\t\t\tconn-\u003erx_len \u003d n;\n+\t\t}\n+\n+\t\tin_buf[0].BufferType \u003d SECBUFFER_TOKEN;\n+\t\tin_buf[0].pvBuffer \u003d conn-\u003erx_buf;\n+\t\tin_buf[0].cbBuffer \u003d (unsigned long)conn-\u003erx_len;\n+\t\tin_buf[1].BufferType \u003d SECBUFFER_EMPTY;\n+\t\tin_buf[1].pvBuffer \u003d NULL;\n+\t\tin_buf[1].cbBuffer \u003d 0;\n+\t\tin_desc.cBuffers \u003d 2;\n+\t\tin_desc.pBuffers \u003d in_buf;\n+\t\tin_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\t\tout_buf[0].BufferType \u003d SECBUFFER_TOKEN;\n+\t\tout_buf[0].cbBuffer \u003d 0;\n+\t\tout_buf[0].pvBuffer \u003d NULL;\n+\t\tout_desc.cBuffers \u003d 1;\n+\t\tout_desc.pBuffers \u003d out_buf;\n+\t\tout_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\t\tstatus \u003d InitializeSecurityContextA(\u0026ctx-\u003ecred, \u0026conn-\u003ectxt, conn-\u003ehostname, req_attrs, 0, 0,\n+\t\t\t\t\u0026in_desc, 0, NULL, \u0026out_desc, \u0026ret_attrs, NULL);\n+\n+\t}\n+\n+\tif (status \u003d\u003d SEC_E_INCOMPLETE_MESSAGE) {\n+\t\tif (conn-\u003erx_len \u003d\u003d conn-\u003erx_alloc) {\n+\t\t\tif (lws_tls_schannel_realloc_buffer(conn, conn-\u003erx_alloc + 2048))\n+\t\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\n+\t\tn \u003d recv(wsi-\u003edesc.sockfd, (char *)conn-\u003erx_buf + conn-\u003erx_len, (int)(conn-\u003erx_alloc - conn-\u003erx_len), 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t} else if (n \u003d\u003d 0) {\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003erx_len +\u003d n;\n+\t\treturn lws_tls_client_connect(wsi, errbuf, len);\n+\t}\n+\n+\tif (status \u003d\u003d SEC_I_CONTINUE_NEEDED || status \u003d\u003d SEC_E_OK) {\n+\t\tif (out_buf[0].cbBuffer \u003e 0 \u0026\u0026 out_buf[0].pvBuffer) {\n+\t\t\tconn-\u003etx_buf \u003d lws_malloc(out_buf[0].cbBuffer, \u0022schannel_tx\u0022);\n+\t\t\tif (!conn-\u003etx_buf) {\n+\t\t\t\tFreeContextBuffer(out_buf[0].pvBuffer);\n+\t\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t\t}\n+\t\t\tmemcpy(conn-\u003etx_buf, out_buf[0].pvBuffer, out_buf[0].cbBuffer);\n+\t\t\tconn-\u003etx_len \u003d out_buf[0].cbBuffer;\n+\t\t\tconn-\u003etx_pos \u003d 0;\n+\t\t\tFreeContextBuffer(out_buf[0].pvBuffer);\n+\n+\t\t\tn \u003d send(wsi-\u003edesc.sockfd, (char *)conn-\u003etx_buf, (int)conn-\u003etx_len, 0);\n+\t\t\tif (n \u003c 0) {\n+\t\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\t\t} else {\n+\t\t\t\tconn-\u003etx_pos +\u003d n;\n+\t\t\t\tif (conn-\u003etx_pos \u003d\u003d conn-\u003etx_len) {\n+\t\t\t\t\tlws_free_set_NULL(conn-\u003etx_buf);\n+\t\t\t\t\tconn-\u003etx_len \u003d 0;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (in_buf[1].BufferType \u003d\u003d SECBUFFER_EXTRA \u0026\u0026 in_buf[1].cbBuffer \u003e 0) {\n+\t\t\tmemmove(conn-\u003erx_buf, (uint8_t*)conn-\u003erx_buf + (conn-\u003erx_len - in_buf[1].cbBuffer), in_buf[1].cbBuffer);\n+\t\t\tconn-\u003erx_len \u003d in_buf[1].cbBuffer;\n+\t\t} else {\n+\t\t\tconn-\u003erx_len \u003d 0;\n+\t\t}\n+\n+\t\tif (status \u003d\u003d SEC_E_OK) {\n+\t\t\tconn-\u003ef_handshake_finished \u003d 1;\n+\t\t\tQueryContextAttributes(\u0026conn-\u003ectxt, SECPKG_ATTR_STREAM_SIZES, \u0026conn-\u003estream_sizes);\n+\n+\t\t\t/* Check ALPN Negotiation Result */\n+\t\t\tSecPkgContext_ApplicationProtocol alpn_result;\n+\t\t\tif (QueryContextAttributes(\u0026conn-\u003ectxt, SECPKG_ATTR_APPLICATION_PROTOCOL, \u0026alpn_result) \u003d\u003d SEC_E_OK) {\n+\t\t\t\tif (alpn_result.ProtoNegoStatus \u003d\u003d SecApplicationProtocolNegotiationStatus_Success) {\n+\t\t\t\t\t/* Inform LWS about negotiated protocol */\n+\t\t\t\t\tchar negotiated[64];\n+\t\t\t\t\tif (alpn_result.ProtocolIdSize \u003c sizeof(negotiated)) {\n+\t\t\t\t\t\tmemcpy(negotiated, alpn_result.ProtocolId, alpn_result.ProtocolIdSize);\n+\t\t\t\t\t\tnegotiated[alpn_result.ProtocolIdSize] \u003d 0;\n+\t\t\t\t\t\tlws_role_call_alpn_negotiated(wsi, negotiated);\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\treturn LWS_SSL_CAPABLE_DONE;\n+\t\t}\n+\n+\t\tif (conn-\u003etx_buf)\n+\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\n+\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t}\n+\n+\tlwsl_err(\u0022%s: InitializeSecurityContext failed 0x%x\u005cn\u0022, __func__, (int)status);\n+\treturn LWS_SSL_CAPABLE_ERROR;\n }\n \n-int\n+\tint\n lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd)\n {\n-\t/* stub */\n+\tstruct lws_tls_schannel_conn *conn;\n+\tconn \u003d lws_zalloc(sizeof(*conn), \u0022schannel_conn_srv\u0022);\n+\tif (!conn) return 1;\n+\twsi-\u003etls.ssl \u003d conn;\n \treturn 0;\n }\n \n enum lws_ssl_capable_status\n lws_tls_server_accept(struct lws *wsi)\n {\n-\t/* stub */\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\tstruct lws_tls_schannel_ctx *ctx \u003d wsi-\u003ea.vhost-\u003etls.ssl_ctx;\n+\tSecBufferDesc out_desc, in_desc;\n+\tSecBuffer out_buf[1], in_buf[2];\n+\tULONG req_attrs, ret_attrs;\n+\tSECURITY_STATUS status;\n+\tssize_t n;\n+\n+ if (!ctx || !conn) {\n+ lwsl_wsi_err(wsi, \u0022ctx %p (vhost %s) conn %p missing\u005cn\u0022, ctx, wsi-\u003ea.vhost-\u003ename, conn);\n+ return LWS_SSL_CAPABLE_ERROR;\n+ }\n+\n+\tif (conn-\u003ef_handshake_finished)\n+\t\treturn LWS_SSL_CAPABLE_DONE;\n+\n+\tif (conn-\u003etx_buf \u0026\u0026 conn-\u003etx_pos \u003c conn-\u003etx_len) {\n+\t\tn \u003d send(wsi-\u003edesc.sockfd, (char *)conn-\u003etx_buf + conn-\u003etx_pos, (int)(conn-\u003etx_len - conn-\u003etx_pos), 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003etx_pos +\u003d n;\n+\t\tif (conn-\u003etx_pos \u003c conn-\u003etx_len)\n+\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\tlws_free_set_NULL(conn-\u003etx_buf);\n+\t\tconn-\u003etx_len \u003d 0;\n+\t}\n+\n+\treq_attrs \u003d ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |\n+\t\tISC_REQ_CONFIDENTIALITY | ISC_REQ_STREAM |\n+\t\tISC_REQ_ALLOCATE_MEMORY;\n+\n+\tif (conn-\u003erx_len \u003d\u003d 0) {\n+\t\tif (conn-\u003erx_alloc \u003c 4096) lws_tls_schannel_realloc_buffer(conn, 4096);\n+\t\tn \u003d recv(wsi-\u003edesc.sockfd, (char *)conn-\u003erx_buf, (int)conn-\u003erx_alloc, 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+ lwsl_err(\u0022%s: recv failed %d\u005cn\u0022, __func__, LWS_ERRNO);\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t} else if (n \u003d\u003d 0) {\n+ lwsl_err(\u0022%s: recv 0 (EOF)\u005cn\u0022, __func__);\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003erx_len \u003d n;\n+ lwsl_info(\u0022%s: recv %d bytes client hello\u005cn\u0022, __func__, (int)n);\n+\t}\n+\n+\tin_buf[0].BufferType \u003d SECBUFFER_TOKEN;\n+\tin_buf[0].pvBuffer \u003d conn-\u003erx_buf;\n+\tin_buf[0].cbBuffer \u003d (unsigned long)conn-\u003erx_len;\n+\tin_buf[1].BufferType \u003d SECBUFFER_EMPTY;\n+\tin_buf[1].pvBuffer \u003d NULL;\n+\tin_buf[1].cbBuffer \u003d 0;\n+\tin_desc.cBuffers \u003d 2;\n+\tin_desc.pBuffers \u003d in_buf;\n+\tin_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\tout_buf[0].BufferType \u003d SECBUFFER_TOKEN;\n+\tout_buf[0].cbBuffer \u003d 0;\n+\tout_buf[0].pvBuffer \u003d NULL;\n+\tout_desc.cBuffers \u003d 1;\n+\tout_desc.pBuffers \u003d out_buf;\n+\tout_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\tstatus \u003d AcceptSecurityContext(\u0026ctx-\u003ecred, conn-\u003ef_context_init ? \u0026conn-\u003ectxt : NULL,\n+\t\t\t\u0026in_desc, req_attrs, 0, \u0026conn-\u003ectxt,\n+\t\t\t\u0026out_desc, \u0026ret_attrs, NULL);\n+\tconn-\u003ef_context_init \u003d 1;\n+\n+ lwsl_info(\u0022%s: AcceptSecurityContext status 0x%x\u005cn\u0022, __func__, (int)status);\n+\n+\tif (status \u003d\u003d SEC_E_INCOMPLETE_MESSAGE) {\n+\t\tif (conn-\u003erx_len \u003d\u003d conn-\u003erx_alloc) {\n+\t\t\tif (lws_tls_schannel_realloc_buffer(conn, conn-\u003erx_alloc + 2048))\n+\t\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tn \u003d recv(wsi-\u003edesc.sockfd, (char *)conn-\u003erx_buf + conn-\u003erx_len, (int)(conn-\u003erx_alloc - conn-\u003erx_len), 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t} else if (n \u003d\u003d 0) {\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003erx_len +\u003d n;\n+\t\treturn lws_tls_server_accept(wsi);\n+\t}\n+\n+\tif (status \u003d\u003d SEC_I_CONTINUE_NEEDED || status \u003d\u003d SEC_E_OK) {\n+\t\tif (out_buf[0].cbBuffer \u003e 0 \u0026\u0026 out_buf[0].pvBuffer) {\n+\t\t\tconn-\u003etx_buf \u003d lws_malloc(out_buf[0].cbBuffer, \u0022schannel_tx_srv\u0022);\n+\t\t\tmemcpy(conn-\u003etx_buf, out_buf[0].pvBuffer, out_buf[0].cbBuffer);\n+\t\t\tconn-\u003etx_len \u003d out_buf[0].cbBuffer;\n+\t\t\tconn-\u003etx_pos \u003d 0;\n+\t\t\tFreeContextBuffer(out_buf[0].pvBuffer);\n+\n+\t\t\tn \u003d send(wsi-\u003edesc.sockfd, (char *)conn-\u003etx_buf, (int)conn-\u003etx_len, 0);\n+\t\t\tif (n \u003c 0) {\n+\t\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\t\t} else {\n+\t\t\t\tconn-\u003etx_pos +\u003d n;\n+\t\t\t\tif (conn-\u003etx_pos \u003d\u003d conn-\u003etx_len) {\n+\t\t\t\t\tlws_free_set_NULL(conn-\u003etx_buf);\n+\t\t\t\t\tconn-\u003etx_len \u003d 0;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (in_buf[1].BufferType \u003d\u003d SECBUFFER_EXTRA \u0026\u0026 in_buf[1].cbBuffer \u003e 0) {\n+\t\t\tmemmove(conn-\u003erx_buf, (uint8_t*)conn-\u003erx_buf + (conn-\u003erx_len - in_buf[1].cbBuffer), in_buf[1].cbBuffer);\n+\t\t\tconn-\u003erx_len \u003d in_buf[1].cbBuffer;\n+\t\t} else {\n+\t\t\tconn-\u003erx_len \u003d 0;\n+\t\t}\n+\n+\t\tif (status \u003d\u003d SEC_E_OK) {\n+\t\t\tconn-\u003ef_handshake_finished \u003d 1;\n+\t\t\tQueryContextAttributes(\u0026conn-\u003ectxt, SECPKG_ATTR_STREAM_SIZES, \u0026conn-\u003estream_sizes);\n+\t\t\treturn LWS_SSL_CAPABLE_DONE;\n+\t\t}\n+\n+\t\tif (conn-\u003etx_buf) return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t}\n+\n+\tlwsl_err(\u0022%s: AcceptSecurityContext failed 0x%x\u005cn\u0022, __func__, (int)status);\n \treturn LWS_SSL_CAPABLE_ERROR;\n }\n \n-enum lws_ssl_capable_status\n-lws_tls_server_abort_connection(struct lws *wsi)\n+\tint\n+lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len)\n {\n-\t/* stub */\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\tSecBufferDesc msg_desc;\n+\tSecBuffer msg_buf[4];\n+\tSECURITY_STATUS status;\n+\tssize_t n;\n+\n+ if (!wsi-\u003etls.ssl)\n+ return lws_ssl_capable_read_no_ssl(wsi, buf, len);\n+\n+\tif (!conn || !conn-\u003ef_handshake_finished) return LWS_SSL_CAPABLE_ERROR;\n+\n+\tconn-\u003ef_socket_is_blocking \u003d 0;\n+\n+\t/* Check if we have decrypted data pending in buflist */\n+\tsize_t pending_len \u003d lws_buflist_next_segment_len(\u0026conn-\u003edecrypted_list, NULL);\n+\tif (pending_len \u003e 0) {\n+\t\tsize_t copy_len \u003d pending_len \u003e len ? len : pending_len;\n+\t\tlws_buflist_linear_use(\u0026conn-\u003edecrypted_list, buf, copy_len);\n+\t\tlwsl_wsi_debug(wsi, \u0022buflist pending %d, copied %d\u0022, (int)pending_len, (int)copy_len);\n+\t\tn \u003d (int)copy_len;\n+\t\tgoto check_pending;\n+\t}\n+\n+\tif (!conn-\u003erx_len) {\n+\t\tif (!conn-\u003erx_alloc)\n+\t\t\tlws_tls_schannel_realloc_buffer(conn, 4096);\n+\t\tn \u003d recv(wsi-\u003edesc.sockfd, (char *)conn-\u003erx_buf, (int)conn-\u003erx_alloc, 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN ||\n+\t\t\t\t\tLWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK) {\n+\t\t\t\tconn-\u003ef_socket_is_blocking \u003d 1;\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t\t\t}\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tif (n \u003d\u003d 0)\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\n+\t\tconn-\u003erx_len \u003d n;\n+\t\tlwsl_wsi_debug(wsi, \u0022recv %d bytes\u0022, (int)n);\n+\t}\n+\n+\t/* Decrypt */\n+\tmsg_buf[0].BufferType \u003d SECBUFFER_DATA;\n+\tmsg_buf[0].pvBuffer \u003d conn-\u003erx_buf;\n+\tmsg_buf[0].cbBuffer \u003d (unsigned long)conn-\u003erx_len;\n+\tmsg_buf[1].BufferType \u003d SECBUFFER_EMPTY;\n+\tmsg_buf[2].BufferType \u003d SECBUFFER_EMPTY;\n+\tmsg_buf[3].BufferType \u003d SECBUFFER_EMPTY;\n+\n+\tmsg_desc.cBuffers \u003d 4;\n+\tmsg_desc.pBuffers \u003d msg_buf;\n+\tmsg_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\tstatus \u003d DecryptMessage(\u0026conn-\u003ectxt, \u0026msg_desc, 0, NULL);\n+\n+\tif (status \u003d\u003d SEC_E_INCOMPLETE_MESSAGE) {\n+\t\tif (conn-\u003erx_len \u003d\u003d conn-\u003erx_alloc)\n+\t\t\tlws_tls_schannel_realloc_buffer(conn, conn-\u003erx_alloc + 2048);\n+\n+\t\tn \u003d recv(wsi-\u003edesc.sockfd, (char *)conn-\u003erx_buf + conn-\u003erx_len, (int)(conn-\u003erx_alloc - conn-\u003erx_len), 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK) {\n+\t\t\t\tconn-\u003ef_socket_is_blocking \u003d 1;\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_READ;\n+\t\t\t}\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t} else if (n \u003d\u003d 0) {\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003erx_len +\u003d n;\n+\t\treturn lws_ssl_capable_read(wsi, buf, len);\n+\t}\n+\n+\tif (status \u003d\u003d SEC_E_OK || status \u003d\u003d SEC_I_RENEGOTIATE) {\n+\t\tint i;\n+\t\tuint8_t *dec_data \u003d NULL;\n+\t\tsize_t dec_len \u003d 0;\n+\n+\t\t/* First locate the data pointer/length before any memmove happens */\n+\t\tfor (i \u003d 0; i \u003c 4; i++) {\n+\t\t\tif (msg_buf[i].BufferType \u003d\u003d SECBUFFER_DATA) {\n+\t\t\t\tdec_data \u003d msg_buf[i].pvBuffer;\n+\t\t\t\tdec_len \u003d msg_buf[i].cbBuffer;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* Process decrypted data immediately */\n+\t\tif (dec_len \u003e 0) {\n+\t\t\tsize_t copy_len \u003d dec_len \u003e len ? len : dec_len;\n+\t\t\tmemcpy(buf, dec_data, copy_len);\n+\n+\t\t\tif (dec_len \u003e copy_len) {\n+\t\t\t\tif (lws_buflist_append_segment(\u0026conn-\u003edecrypted_list, dec_data + copy_len, dec_len - copy_len) \u003c 0) {\n+\t\t\t\t\tlwsl_err(\u0022OOM appending to buflist\u005cn\u0022);\n+\t\t\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tn \u003d (int)copy_len; /* Return value */\n+\t\t\tlwsl_wsi_debug(wsi, \u0022decrypted %d bytes, copied %d to user\u005cn\u0022, (int)dec_len, (int)n);\n+\t\t} else {\n+\t\t\t/* Handshake message or empty record. Recurse to read next record. */\n+\t\t\t/* But first move extra data */\n+\t\t\tfor (i \u003d 0; i \u003c 4; i++) {\n+\t\t\t\tif (msg_buf[i].BufferType \u003d\u003d SECBUFFER_EXTRA) {\n+\t\t\t\t\tmemmove(conn-\u003erx_buf, msg_buf[i].pvBuffer, msg_buf[i].cbBuffer);\n+\t\t\t\t\tconn-\u003erx_len \u003d msg_buf[i].cbBuffer;\n+\t\t\t\t\treturn lws_ssl_capable_read(wsi, buf, len);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tconn-\u003erx_len \u003d 0;\n+\t\t\treturn lws_ssl_capable_read(wsi, buf, len);\n+\t\t}\n+\n+\t\t/* Now handle extra data buffering */\n+\t\tfor (i \u003d 0; i \u003c 4; i++) {\n+\t\t\tif (msg_buf[i].BufferType \u003d\u003d SECBUFFER_EXTRA) {\n+\t\t\t\tmemmove(conn-\u003erx_buf, msg_buf[i].pvBuffer, msg_buf[i].cbBuffer);\n+\t\t\t\tconn-\u003erx_len \u003d msg_buf[i].cbBuffer;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t}\n+\t\tconn-\u003erx_len \u003d 0;\n+\n+done:\n+\t\tgoto check_pending;\n+\t}\n+\n \treturn LWS_SSL_CAPABLE_ERROR;\n+\n+check_pending:\n+\tif (n !\u003d (ssize_t)len) {\n+\t\tlws_ssl_remove_wsi_from_buffered_list(wsi);\n+\t\treturn (int)n;\n+\t}\n+\n+\tif (lws_ssl_pending(wsi)) {\n+\t\tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003ea.context-\u003ept[(int)wsi-\u003etsi];\n+\t\tif (lws_dll2_is_detached(\u0026wsi-\u003etls.dll_pending_tls))\n+\t\t\tlws_dll2_add_head(\u0026wsi-\u003etls.dll_pending_tls, \u0026pt-\u003etls.dll_pending_tls_owner);\n+\t} else {\n+\t\tlws_ssl_remove_wsi_from_buffered_list(wsi);\n+\t}\n+\n+\treturn (int)n;\n }\n \n-enum lws_ssl_capable_status\n-__lws_tls_shutdown(struct lws *wsi)\n+\tint\n+lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len)\n {\n-\t/* stub */\n-\treturn LWS_SSL_CAPABLE_ERROR;\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\tSecBufferDesc msg_desc;\n+\tSecBuffer msg_buf[4];\n+\tSECURITY_STATUS status;\n+\tuint8_t *alloc_buf;\n+\tsize_t alloc_len;\n+\tssize_t n;\n+\n+ if (!wsi-\u003etls.ssl)\n+ return lws_ssl_capable_write_no_ssl(wsi, buf, len);\n+\n+\tif (!conn || !conn-\u003ef_handshake_finished) return LWS_SSL_CAPABLE_ERROR;\n+\n+\t/* Flush existing ciphertext */\n+\tif (conn-\u003etx_buf) {\n+\t\tn \u003d send(wsi-\u003edesc.sockfd, (char *)conn-\u003etx_buf + conn-\u003etx_pos, (int)(conn-\u003etx_len - conn-\u003etx_pos), 0);\n+\t\tif (n \u003c 0) {\n+\t\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK)\n+\t\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\t\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t\t}\n+\t\tconn-\u003etx_pos +\u003d n;\n+\t\tif (conn-\u003etx_pos \u003c conn-\u003etx_len)\n+\t\t\treturn LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;\n+\n+\t\tlws_free_set_NULL(conn-\u003etx_buf);\n+\t\tconn-\u003etx_len \u003d 0;\n+\t\tconn-\u003etx_pos \u003d 0;\n+\n+\t\t/* Consumed old data, but what about new data?\n+\t\t The caller called us to write 'buf'.\n+\t\t We just flushed OLD data.\n+\t\t We should now process the new data if possible, or return 0?\n+\t\t If we return 0, LWS might think we wrote nothing.\n+\t\t Actually, we should proceed to encrypt 'buf' now that we are clear.\n+\t\t */\n+\t}\n+\n+\talloc_len \u003d conn-\u003estream_sizes.cbHeader + len + conn-\u003estream_sizes.cbTrailer;\n+\talloc_buf \u003d lws_malloc(alloc_len, \u0022schannel_write\u0022);\n+\tif (!alloc_buf) return LWS_SSL_CAPABLE_ERROR;\n+\n+\tmsg_buf[0].BufferType \u003d SECBUFFER_STREAM_HEADER;\n+\tmsg_buf[0].pvBuffer \u003d alloc_buf;\n+\tmsg_buf[0].cbBuffer \u003d conn-\u003estream_sizes.cbHeader;\n+\n+\tmsg_buf[1].BufferType \u003d SECBUFFER_DATA;\n+\tmsg_buf[1].pvBuffer \u003d alloc_buf + conn-\u003estream_sizes.cbHeader;\n+\tmsg_buf[1].cbBuffer \u003d (unsigned long)len;\n+\tmemcpy(msg_buf[1].pvBuffer, buf, len);\n+\n+\tmsg_buf[2].BufferType \u003d SECBUFFER_STREAM_TRAILER;\n+\tmsg_buf[2].pvBuffer \u003d alloc_buf + conn-\u003estream_sizes.cbHeader + len;\n+\tmsg_buf[2].cbBuffer \u003d conn-\u003estream_sizes.cbTrailer;\n+\n+\tmsg_buf[3].BufferType \u003d SECBUFFER_EMPTY;\n+\tmsg_buf[3].cbBuffer \u003d 0;\n+\n+\tmsg_desc.cBuffers \u003d 4;\n+\tmsg_desc.pBuffers \u003d msg_buf;\n+\tmsg_desc.ulVersion \u003d SECBUFFER_VERSION;\n+\n+\tstatus \u003d EncryptMessage(\u0026conn-\u003ectxt, 0, \u0026msg_desc, 0);\n+\tif (status !\u003d SEC_E_OK) {\n+\t\tlws_free(alloc_buf);\n+\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t}\n+\n+\tsize_t total_len \u003d msg_buf[0].cbBuffer + msg_buf[1].cbBuffer + msg_buf[2].cbBuffer;\n+\n+\tn \u003d send(wsi-\u003edesc.sockfd, (char *)alloc_buf, (int)total_len, 0);\n+\n+\tif (n \u003c 0) {\n+\t\tif (LWS_ERRNO \u003d\u003d LWS_EAGAIN || LWS_ERRNO \u003d\u003d LWS_EWOULDBLOCK) {\n+\t\t\t/* Blocked immediately. Buffer EVERYTHING. */\n+\t\t\tconn-\u003etx_buf \u003d alloc_buf;\n+\t\t\tconn-\u003etx_len \u003d total_len;\n+\t\t\tconn-\u003etx_pos \u003d 0;\n+\t\t\treturn (int)len; /* Valid write of plaintext, but buffered ciphertext */\n+\t\t}\n+\t\tlws_free(alloc_buf);\n+\t\treturn LWS_SSL_CAPABLE_ERROR;\n+\t}\n+\n+\tif ((size_t)n \u003c total_len) {\n+\t\t/* Partial write. Buffer remainder. */\n+\t\tconn-\u003etx_buf \u003d alloc_buf;\n+\t\tconn-\u003etx_len \u003d total_len;\n+\t\tconn-\u003etx_pos \u003d n;\n+\t\t/* We return 'len' because we accepted the whole plaintext frame and encrypted it. */\n+\t\treturn (int)len;\n+\t}\n+\n+\tlws_free(alloc_buf);\n+\treturn (int)len;\n }\n \n-enum lws_ssl_capable_status\n-lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t len)\n+\tint\n+lws_ssl_pending(struct lws *wsi)\n {\n-\t/* stub */\n-\treturn LWS_SSL_CAPABLE_ERROR;\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\n+\tif (conn \u0026\u0026 lws_buflist_next_segment_len(\u0026conn-\u003edecrypted_list, NULL) \u003e 0) {\n+\t\tlwsl_wsi_debug(wsi, \u0022pending buflist\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\tif (conn \u0026\u0026 conn-\u003erx_len \u003e 0 \u0026\u0026 !conn-\u003ef_socket_is_blocking) {\n+\t\tlwsl_wsi_debug(wsi, \u0022pending rx_len %d\u0022, (int)conn-\u003erx_len);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n }\n \n-int\n-lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len)\n+\tint\n+lws_ssl_close(struct lws *wsi)\n {\n-\t/* stub */\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\tif (conn) {\n+\t\tDeleteSecurityContext(\u0026conn-\u003ectxt);\n+\t\tlws_free_set_NULL(conn-\u003erx_buf);\n+\t\tlws_free_set_NULL(conn-\u003etx_buf);\n+\t\tlws_buflist_destroy_all_segments(\u0026conn-\u003edecrypted_list);\n+\t\tlws_free_set_NULL(conn);\n+\t\twsi-\u003etls.ssl \u003d NULL;\n+\t}\n \treturn 0;\n }\n \n-int\n+\tvoid\n+lws_ssl_bind_passphrase(lws_tls_ctx *ssl_ctx, int is_client,\n+\t\tconst struct lws_context_creation_info *info)\n+{\n+}\n+\n+\tenum lws_ssl_capable_status\n+lws_tls_server_abort_connection(struct lws *wsi)\n+{\n+\tlws_ssl_close(wsi);\n+\treturn LWS_SSL_CAPABLE_DONE;\n+}\n+\n+\tenum lws_ssl_capable_status\n+__lws_tls_shutdown(struct lws *wsi)\n+{\n+\tlws_ssl_close(wsi);\n+\treturn LWS_SSL_CAPABLE_DONE;\n+}\n+\n+\tint\n+lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len)\n+{\n+\tstruct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+\tPCCERT_CONTEXT pCert \u003d NULL;\n+\tSECURITY_STATUS status;\n+\tint ret \u003d -1;\n+\n+\tif (!conn) return -1;\n+\n+\tstatus \u003d QueryContextAttributes(\u0026conn-\u003ectxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, \u0026pCert);\n+\tif (status !\u003d SEC_E_OK || !pCert) {\n+\t\t/* No remote cert */\n+\t\treturn -1;\n+\t}\n+\n+\tCERT_CHAIN_PARA ChainPara;\n+\tPCCERT_CHAIN_CONTEXT pChainContext \u003d NULL;\n+\n+\tmemset(\u0026ChainPara, 0, sizeof(ChainPara));\n+\tChainPara.cbSize \u003d sizeof(ChainPara);\n+\n+\tif (CertGetCertificateChain(NULL, pCert, NULL, NULL, \u0026ChainPara, 0, NULL, \u0026pChainContext)) {\n+\n+\t\tHTTPSPolicyCallbackData polHttps;\n+\t\tmemset(\u0026polHttps, 0, sizeof(HTTPSPolicyCallbackData));\n+\t\tpolHttps.cbStruct \u003d sizeof(HTTPSPolicyCallbackData);\n+\t\tpolHttps.dwAuthType \u003d AUTHTYPE_SERVER;\n+\n+\t\t/* Convert stored hostname to WCHAR for validation */\n+\t\tWCHAR wszServerName[128];\n+\t\tif (MultiByteToWideChar(CP_UTF8, 0, conn-\u003ehostname, -1, wszServerName, LWS_ARRAY_SIZE(wszServerName))) {\n+\t\t\tpolHttps.pwszServerName \u003d wszServerName;\n+\t\t}\n+\n+\t\tCERT_CHAIN_POLICY_PARA PolicyPara;\n+\t\tmemset(\u0026PolicyPara, 0, sizeof(PolicyPara));\n+\t\tPolicyPara.cbSize \u003d sizeof(PolicyPara);\n+\t\tPolicyPara.pvExtraPolicyPara \u003d \u0026polHttps;\n+\n+\t\tCERT_CHAIN_POLICY_STATUS PolicyStatus;\n+\t\tmemset(\u0026PolicyStatus, 0, sizeof(PolicyStatus));\n+\t\tPolicyStatus.cbSize \u003d sizeof(PolicyStatus);\n+\n+\t\tif (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, \u0026PolicyPara, \u0026PolicyStatus)) {\n+\t\t\tif (PolicyStatus.dwError \u003d\u003d ERROR_SUCCESS) {\n+\t\t\t\tret \u003d 0;\n+\t\t\t} else {\n+\t\t\t\t/* Check if we allow self signed */\n+\t\t\t\tif (conn-\u003ef_allow_self_signed) {\n+\t\t\t\t\t/* Check if the error is only related to untrusted root or partial chain */\n+\t\t\t\t\tif (PolicyStatus.dwError \u003d\u003d CERT_E_UNTRUSTEDROOT ||\n+\t\t\t\t\t\t\tPolicyStatus.dwError \u003d\u003d CERT_E_CHAINING) {\n+\t\t\t\t\t\tret \u003d 0;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tif (ret !\u003d 0) {\n+\t\t\t\t\tlws_snprintf(ebuf, ebuf_len, \u0022Certificate validation failed: 0x%x\u0022, (unsigned int)PolicyStatus.dwError);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tCertFreeCertificateChain(pChainContext);\n+\t}\n+\n+\tCertFreeCertificateContext(pCert);\n+\treturn ret;\n+}\n+\n+\tint\n lws_ssl_get_error(struct lws *wsi, int n)\n {\n-\t/* stub */\n-\treturn 0;\n+\treturn n;\n }\n \n-static int\n+\tstatic int\n tops_fake_POLLIN_for_buffered_schannel(struct lws_context_per_thread *pt)\n {\n-\treturn lws_tls_fake_POLLIN_for_buffered(pt);\n+\tint ret \u003d lws_tls_fake_POLLIN_for_buffered(pt);\n+\tif (ret) lwsl_info(\u0022%s: triggered %d\u005cn\u0022, __func__, ret);\n+\treturn ret;\n }\n \n const struct lws_tls_ops tls_ops_schannel \u003d {\ndiff --git a/lib/tls/schannel/schannel-tls.c b/lib/tls/schannel/schannel-tls.c\nindex be10798..fbd94d1 100644\n--- a/lib/tls/schannel/schannel-tls.c\n+++ b/lib/tls/schannel/schannel-tls.c\n@@ -24,19 +24,19 @@\n \n #include \u0022private-lib-core.h\u0022\n #include \u0022private-lib-tls.h\u0022\n+#include \u0022private.h\u0022\n \n int\n lws_context_init_ssl_library(struct lws_context *cx,\n \t\t\t const struct lws_context_creation_info *info)\n {\n-\t/* stub */\n+\tcx-\u003etls_ops \u003d \u0026tls_ops_schannel;\n \treturn 0;\n }\n \n void\n lws_context_deinit_ssl_library(struct lws_context *context)\n {\n-\t/* stub */\n }\n \n int\n@@ -45,38 +45,160 @@ lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi,\n \t\t\t const char *mem_cert, size_t len_mem_cert,\n \t\t\t const char *mem_privkey, size_t mem_privkey_len)\n {\n-\t/* stub */\n+ PCCERT_CONTEXT pCertCtx \u003d NULL;\n+ SCHANNEL_CRED schannel_cred \u003d { 0 };\n+ SECURITY_STATUS status;\n+ TimeStamp tsExpiry;\n+\n+ if (!cert \u0026\u0026 !mem_cert)\n+ return 0;\n+\n+ vhost-\u003etls.ssl_ctx \u003d lws_zalloc(sizeof(*vhost-\u003etls.ssl_ctx), \u0022schannel_ctx\u0022);\n+ if (!vhost-\u003etls.ssl_ctx)\n+ return 1;\n+\n+ /* Generate a unique container name for this vhost context to persist keys */\n+ /* We need to clean this up in destroy */\n+ lws_snprintf(vhost-\u003etls.ssl_ctx-\u003ekey_container_name, sizeof(vhost-\u003etls.ssl_ctx-\u003ekey_container_name),\n+ \u0022lws_vhost_%p_%u\u0022, vhost, (unsigned int)time(NULL));\n+\n+ if (lws_tls_schannel_cert_info_load(vhost-\u003econtext, cert, private_key,\n+ mem_cert, len_mem_cert,\n+ mem_privkey, mem_privkey_len, \u0026pCertCtx,\n+ \u0026vhost-\u003etls.ssl_ctx-\u003estore,\n+ (void **)\u0026vhost-\u003etls.ssl_ctx-\u003eu.key_prov,\n+ \u0026vhost-\u003etls.ssl_ctx-\u003ekey_type,\n+ vhost-\u003etls.ssl_ctx-\u003ekey_container_name)) {\n+ lwsl_err(\u0022%s: Failed to load server certs\u005cn\u0022, __func__);\n+ lws_free(vhost-\u003etls.ssl_ctx);\n+ vhost-\u003etls.ssl_ctx \u003d NULL;\n+ return 1;\n+ }\n+\n+ schannel_cred.dwVersion \u003d SCHANNEL_CRED_VERSION;\n+ schannel_cred.cCreds \u003d 1;\n+ schannel_cred.paCred \u003d \u0026pCertCtx;\n+ schannel_cred.dwFlags \u003d SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_NO_SYSTEM_MAPPER;\n+ /* Allow all protocol versions by default */\n+ schannel_cred.grbitEnabledProtocols \u003d 0;\n+\n+ status \u003d AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND, NULL,\n+ \u0026schannel_cred, NULL, NULL,\n+ \u0026vhost-\u003etls.ssl_ctx-\u003ecred, \u0026tsExpiry);\n+\n+ CertFreeCertificateContext(pCertCtx);\n+\n+ if (status !\u003d SEC_E_OK) {\n+ lwsl_err(\u0022%s: AcquireCredentialsHandle failed 0x%x\u005cn\u0022, __func__, (int)status);\n+ lws_free(vhost-\u003etls.ssl_ctx);\n+ vhost-\u003etls.ssl_ctx \u003d NULL;\n+ return 1;\n+ }\n+\n+ vhost-\u003etls.ssl_ctx-\u003einitialized \u003d 1;\n+ lwsl_vhost_notice(vhost, \u0022vhost %p: server ctx %p created\u0022, vhost, vhost-\u003etls.ssl_ctx);\n+\n \treturn 0;\n }\n \n void\n lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost)\n {\n-\t/* stub */\n }\n \n void\n lws_ssl_destroy(struct lws_vhost *vhost)\n {\n-\t/* stub */\n+ if (vhost-\u003etls.ssl_ctx) {\n+ if (vhost-\u003etls.ssl_ctx-\u003einitialized)\n+ FreeCredentialsHandle(\u0026vhost-\u003etls.ssl_ctx-\u003ecred);\n+\n+ /* Cleanup Key */\n+ if (vhost-\u003etls.ssl_ctx-\u003ekey_type \u003d\u003d 0) {\n+ /* CAPI */\n+ if (vhost-\u003etls.ssl_ctx-\u003eu.key_prov) {\n+ CryptReleaseContext(vhost-\u003etls.ssl_ctx-\u003eu.key_prov, 0);\n+ /* Clean up the temporary key container */\n+ if (vhost-\u003etls.ssl_ctx-\u003ekey_container_name[0]) {\n+ HCRYPTPROV hProv;\n+ if (CryptAcquireContext(\u0026hProv, vhost-\u003etls.ssl_ctx-\u003ekey_container_name, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_DELETEKEYSET | CRYPT_SILENT)) {\n+ // Successfully deleted\n+ }\n+ }\n+ }\n+ } else {\n+ /* CNG (EC) */\n+ /* Ephemeral handles are returned, named container handles are closed but need deleting. */\n+ /* If it's a named container, we might not have the handle open (it was closed after link). */\n+ /* If it's ephemeral, we have the handle. */\n+\n+ if (vhost-\u003etls.ssl_ctx-\u003eu.key_cng) {\n+ NCryptFreeObject(vhost-\u003etls.ssl_ctx-\u003eu.key_cng);\n+ }\n+\n+ if (vhost-\u003etls.ssl_ctx-\u003ekey_container_name[0]) {\n+ /* Delete named key */\n+ NCRYPT_PROV_HANDLE hProv \u003d 0;\n+ if (NCryptOpenStorageProvider(\u0026hProv, MS_KEY_STORAGE_PROVIDER, 0) \u003d\u003d ERROR_SUCCESS) {\n+ NCRYPT_KEY_HANDLE hKey \u003d 0;\n+ /* We need to open it to delete it? */\n+ /* Wait, NCryptDeleteKey takes a key handle. */\n+ WCHAR wName[128];\n+ if (MultiByteToWideChar(CP_UTF8, 0, vhost-\u003etls.ssl_ctx-\u003ekey_container_name, -1, wName, 128)) {\n+ if (NCryptOpenKey(hProv, \u0026hKey, wName, 0, 0) \u003d\u003d ERROR_SUCCESS) {\n+ NCryptDeleteKey(hKey, 0);\n+ /* hKey is freed by DeleteKey? \u0022The handle is invalid after this function returns\u0022 */\n+ }\n+ }\n+ NCryptFreeObject(hProv);\n+ }\n+ }\n+ }\n+\n+ if (vhost-\u003etls.ssl_ctx-\u003estore)\n+ CertCloseStore(vhost-\u003etls.ssl_ctx-\u003estore, 0);\n+ lws_free(vhost-\u003etls.ssl_ctx);\n+ vhost-\u003etls.ssl_ctx \u003d NULL;\n+ }\n+ if (vhost-\u003etls.ssl_client_ctx) {\n+ if (vhost-\u003etls.ssl_client_ctx-\u003einitialized)\n+ FreeCredentialsHandle(\u0026vhost-\u003etls.ssl_client_ctx-\u003ecred);\n+ /* Client context might not have key_prov set if we passed NULL, but if it does (future use), use CryptReleaseContext if it was CAPI?\n+ Wait, lws_tls_client_create_vhost_context passes NULL for phProv currently.\n+ But if it passed a pointer, it would get an HCRYPTPROV.\n+ Let's assume CAPI.\n+ */\n+ if (vhost-\u003etls.ssl_client_ctx-\u003ekey_type \u003d\u003d 0) {\n+ if (vhost-\u003etls.ssl_client_ctx-\u003eu.key_prov)\n+ CryptReleaseContext(vhost-\u003etls.ssl_client_ctx-\u003eu.key_prov, 0);\n+ } else {\n+ if (vhost-\u003etls.ssl_client_ctx-\u003eu.key_cng)\n+ NCryptFreeObject(vhost-\u003etls.ssl_client_ctx-\u003eu.key_cng);\n+ }\n+\n+ if (vhost-\u003etls.ssl_client_ctx-\u003estore)\n+ CertCloseStore(vhost-\u003etls.ssl_client_ctx-\u003estore, 0);\n+ lws_free(vhost-\u003etls.ssl_client_ctx);\n+ vhost-\u003etls.ssl_client_ctx \u003d NULL;\n+ }\n }\n \n void\n lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost)\n {\n-\t/* stub */\n+ lws_ssl_destroy(vhost);\n }\n \n void\n lws_ssl_context_destroy(struct lws_context *context)\n {\n-\t/* stub */\n }\n \n lws_tls_ctx *\n lws_tls_ctx_from_wsi(struct lws *wsi)\n {\n-\t/* stub */\n+ if (!wsi) return NULL;\n+ if (wsi-\u003ea.vhost) return wsi-\u003ea.vhost-\u003etls.ssl_ctx;\n \treturn NULL;\n }\n \n@@ -94,20 +216,81 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh,\n \t\t\t\t const void *key_mem,\n \t\t\t\t unsigned int key_mem_len)\n {\n-\t/* stub */\n-\treturn 0;\n+ SCHANNEL_CRED schannel_cred \u003d { 0 };\n+ SECURITY_STATUS status;\n+ TimeStamp tsExpiry;\n+ PCCERT_CONTEXT pCertCtx \u003d NULL;\n+\n+ vh-\u003etls.ssl_client_ctx \u003d lws_zalloc(sizeof(*vh-\u003etls.ssl_client_ctx), \u0022schannel_client_ctx\u0022);\n+ if (!vh-\u003etls.ssl_client_ctx) return 1;\n+\n+ schannel_cred.dwVersion \u003d SCHANNEL_CRED_VERSION;\n+ schannel_cred.dwFlags \u003d SCH_CRED_MANUAL_CRED_VALIDATION;\n+\n+ if (cert_filepath || cert_mem) {\n+ schannel_cred.dwFlags |\u003d SCH_CRED_NO_DEFAULT_CREDS;\n+ if (lws_tls_schannel_cert_info_load(vh-\u003econtext, cert_filepath, private_key_filepath,\n+ cert_mem, cert_mem_len,\n+ key_mem, key_mem_len, \u0026pCertCtx,\n+ \u0026vh-\u003etls.ssl_client_ctx-\u003estore,\n+ /* We pass NULL for provider because client usually relies on default behavior or different handling.\n+ Actually, if we want to support client certs from memory properly, we SHOULD pass \u0026vh-\u003etls.ssl_client_ctx-\u003ekey_prov.\n+ But the current change switches implementation to CAPI.\n+ Let's stick to NULL for client for now to match \u0022existing working\u0022 state (where we assumed it worked without keeping prov open, or used a different flow).\n+ Wait, earlier finding was that client LEAKED the provider.\n+ If we pass NULL, my new implementation in schannel-x509.c closes it.\n+ If client needs it open, we MUST pass the pointer.\n+ So I will pass the pointer.\n+ */\n+ (void **)\u0026vh-\u003etls.ssl_client_ctx-\u003eu.key_prov,\n+ \u0026vh-\u003etls.ssl_client_ctx-\u003ekey_type,\n+ NULL) \u003d\u003d 0) {\n+ schannel_cred.cCreds \u003d 1;\n+ schannel_cred.paCred \u003d \u0026pCertCtx;\n+ }\n+ }\n+\n+ status \u003d AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL,\n+ \u0026schannel_cred, NULL, NULL,\n+ \u0026vh-\u003etls.ssl_client_ctx-\u003ecred, \u0026tsExpiry);\n+\n+ if (status \u003d\u003d SEC_E_NO_CREDENTIALS \u0026\u0026 schannel_cred.cCreds \u003e 0) {\n+ lwsl_warn(\u0022%s: client cert rejected by SChannel, retrying without\u005cn\u0022, __func__);\n+ schannel_cred.cCreds \u003d 0;\n+ schannel_cred.paCred \u003d NULL;\n+ schannel_cred.dwFlags \u0026\u003d ~SCH_CRED_NO_DEFAULT_CREDS;\n+ status \u003d AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL,\n+ \u0026schannel_cred, NULL, NULL,\n+ \u0026vh-\u003etls.ssl_client_ctx-\u003ecred, \u0026tsExpiry);\n+ }\n+\n+ if (pCertCtx) CertFreeCertificateContext(pCertCtx);\n+\n+ if (status !\u003d SEC_E_OK) {\n+ lwsl_err(\u0022%s: AcquireCredentialsHandle failed 0x%x\u005cn\u0022, __func__, (int)status);\n+ lws_free(vh-\u003etls.ssl_client_ctx);\n+ vh-\u003etls.ssl_client_ctx \u003d NULL;\n+ return 1;\n+ }\n+\n+ vh-\u003etls.ssl_client_ctx-\u003einitialized \u003d 1;\n+ return 0;\n }\n \n void\n lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret)\n {\n-\t/* stub */\n }\n \n int\n lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info,\n \t\t\t\t struct lws_vhost *vhost, struct lws *wsi)\n {\n-\t/* stub */\n-\treturn 0;\n+ return lws_tls_server_certs_load(vhost, wsi,\n+ info-\u003essl_cert_filepath,\n+ info-\u003essl_private_key_filepath,\n+ info-\u003eserver_ssl_cert_mem,\n+ info-\u003eserver_ssl_cert_mem_len,\n+ info-\u003eserver_ssl_private_key_mem,\n+ info-\u003eserver_ssl_private_key_mem_len);\n }\ndiff --git a/lib/tls/schannel/schannel-x509.c b/lib/tls/schannel/schannel-x509.c\nindex 374c3fa..1d0edd0 100644\n--- a/lib/tls/schannel/schannel-x509.c\n+++ b/lib/tls/schannel/schannel-x509.c\n@@ -24,39 +24,123 @@\n \n #include \u0022private-lib-core.h\u0022\n #include \u0022private-lib-tls.h\u0022\n+#include \u0022private.h\u0022\n \n struct lws_x509_cert {\n \tPCCERT_CONTEXT cert;\n };\n \n+static time_t\n+filetime_to_unix(FILETIME ft)\n+{\n+ ULARGE_INTEGER ull;\n+ ull.LowPart \u003d ft.dwLowDateTime;\n+ ull.HighPart \u003d ft.dwHighDateTime;\n+\n+ return (time_t)((ull.QuadPart - 116444736000000000ULL) / 10000000ULL);\n+}\n+\n+static int\n+lws_tls_schannel_cert_info(PCCERT_CONTEXT pCert, enum lws_tls_cert_info type,\n+\t\t\t union lws_tls_cert_info_results *buf, size_t len)\n+{\n+ if (!pCert) return -1;\n+\n+ switch(type) {\n+ case LWS_TLS_CERT_INFO_VALIDITY_FROM:\n+ buf-\u003etime \u003d filetime_to_unix(pCert-\u003epCertInfo-\u003eNotBefore);\n+ break;\n+ case LWS_TLS_CERT_INFO_VALIDITY_TO:\n+ buf-\u003etime \u003d filetime_to_unix(pCert-\u003epCertInfo-\u003eNotAfter);\n+ break;\n+ case LWS_TLS_CERT_INFO_COMMON_NAME:\n+ if (!CertGetNameStringA(pCert, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME, buf-\u003ens.name, (DWORD)len))\n+ return -1;\n+ buf-\u003ens.len \u003d (int)strlen(buf-\u003ens.name);\n+ break;\n+ case LWS_TLS_CERT_INFO_ISSUER_NAME:\n+ if (!CertNameToStrA(pCert-\u003edwCertEncodingType, \u0026pCert-\u003epCertInfo-\u003eIssuer,\n+ CERT_X500_NAME_STR, buf-\u003ens.name, (DWORD)len))\n+ return -1;\n+ buf-\u003ens.len \u003d (int)strlen(buf-\u003ens.name);\n+ break;\n+ case LWS_TLS_CERT_INFO_USAGE:\n+ {\n+ BYTE usage[2] \u003d {0};\n+ if (CertGetIntendedKeyUsage(pCert-\u003edwCertEncodingType, pCert-\u003epCertInfo, usage, 2)) {\n+ buf-\u003eusage \u003d usage[0] | (usage[1] \u003c\u003c 8);\n+ } else {\n+ buf-\u003eusage \u003d 0;\n+ }\n+ }\n+ break;\n+ case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY:\n+ if (len \u003c pCert-\u003epCertInfo-\u003eSubjectPublicKeyInfo.PublicKey.cbData) return -1;\n+ memcpy(buf-\u003ens.name, pCert-\u003epCertInfo-\u003eSubjectPublicKeyInfo.PublicKey.pbData,\n+ pCert-\u003epCertInfo-\u003eSubjectPublicKeyInfo.PublicKey.cbData);\n+ buf-\u003ens.len \u003d (int)pCert-\u003epCertInfo-\u003eSubjectPublicKeyInfo.PublicKey.cbData;\n+ break;\n+ case LWS_TLS_CERT_INFO_DER_RAW:\n+ if (len \u003c pCert-\u003ecbCertEncoded) return -1;\n+ memcpy(buf-\u003ens.name, pCert-\u003epbCertEncoded, pCert-\u003ecbCertEncoded);\n+ buf-\u003ens.len \u003d (int)pCert-\u003ecbCertEncoded;\n+ break;\n+ default:\n+ return -1;\n+ }\n+ return 0;\n+}\n+\n int\n lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,\n \t\t union lws_tls_cert_info_results *buf, size_t len)\n {\n-\t/* stub */\n-\treturn 0;\n+\t/* stub - usually for server's own cert info? */\n+\t/* SChannel stores creds, not easy to extract cert back unless we kept it */\n+\t/* lws_tls_schannel_ctx has 'cred', but not cert. */\n+\t/* For now, leave as stub or failure */\n+\treturn -1;\n }\n \n int\n lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,\n \t\t union lws_tls_cert_info_results *buf, size_t len)\n {\n-\t/* stub */\n-\treturn 0;\n+ struct lws_tls_schannel_conn *conn \u003d wsi-\u003etls.ssl;\n+ PCCERT_CONTEXT pCert \u003d NULL;\n+ int ret \u003d 0;\n+\n+ if (!conn) return -1;\n+\n+ if (QueryContextAttributes(\u0026conn-\u003ectxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, \u0026pCert) !\u003d SEC_E_OK || !pCert)\n+ return -1;\n+\n+ switch (type) {\n+ case LWS_TLS_CERT_INFO_VERIFIED:\n+ /* If we are here, handshake succeeded. */\n+ /* SChannel verifies by default unless SCH_CRED_NO_SERVER_CREDENTIALS */\n+ /* But lws_tls_client_confirm_peer_cert does extra checks */\n+ /* We can assume true if handshake passed, or check flags if we stored them */\n+ buf-\u003everified \u003d 1;\n+ break;\n+ default:\n+ ret \u003d lws_tls_schannel_cert_info(pCert, type, buf, len);\n+ }\n+\n+ CertFreeCertificateContext(pCert);\n+ return ret;\n }\n \n int\n lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type,\n \t union lws_tls_cert_info_results *buf, size_t len)\n {\n-\t/* stub */\n-\treturn 0;\n+\treturn lws_tls_schannel_cert_info(x509-\u003ecert, type, buf, len);\n }\n \n int\n lws_tls_server_client_cert_verify_config(struct lws_vhost *vh)\n {\n-\t/* stub */\n \treturn 0;\n }\n \n@@ -141,6 +225,47 @@ lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted,\n \treturn -1;\n }\n \n+/* Minimal ASN.1 Reader Helpers */\n+static int lws_asn1_read_length(const uint8_t **p, const uint8_t *end, size_t *len) {\n+\tif (*p \u003e\u003d end) return -1;\n+\tuint8_t c \u003d *(*p)++;\n+\tif (!(c \u0026 0x80)) {\n+\t\t*len \u003d c;\n+\t} else {\n+\t\tint bytes \u003d c \u0026 0x7F;\n+\t\tif (bytes \u003e 4 || *p + bytes \u003e end) return -1;\n+\t\t*len \u003d 0;\n+\t\twhile (bytes--) {\n+\t\t\t*len \u003d (*len \u003c\u003c 8) | *(*p)++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static int lws_asn1_read_integer(const uint8_t **p, const uint8_t *end, struct lws_gencrypto_keyelem *el) {\n+\tsize_t len;\n+\tif (*p \u003e\u003d end || *(*p)++ !\u003d 0x02) return -1; /* Expect INTEGER tag */\n+\tif (lws_asn1_read_length(p, end, \u0026len) \u003c 0) return -1;\n+\tif (*p + len \u003e end) return -1;\n+\n+\t/* Skip leading zero if present (ASN.1 integer is signed, might have 0x00 pad for positive MSB) */\n+\tconst uint8_t *val \u003d *p;\n+\tsize_t vlen \u003d len;\n+\twhile (vlen \u003e 0 \u0026\u0026 val[0] \u003d\u003d 0x00) {\n+\t\tval++;\n+\t\tvlen--;\n+\t}\n+\n+\t/* Copy to key element */\n+\tel-\u003elen \u003d (uint32_t)vlen;\n+\tel-\u003ebuf \u003d lws_malloc(vlen, \u0022asn1 int\u0022);\n+\tif (!el-\u003ebuf) return -1;\n+\tmemcpy(el-\u003ebuf, val, vlen);\n+\n+\t*p +\u003d len;\n+\treturn 0;\n+}\n+\n #if defined(LWS_WITH_JOSE)\n int\n lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509,\n@@ -222,47 +347,6 @@ lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509,\n \treturn ret;\n }\n \n-/* Minimal ASN.1 Reader Helpers */\n-static int lws_asn1_read_length(const uint8_t **p, const uint8_t *end, size_t *len) {\n-\tif (*p \u003e\u003d end) return -1;\n-\tuint8_t c \u003d *(*p)++;\n-\tif (!(c \u0026 0x80)) {\n-\t\t*len \u003d c;\n-\t} else {\n-\t\tint bytes \u003d c \u0026 0x7F;\n-\t\tif (bytes \u003e 4 || *p + bytes \u003e end) return -1;\n-\t\t*len \u003d 0;\n-\t\twhile (bytes--) {\n-\t\t\t*len \u003d (*len \u003c\u003c 8) | *(*p)++;\n-\t\t}\n-\t}\n-\treturn 0;\n-}\n-\n-static int lws_asn1_read_integer(const uint8_t **p, const uint8_t *end, struct lws_gencrypto_keyelem *el) {\n-\tsize_t len;\n-\tif (*p \u003e\u003d end || *(*p)++ !\u003d 0x02) return -1; /* Expect INTEGER tag */\n-\tif (lws_asn1_read_length(p, end, \u0026len) \u003c 0) return -1;\n-\tif (*p + len \u003e end) return -1;\n-\n-\t/* Skip leading zero if present (ASN.1 integer is signed, might have 0x00 pad for positive MSB) */\n-\tconst uint8_t *val \u003d *p;\n-\tsize_t vlen \u003d len;\n-\tif (vlen \u003e 0 \u0026\u0026 val[0] \u003d\u003d 0x00) {\n-\t\tval++;\n-\t\tvlen--;\n-\t}\n-\n-\t/* Copy to key element */\n-\tel-\u003elen \u003d (uint32_t)vlen;\n-\tel-\u003ebuf \u003d lws_malloc(vlen, \u0022asn1 int\u0022);\n-\tif (!el-\u003ebuf) return -1;\n-\tmemcpy(el-\u003ebuf, val, vlen);\n-\n-\t*p +\u003d len;\n-\treturn 0;\n-}\n-\n int\n lws_x509_jwk_privkey_pem(struct lws_context *cx, struct lws_jwk *jwk,\n \t\t\t void *pem, size_t len, const char *passphrase)\n@@ -340,9 +424,7 @@ lws_x509_jwk_privkey_pem(struct lws_context *cx, struct lws_jwk *jwk,\n \t\tif (lws_asn1_read_length(\u0026p, end, \u0026ver_len) \u003c 0) goto bail;\n \t\tp +\u003d ver_len;\n \t} else if (*p \u003d\u003d 0x02) {\n-\t\t/* Likely RSA PKCS#1 starting with Modulus (since we already read Version) */\n-\t\t/* Backtrack pointer to Modulus tag? No, we just continue reading RSA fields. */\n-\t\tp--; /* Back to tag */\n+\t\t/* PKCS#1: kp points to Modulus tag. Version already consumed. */\n \t} else {\n \t\tgoto bail;\n \t}\n@@ -366,3 +448,520 @@ bail:\n \treturn ret;\n }\n #endif\n+\n+int\n+lws_tls_schannel_cert_info_load(struct lws_context *context,\n+ const char *cert, const char *private_key,\n+ const char *mem_cert, size_t len_mem_cert,\n+ const char *mem_privkey, size_t mem_privkey_len,\n+ PCCERT_CONTEXT *pcert, HCERTSTORE *phStore,\n+ void **phKey, int *pKeyType,\n+ const char *container_name)\n+{\n+\tstruct lws_x509_cert x509_obj \u003d {0};\n+\tstruct lws_gencrypto_keyelem e[LWS_GENCRYPTO_RSA_KEYEL_COUNT];\n+\tBYTE *rsablob \u003d NULL;\n+\tULONG bloblen;\n+\tHCRYPTPROV hProv \u003d 0;\n+\tHCRYPTKEY hKey \u003d 0;\n+\tint ret \u003d 1;\n+\n+\tif (phStore) *phStore \u003d NULL;\n+ if (phKey) *phKey \u003d NULL;\n+ if (pKeyType) *pKeyType \u003d 0; /* Default CAPI */\n+\n+\tmemset(e, 0, sizeof(e));\n+\n+\t/* 1. Load Certificate */\n+ HCERTSTORE hStore \u003d CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL);\n+ if (!hStore) {\n+ lwsl_err(\u0022%s: Failed to create memory store\u005cn\u0022, __func__);\n+ return 1;\n+ }\n+\n+\tif (cert) {\n+\t\tuint8_t *der \u003d NULL;\n+\t\tlws_filepos_t amount;\n+\n+\t\tif (lws_tls_alloc_pem_to_der_file(context, cert, mem_cert, len_mem_cert, \u0026der, \u0026amount)) {\n+\t\t\tlwsl_err(\u0022%s: Failed to load cert file %s\u005cn\u0022, __func__, cert ? cert : \u0022mem\u0022);\n+ CertCloseStore(hStore, 0);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tif (!CertAddEncodedCertificateToStore(hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der, (DWORD)amount, CERT_STORE_ADD_ALWAYS, \u0026x509_obj.cert)) {\n+ lwsl_err(\u0022%s: CertAddEncodedCertificateToStore failed\u005cn\u0022, __func__);\n+ lws_free(der);\n+ CertCloseStore(hStore, 0);\n+ return 1;\n+ }\n+\t\tlws_free(der);\n+\t} else if (mem_cert) {\n+\t\tif (lws_x509_parse_from_pem(\u0026x509_obj, mem_cert, len_mem_cert)) {\n+\t\t\tlwsl_err(\u0022%s: Failed to parse cert pem\u005cn\u0022, __func__);\n+ CertCloseStore(hStore, 0);\n+\t\t\treturn 1;\n+\t\t}\n+\n+ /* Move to store */\n+ PCCERT_CONTEXT pStoreCert \u003d NULL;\n+ if (!CertAddCertificateContextToStore(hStore, x509_obj.cert, CERT_STORE_ADD_ALWAYS, \u0026pStoreCert)) {\n+ lwsl_err(\u0022%s: CertAddCertificateContextToStore failed\u005cn\u0022, __func__);\n+ CertFreeCertificateContext(x509_obj.cert);\n+ CertCloseStore(hStore, 0);\n+ return 1;\n+ }\n+ CertFreeCertificateContext(x509_obj.cert);\n+ x509_obj.cert \u003d pStoreCert;\n+\t} else {\n+ CertCloseStore(hStore, 0);\n+\t\treturn 1; /* No cert */\n+\t}\n+\n+ if (phStore)\n+ *phStore \u003d hStore;\n+ else\n+ CertCloseStore(hStore, 0);\n+\n+\tif (!x509_obj.cert) {\n+\t\tlwsl_err(\u0022%s: Failed to create cert context\u005cn\u0022, __func__);\n+\t\treturn 1;\n+\t}\n+\n+\t/* 2. Load Private Key */\n+\tif (!private_key \u0026\u0026 !mem_privkey) {\n+\t\t*pcert \u003d x509_obj.cert;\n+\t\treturn 0;\n+\t}\n+\n+ /* Load key DER */\n+ uint8_t *key_der \u003d NULL;\n+ lws_filepos_t key_der_len;\n+\n+ if (lws_tls_alloc_pem_to_der_file(context, private_key, mem_privkey, mem_privkey_len, \u0026key_der, \u0026key_der_len)) {\n+ lwsl_err(\u0022%s: Failed to load key\u005cn\u0022, __func__);\n+ goto cleanup;\n+ }\n+\n+ /* Check if it is an EC key */\n+ /* If it is EC, we use CNG. If RSA, we use Legacy CAPI. */\n+ /* Simple check: If pem string contains \u0022EC PRIVATE KEY\u0022, it's EC. */\n+ /* Or check OID in PKCS#8 */\n+ int is_ec \u003d 0;\n+ if (strstr(private_key ? private_key : (mem_privkey ? mem_privkey : \u0022\u0022), \u0022EC PRIVATE KEY\u0022)) {\n+ is_ec \u003d 1;\n+ } else {\n+ /* Check DER for OID 1.2.840.10045.2.1 (ecPublicKey) */\n+ /* Sequence { Version, AlgorithmIdentifier { OID ... } ... } */\n+ const uint8_t *kp \u003d key_der;\n+ const uint8_t *kend \u003d key_der + key_der_len;\n+ size_t seq_len;\n+ if (kp \u003c kend \u0026\u0026 *kp++ \u003d\u003d 0x30 \u0026\u0026 lws_asn1_read_length(\u0026kp, kend, \u0026seq_len) \u003d\u003d 0) {\n+ /* Check for version 0 */\n+ size_t ver_len;\n+ if (kp \u003c kend \u0026\u0026 *kp++ \u003d\u003d 0x02 \u0026\u0026 lws_asn1_read_length(\u0026kp, kend, \u0026ver_len) \u003d\u003d 0) {\n+ kp +\u003d ver_len;\n+ /* Next is AlgorithmIdentifier Sequence */\n+ size_t alg_len;\n+ if (kp \u003c kend \u0026\u0026 *kp++ \u003d\u003d 0x30 \u0026\u0026 lws_asn1_read_length(\u0026kp, kend, \u0026alg_len) \u003d\u003d 0) {\n+ /* Check OID: 1.2.840.10045.2.1 is 06 07 2A 86 48 CE 3D 02 01 */\n+ const uint8_t ec_oid[] \u003d { 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 };\n+ if (alg_len \u003e\u003d sizeof(ec_oid) \u0026\u0026 !memcmp(kp, ec_oid, sizeof(ec_oid))) {\n+ is_ec \u003d 1;\n+ }\n+ }\n+ }\n+ }\n+ }\n+\n+ if (is_ec) {\n+ /* EC Path: Use CNG (NCrypt) */\n+ NCRYPT_PROV_HANDLE hProvCNG \u003d 0;\n+ NCRYPT_KEY_HANDLE hKeyCNG \u003d 0;\n+ SECURITY_STATUS status;\n+\n+ /* Open Storage Provider */\n+ /* For server (named), use MS_KEY_STORAGE_PROVIDER. For client (ephemeral), we could use it too but verify flags. */\n+ /* Actually, SChannel works best with KSP for EC. */\n+\n+ status \u003d NCryptOpenStorageProvider(\u0026hProvCNG, MS_KEY_STORAGE_PROVIDER, 0);\n+ if (status !\u003d ERROR_SUCCESS) {\n+ lwsl_err(\u0022NCryptOpenStorageProvider failed 0x%x\u005cn\u0022, (int)status);\n+ lws_free(key_der);\n+ goto cleanup;\n+ }\n+\n+ /* Import Key */\n+ /* We have DER. NCryptImportKey supports NCRYPT_PKCS8_PRIVATE_KEY_BLOB */\n+ /* Note: If the PEM was \u0022EC PRIVATE KEY\u0022 (SEC1), CryptStringToBinary converted it to DER SEC1. */\n+ /* NCryptImportKey typically expects PKCS#8. If it is SEC1, we might need to wrap it? */\n+ /* Windows 10+ might support ECCPRIVATE_BLOB? */\n+ /* But generic \u0022Private Key\u0022 usually implies PKCS#8. */\n+ /* Let's try importing as PKCS8 first. */\n+\n+ DWORD flags \u003d 0;\n+ if (container_name) {\n+ /* We want a persisted key */\n+ /* NCryptImportKey takes a key name? */\n+ /* Yes, pszKeyName. */\n+ /* And we need NCRYPT_OVERWRITE_KEY_FLAG if it exists? */\n+ flags \u003d NCRYPT_OVERWRITE_KEY_FLAG;\n+ }\n+\n+ WCHAR wContainer[128];\n+ LPCWSTR keyName \u003d NULL;\n+ if (container_name) {\n+ if (MultiByteToWideChar(CP_UTF8, 0, container_name, -1, wContainer, sizeof(wContainer)/sizeof(wContainer[0]))) {\n+ keyName \u003d wContainer;\n+ }\n+ }\n+\n+ /* NCryptImportKey signature:\n+ (hProvider, hImportKey, pszBlobType, pParameterList, phKey, pbInput, cbInput, dwFlags)\n+ */\n+ if (container_name) {\n+ /* For persisted keys, we need to pass the key name property.\n+ However, NCryptImportKey into a named key usually requires specific steps or using NCryptCreatePersistedKey.\n+ Wait, if we use NCryptImportKey with NCRYPT_OVERWRITE_KEY_FLAG and a key name, how do we pass the key name?\n+ Docs say: \u0022The behavior of this function is consistent with the NCryptCreatePersistedKey function...\u0022.\n+ NCryptCreatePersistedKey takes pszKeyName directly.\n+ NCryptImportKey does NOT take pszKeyName directly in the signature.\n+\n+ Actually, to import a named key, we should:\n+ 1. Create a parameter list with NCRYPT_KEY_NAME_PROPERTY (L\u0022Name\u0022).\n+ */\n+ NCryptBuffer nameBuf;\n+ NCryptBufferDesc nameDesc;\n+\n+ nameBuf.cbBuffer \u003d (ULONG)((wcslen(wContainer) + 1) * sizeof(WCHAR));\n+ nameBuf.BufferType \u003d NCRYPTBUFFER_PKCS_KEY_NAME;\n+ nameBuf.pvBuffer \u003d wContainer;\n+\n+ nameDesc.ulVersion \u003d NCRYPTBUFFER_VERSION;\n+ nameDesc.cBuffers \u003d 1;\n+ nameDesc.pBuffers \u003d \u0026nameBuf;\n+\n+ status \u003d NCryptImportKey(hProvCNG, 0, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, \u0026nameDesc, \u0026hKeyCNG, (PUCHAR)key_der, (DWORD)key_der_len, flags);\n+ } else {\n+ status \u003d NCryptImportKey(hProvCNG, 0, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, NULL, \u0026hKeyCNG, (PUCHAR)key_der, (DWORD)key_der_len, flags);\n+ }\n+\n+ if (status !\u003d ERROR_SUCCESS) {\n+ /* Maybe it is SEC1 (EC PRIVATE KEY) and not PKCS#8? */\n+ /* Trying to wrap SEC1 into PKCS#8 manually is hard. */\n+ /* However, CryptImportPKCS8 is CAPI. */\n+ lwsl_err(\u0022NCryptImportKey (PKCS8) failed 0x%x. Note: EC SEC1 keys not auto-converted.\u005cn\u0022, (int)status);\n+ NCryptFreeObject(hProvCNG);\n+ lws_free(key_der);\n+ goto cleanup;\n+ }\n+ lws_free(key_der);\n+\n+ /* Link to Cert */\n+ if (container_name) {\n+ /* Named Key: Use PROV_INFO with CNG Provider */\n+ CRYPT_KEY_PROV_INFO kpi \u003d {0};\n+ kpi.pwszContainerName \u003d wContainer;\n+ kpi.pwszProvName \u003d MS_KEY_STORAGE_PROVIDER;\n+ kpi.dwProvType \u003d 0; /* CNG */\n+ kpi.dwFlags \u003d 0;\n+ kpi.dwKeySpec \u003d 0; /* CNG keys have no KeySpec usually, or 0 */\n+\n+ if (!CertSetCertificateContextProperty(x509_obj.cert, CERT_KEY_PROV_INFO_PROP_ID, 0, (void*)\u0026kpi)) {\n+ lwsl_err(\u0022CertSetCertificateContextProperty (CNG ProvInfo) failed 0x%x\u005cn\u0022, GetLastError());\n+ NCryptFreeObject(hKeyCNG); // Free key handle?\n+ /* If we persist it, we close the handle but the key remains in storage. */\n+ NCryptFreeObject(hProvCNG);\n+ goto cleanup;\n+ }\n+\n+ /* Clean up handles */\n+ NCryptFreeObject(hKeyCNG);\n+ NCryptFreeObject(hProvCNG);\n+\n+ } else {\n+ /* Ephemeral: Use CERT_KEY_PROV_HANDLE_PROP_ID with NCRYPT_KEY_HANDLE_PROP_ID? */\n+ /* Actually for CNG, we use CERT_NCRYPT_KEY_HANDLE_PROP_ID */\n+ if (!CertSetCertificateContextProperty(x509_obj.cert, CERT_NCRYPT_KEY_HANDLE_PROP_ID, 0, (void*)hKeyCNG)) {\n+ lwsl_err(\u0022CertSetCertificateContextProperty (NCrypt Handle) failed 0x%x\u005cn\u0022, GetLastError());\n+ NCryptFreeObject(hKeyCNG);\n+ NCryptFreeObject(hProvCNG);\n+ goto cleanup;\n+ }\n+ /* We must keep the handle open? Or does Cert context take ownership? */\n+ /* For NCRYPT_KEY_HANDLE_PROP_ID, the doc says: \u0022This property value is an NCRYPT_KEY_HANDLE data type.\u0022 */\n+ /* Usually we don't close it if we want the cert to use it? */\n+ /* Actually, if we pass the handle, we should be careful. */\n+ /* But wait, my CAPI logic closes the key handle but keeps the provider. */\n+ /* For CNG, the Key Handle IS the object. */\n+ /* Let's try NOT freeing hKeyCNG here for ephemeral case, but free hProvCNG? */\n+ /* NCrypt handles are independent? */\n+ NCryptFreeObject(hProvCNG);\n+ /* We do NOT free hKeyCNG if successful, it is attached to cert? */\n+ /* Actually, CertSetCertificateContextProperty adds a *property*. It doesn't take ownership of the handle. */\n+ /* But if we close the handle, the property becomes invalid? */\n+ /* Yes. So we must leak/keep hKeyCNG for the lifetime of the cert. */\n+ /* But we return pcert \u003d x509_obj.cert. */\n+ /* The caller will free pcert. */\n+ /* For CAPI, the Prov Handle is kept open. */\n+ /* For CNG, we need to keep the Key Handle open. */\n+ /* The lws_tls_schannel_cert_info_load signature returns HCRYPTPROV* phProv. */\n+ /* It does not have a slot for NCRYPT_KEY_HANDLE. */\n+ /* This is a problem for Client (Ephemeral) EC. */\n+ /* BUT: Client usually doesn't need to persist beyond the connection setup. */\n+ /* However, for Server EC, we use Named container, so we close handles and use string property. */\n+\n+ /* Let's assume this patch is primarily for Server EC fix. */\n+ /* Client EC might work if we just return 0 for phProv (since it's not CAPI). */\n+\n+ /* IMPORTANT: For Ephemeral (Client), we can't return the CNG handle in HCRYPTPROV* phProv. */\n+ /* We need to store it in the context using the new key_type aware structure. */\n+ if (phKey) *phKey \u003d (void*)hKeyCNG;\n+ if (pKeyType) *pKeyType \u003d 1; /* CNG */\n+ }\n+\n+ *pcert \u003d x509_obj.cert;\n+ return 0;\n+ }\n+\n+ /* RSA Path (Legacy CAPI) */\n+ /* Parse DER to Key Elements */\n+ const uint8_t *kp \u003d key_der;\n+ const uint8_t *kend \u003d key_der + key_der_len;\n+ size_t seq_len;\n+\n+ if (kp \u003e\u003d kend || *kp++ !\u003d 0x30) { lws_free(key_der); goto cleanup; }\n+ if (lws_asn1_read_length(\u0026kp, kend, \u0026seq_len) \u003c 0) { lws_free(key_der); goto cleanup; }\n+\n+ /* Check for version */\n+ if (kp \u003e\u003d kend || *kp++ !\u003d 0x02) { lws_free(key_der); goto cleanup; }\n+ size_t ver_len;\n+ if (lws_asn1_read_length(\u0026kp, kend, \u0026ver_len) \u003c 0) { lws_free(key_der); goto cleanup; }\n+ kp +\u003d ver_len;\n+\n+ /* PKCS#8 check */\n+ if (kp \u003c kend \u0026\u0026 *kp \u003d\u003d 0x30) {\n+ size_t alg_len;\n+ kp++;\n+ if (lws_asn1_read_length(\u0026kp, kend, \u0026alg_len) \u003c 0) { lws_free(key_der); goto cleanup; }\n+ kp +\u003d alg_len;\n+ if (kp \u003e\u003d kend || *kp++ !\u003d 0x04) { lws_free(key_der); goto cleanup; }\n+ size_t oct_len;\n+ if (lws_asn1_read_length(\u0026kp, kend, \u0026oct_len) \u003c 0) { lws_free(key_der); goto cleanup; }\n+ if (kp \u003e\u003d kend || *kp++ !\u003d 0x30) { lws_free(key_der); goto cleanup; }\n+ if (lws_asn1_read_length(\u0026kp, kend, \u0026seq_len) \u003c 0) { lws_free(key_der); goto cleanup; }\n+ if (kp \u003e\u003d kend || *kp++ !\u003d 0x02) { lws_free(key_der); goto cleanup; }\n+ if (lws_asn1_read_length(\u0026kp, kend, \u0026ver_len) \u003c 0) { lws_free(key_der); goto cleanup; }\n+ kp +\u003d ver_len;\n+ } else if (kp \u003c kend \u0026\u0026 *kp \u003d\u003d 0x02) {\n+ /* PKCS#1: kp points to Modulus tag. Version already consumed. */\n+ } else {\n+ lws_free(key_der);\n+ goto cleanup;\n+ }\n+\n+ if (lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_N]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_E]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_D]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_P]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_Q]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_DP]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_DQ]) \u003c 0 ||\n+ lws_asn1_read_integer(\u0026kp, kend, \u0026e[LWS_GENCRYPTO_RSA_KEYEL_QI]) \u003c 0) {\n+ lws_free(key_der);\n+ lws_gencrypto_destroy_elements(e, LWS_GENCRYPTO_RSA_KEYEL_COUNT);\n+ goto cleanup;\n+ }\n+ lws_free(key_der);\n+\n+\t/* 3. Convert to PRIVATEKEYBLOB (CAPI) */\n+\t/* BLOBHEADER + RSAPUBKEY + Modulus + Prime1 + Prime2 + Exponent1 + Exponent2 + Coefficient + PrivateExponent */\n+\t/* CAPI uses Little Endian. */\n+\n+ uint32_t cbModulus \u003d e[LWS_GENCRYPTO_RSA_KEYEL_N].len;\n+ /* Ensure alignment to 8 bytes if needed? Usually just length */\n+ uint32_t bitlen \u003d cbModulus * 8;\n+ uint32_t cbPrime \u003d cbModulus / 2;\n+\n+ bloblen \u003d sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) +\n+ cbModulus +\n+ cbPrime + /* P */\n+ cbPrime + /* Q */\n+ cbPrime + /* DP */\n+ cbPrime + /* DQ */\n+ cbPrime + /* InverseQ (Coeff) */\n+ cbModulus; /* D */\n+\n+ rsablob \u003d lws_malloc(bloblen, \u0022rsablob capi\u0022);\n+ if (!rsablob) {\n+ lws_gencrypto_destroy_elements(e, LWS_GENCRYPTO_RSA_KEYEL_COUNT);\n+ goto cleanup;\n+ }\n+\n+ BLOBHEADER *blobHeader \u003d (BLOBHEADER *)rsablob;\n+ blobHeader-\u003ebType \u003d PRIVATEKEYBLOB;\n+ blobHeader-\u003ebVersion \u003d CUR_BLOB_VERSION;\n+ blobHeader-\u003ereserved \u003d 0;\n+ blobHeader-\u003eaiKeyAlg \u003d CALG_RSA_KEYX;\n+\n+ RSAPUBKEY *rsaPubKey \u003d (RSAPUBKEY *)(rsablob + sizeof(BLOBHEADER));\n+ rsaPubKey-\u003emagic \u003d 0x32415352; /* \u0022RSA2\u0022 for private key */\n+ rsaPubKey-\u003ebitlen \u003d bitlen;\n+ /* Public Exponent: usually small, fit in 4 bytes. e.g. 65537 */\n+ uint32_t pubExp \u003d 0;\n+ if (e[LWS_GENCRYPTO_RSA_KEYEL_E].len \u003c\u003d 4) {\n+ /* ASN.1 is Big Endian. Convert to int host order (usually LE on Windows) */\n+ for (uint32_t i \u003d 0; i \u003c e[LWS_GENCRYPTO_RSA_KEYEL_E].len; i++) {\n+ pubExp \u003d (pubExp \u003c\u003c 8) | e[LWS_GENCRYPTO_RSA_KEYEL_E].buf[i];\n+ }\n+ } else {\n+ /* Standard CAPI RSAPUBKEY has only DWORD pubexp. If larger, this structure is insufficient. */\n+ /* Assuming standard exp */\n+ pubExp \u003d 65537;\n+ }\n+ rsaPubKey-\u003epubexp \u003d pubExp; /* Already LE if assigned to uint32 */\n+\n+ uint8_t *p \u003d (uint8_t *)(rsaPubKey + 1);\n+\n+ /* Helper macro to copy and reverse (Big Endian -\u003e Little Endian) */\n+ /* And pad with zeros at the END (high bytes) if source is shorter than dest */\n+#define COPY_REVERSE(elem, size) \u005c\n+ do { \u005c\n+ uint32_t _len \u003d e[elem].len; \u005c\n+ uint32_t _size \u003d size; \u005c\n+ if (_len \u003e _size) _len \u003d _size; \u005c\n+ for (uint32_t i \u003d 0; i \u003c _len; i++) { \u005c\n+ p[(_len - 1) - i] \u003d e[elem].buf[i]; \u005c\n+ } \u005c\n+ if (_size \u003e _len) { \u005c\n+ memset(p + _len, 0, _size - _len); \u005c\n+ } \u005c\n+ p +\u003d _size; \u005c\n+ } while(0)\n+\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_N, cbModulus);\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_P, cbPrime);\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_Q, cbPrime);\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_DP, cbPrime);\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_DQ, cbPrime);\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_QI, cbPrime);\n+ COPY_REVERSE(LWS_GENCRYPTO_RSA_KEYEL_D, cbModulus);\n+\n+#undef COPY_REVERSE\n+\n+\tlws_gencrypto_destroy_elements(e, LWS_GENCRYPTO_RSA_KEYEL_COUNT);\n+\n+\t/* 4. Import Key (Legacy CAPI) */\n+ /* MS_ENH_RSA_AES_PROV type is PROV_RSA_AES, not PROV_RSA_FULL. Mismatch causes NTE_KEYSET_ENTRY_BAD. */\n+ if (container_name) {\n+ /* Use named container for persistence (needed for SChannel server) */\n+ /* First try to create new keyset */\n+ if (!CryptAcquireContext(\u0026hProv, container_name, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_SILENT)) {\n+ if (GetLastError() \u003d\u003d NTE_EXISTS) {\n+ /* Exists, open it */\n+ if (!CryptAcquireContext(\u0026hProv, container_name, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_SILENT)) {\n+ lwsl_err(\u0022CryptAcquireContext (open) failed 0x%x\u005cn\u0022, GetLastError());\n+ lws_free(rsablob);\n+ goto cleanup;\n+ }\n+ } else {\n+ lwsl_err(\u0022CryptAcquireContext (new) failed 0x%x\u005cn\u0022, GetLastError());\n+ lws_free(rsablob);\n+ goto cleanup;\n+ }\n+ }\n+ } else {\n+ /* Ephemeral (Client) */\n+ if (!CryptAcquireContext(\u0026hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {\n+ lwsl_err(\u0022CryptAcquireContext (ephemeral) failed 0x%x\u005cn\u0022, GetLastError());\n+ lws_free(rsablob);\n+ goto cleanup;\n+ }\n+ }\n+\n+ if (!CryptImportKey(hProv, rsablob, bloblen, 0, CRYPT_EXPORTABLE, \u0026hKey)) {\n+ lwsl_err(\u0022CryptImportKey failed 0x%x\u005cn\u0022, GetLastError());\n+ lws_free(rsablob);\n+ goto cleanup;\n+ }\n+ lws_free(rsablob);\n+\n+\t/* 5. Link Key to Cert (Legacy Property) */\n+ if (container_name) {\n+ /*\n+ * For Server (Named Container), we must tell SChannel where the key is using PROV_INFO.\n+ * SChannel runs in LSA and cannot always use the process-local handle we have.\n+ */\n+ CRYPT_KEY_PROV_INFO kpi \u003d {0};\n+ WCHAR wContainer[128];\n+\n+ /* Convert container name to Wide String */\n+ if (!MultiByteToWideChar(CP_UTF8, 0, container_name, -1, wContainer, sizeof(wContainer)/sizeof(wContainer[0]))) {\n+ lwsl_err(\u0022MultiByteToWideChar failed\u005cn\u0022);\n+ goto cleanup;\n+ }\n+\n+ kpi.pwszContainerName \u003d wContainer;\n+#ifdef MS_ENH_RSA_AES_PROV_W\n+ kpi.pwszProvName \u003d (LPWSTR)MS_ENH_RSA_AES_PROV_W;\n+#else\n+ kpi.pwszProvName \u003d (LPWSTR)L\u0022Microsoft Enhanced RSA and AES Cryptographic Provider\u0022;\n+#endif\n+ kpi.dwProvType \u003d PROV_RSA_AES;\n+ kpi.dwFlags \u003d 0;\n+ kpi.cProvParam \u003d 0;\n+ kpi.rgProvParam \u003d NULL;\n+ kpi.dwKeySpec \u003d AT_KEYEXCHANGE;\n+\n+ if (!CertSetCertificateContextProperty(x509_obj.cert, CERT_KEY_PROV_INFO_PROP_ID, 0, (void*)\u0026kpi)) {\n+ lwsl_err(\u0022CertSetCertificateContextProperty (ProvInfo) failed 0x%x\u005cn\u0022, GetLastError());\n+ goto cleanup;\n+ }\n+ } else {\n+ /*\n+ * For Client (Ephemeral), we use the handle directly.\n+ * Note: CERT_KEY_PROV_HANDLE_PROP_ID usage is process-specific but usually works for client auth\n+ * if the CSP supports it or if we are verifying context.\n+ */\n+ if (!CertSetCertificateContextProperty(x509_obj.cert, CERT_KEY_PROV_HANDLE_PROP_ID, 0, (void*)hProv)) {\n+ lwsl_err(\u0022CertSetCertificateContextProperty (ProvHandle) failed 0x%x\u005cn\u0022, GetLastError());\n+ goto cleanup;\n+ }\n+\n+ /* Explicitly set Key Spec to AT_KEYEXCHANGE (1) */\n+ DWORD keySpec \u003d AT_KEYEXCHANGE;\n+ if (!CertSetCertificateContextProperty(x509_obj.cert, CERT_KEY_SPEC_PROP_ID, 0, (void*)\u0026keySpec)) {\n+ lwsl_warn(\u0022CertSetCertificateContextProperty (KeySpec) failed 0x%x\u005cn\u0022, GetLastError());\n+ }\n+ }\n+\n+\t/* We can close the key handle, but MUST keep the provider handle open.\n+ The key is associated with the provider context.\n+ */\n+ if (hKey) {\n+ CryptDestroyKey(hKey);\n+ hKey \u003d 0;\n+ }\n+\n+\t*pcert \u003d x509_obj.cert;\n+\tret \u003d 0;\n+\n+cleanup:\n+ /* hKey is CryptDestroyKey for CAPI, but if we zeroed it, it's fine. */\n+ if (hKey) CryptDestroyKey(hKey);\n+ if (hProv) {\n+ if (!ret \u0026\u0026 phKey) {\n+ *phKey \u003d (void*)hProv;\n+ if (pKeyType) *pKeyType \u003d 0; /* CAPI */\n+ } else {\n+ CryptReleaseContext(hProv, 0);\n+ }\n+ }\n+ if (ret \u0026\u0026 x509_obj.cert) CertFreeCertificateContext(x509_obj.cert);\n+ if (ret \u0026\u0026 phStore \u0026\u0026 *phStore) {\n+ CertCloseStore(*phStore, 0);\n+ *phStore \u003d NULL;\n+ }\n+\n+ return ret;\n+}\ndiff --git a/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c b/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c\nindex d2c0ab2..c538c43 100644\n--- a/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c\n+++ b/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c\n@@ -658,7 +658,7 @@ bail:\n }\n #endif\n \n-#if !defined(LWS_WITH_SCHANNEL)\n+//#if !defined(LWS_WITH_SCHANNEL)\n static const uint8_t\n \t/*\n \t * https://csrc.nist.gov/CSRC/media/Projects/\n@@ -773,7 +773,7 @@ bail:\n \n \treturn -1;\n }\n-#endif\n+//#endif\n \n int\n test_genaes(struct lws_context *context)\n@@ -817,10 +817,10 @@ test_genaes(struct lws_context *context)\n \t\tgoto bail;\n #endif\n \n-#if !defined(LWS_WITH_SCHANNEL)\n+//#if !defined(LWS_WITH_SCHANNEL)\n \tif (test_genaes_gcm())\n \t\tgoto bail;\n-#endif\n+//#endif\n \n \t/* end */\n \n","s":{"c":1766021946,"u": 18646}}
],"g": 23751,"chitpc": 0,"ehitpc": 0,"indexed":0
,
"ab": 0, "si": 0, "db":0, "di":0, "sat":0, "lfc": "0000"}