static int _rngf(void *context, unsigned char *buf, size_t len) { if ((size_t)lws_get_random(context, buf, len) == len) return 0; return -1; }
char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { char buf[128], hash[20], key_b64[40], *p = pkt; struct lws_context *context = wsi->context; int n; #ifndef LWS_NO_EXTENSIONS const struct lws_extension *ext; int ext_count = 0; #endif /* * create the random key */ n = lws_get_random(context, hash, 16); if (n != 16) { lwsl_err("Unable to read from random dev %s\n", SYSTEM_RANDOM_FILEPATH); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; } lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); /* * 00 example client handshake * * GET /socket.io/websocket HTTP/1.1 * Upgrade: WebSocket * Connection: Upgrade * Host: 127.0.0.1:9999 * Origin: http://127.0.0.1 * Sec-WebSocket-Key1: 1 0 2#0W 9 89 7 92 ^ * Sec-WebSocket-Key2: 7 7Y 4328 B2v[8(z1 * Cookie: socketio=websocket * * (Á®Ä0¶†≥ * * 04 example client handshake * * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 4 */ p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); p += sprintf(p, "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); p += sprintf(p, "Host: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); p += sprintf(p, "Upgrade: websocket\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Sec-WebSocket-Key: "); strcpy(p, key_b64); p += strlen(key_b64); p += sprintf(p, "\x0d\x0a"); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) p += sprintf(p, "Origin: http://%s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); /* tell the server what extensions we could support */ p += sprintf(p, "Sec-WebSocket-Extensions: "); #ifndef LWS_NO_EXTENSIONS ext = context->extensions; while (ext && ext->callback) { n = lws_ext_cb_all_exts(context, wsi, LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, (char *)ext->name, 0); if (n) { /* an extension vetos us */ lwsl_ext("ext %s vetoed\n", (char *)ext->name); ext++; continue; } n = context->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, wsi->user_space, (char *)ext->name, 0); /* * zero return from callback means * go ahead and allow the extension, * it's what we get if the callback is * unhandled */ if (n) { ext++; continue; } /* apply it */ if (ext_count) *p++ = ','; p += sprintf(p, "%s", ext->client_offer); ext_count++; ext++; } #endif p += sprintf(p, "\x0d\x0a"); if (wsi->ietf_spec_revision) p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", wsi->ietf_spec_revision); /* give userland a chance to append, eg, cookies */ context->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, NULL, &p, (pkt + LWS_MAX_SOCKET_IO_BUF) - p - 12); p += sprintf(p, "\x0d\x0a"); /* prepare the expected server accept response */ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); lws_b64_encode_string(hash, 20, wsi->u.hdr.ah->initial_handshake_hash_base64, sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); return p; }
static int callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4096]; unsigned int rands[4]; int l = 0; int n; switch (reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: lwsl_notice("mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n"); lws_get_random(lws_get_context(wsi), rands, sizeof(rands[0])); mirror_lifetime = 16384 + (rands[0] & 65535); /* useful to test single connection stability */ if (longlived) mirror_lifetime += 500000; lwsl_info("opened mirror connection with " "%d lifetime\n", mirror_lifetime); /* * mirror_lifetime is decremented each send, when it reaches * zero the connection is closed in the send callback. * When the close callback comes, wsi_mirror is set to NULL * so a new connection will be opened * * start the ball rolling, * LWS_CALLBACK_CLIENT_WRITEABLE will come next service */ lws_callback_on_writable(wsi); break; case LWS_CALLBACK_CLOSED: lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime); wsi_mirror = NULL; break; case LWS_CALLBACK_CLIENT_WRITEABLE: for (n = 0; n < 1; n++) { lws_get_random(lws_get_context(wsi), rands, sizeof(rands)); l += sprintf((char *)&buf[LWS_SEND_BUFFER_PRE_PADDING + l], "c #%06X %d %d %d;", (int)rands[0] & 0xffffff, (int)rands[1] % 500, (int)rands[2] % 250, (int)rands[3] % 24); } n = lws_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], l, opts | LWS_WRITE_TEXT); if (n < 0) return -1; if (n < l) { lwsl_err("Partial write LWS_CALLBACK_CLIENT_WRITEABLE\n"); return -1; } mirror_lifetime--; if (!mirror_lifetime) { lwsl_info("closing mirror session\n"); return -1; } /* get notified as soon as we can write again */ lws_callback_on_writable(wsi); break; default: break; } return 0; }
LWS_VISIBLE LWS_EXTERN int libwebsockets_get_random(struct lws_context *context, void *buf, int len) { return lws_get_random(context, buf, len); }
char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { char buf[128], hash[20], key_b64[40], *p = pkt; struct lws_context *context = wsi->context; const char *meth; int n; #ifndef LWS_NO_EXTENSIONS const struct lws_extension *ext; int ext_count = 0; #endif const char *pp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (!meth) { meth = "GET"; wsi->do_ws = 1; } else { wsi->do_ws = 0; } if (!strcmp(meth, "RAW")) { lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); lwsl_notice("client transition to raw\n"); if (pp) { const struct lws_protocols *pr; pr = lws_vhost_name_to_protocol(wsi->vhost, pp); if (!pr) { lwsl_err("protocol %s not enabled on vhost\n", pp); return NULL; } lws_bind_protocol(wsi, pr); } if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, wsi->user_space, NULL, 0)) return NULL; lws_header_table_force_to_detachable_state(wsi); lws_union_transition(wsi, LWSCM_RAW); lws_header_table_detach(wsi, 1); return NULL; } if (wsi->do_ws) { /* * create the random key */ n = lws_get_random(context, hash, 16); if (n != 16) { lwsl_err("Unable to read from random dev %s\n", SYSTEM_RANDOM_FILEPATH); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; } lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); } /* * 04 example client handshake * * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 4 */ p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); p += sprintf(p, "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); p += sprintf(p, "Host: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) p += sprintf(p, "Origin: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); else p += sprintf(p, "Origin: http://%s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); } if (wsi->do_ws) { p += sprintf(p, "Upgrade: websocket\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Sec-WebSocket-Key: "); strcpy(p, key_b64); p += strlen(key_b64); p += sprintf(p, "\x0d\x0a"); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); /* tell the server what extensions we could support */ #ifndef LWS_NO_EXTENSIONS ext = wsi->vhost->extensions; while (ext && ext->callback) { n = lws_ext_cb_all_exts(context, wsi, LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, (char *)ext->name, 0); if (n) { /* an extension vetos us */ lwsl_ext("ext %s vetoed\n", (char *)ext->name); ext++; continue; } n = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, wsi->user_space, (char *)ext->name, 0); /* * zero return from callback means * go ahead and allow the extension, * it's what we get if the callback is * unhandled */ if (n) { ext++; continue; } /* apply it */ if (ext_count) *p++ = ','; else p += sprintf(p, "Sec-WebSocket-Extensions: "); p += sprintf(p, "%s", ext->client_offer); ext_count++; ext++; } if (ext_count) p += sprintf(p, "\x0d\x0a"); #endif if (wsi->ietf_spec_revision) p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", wsi->ietf_spec_revision); /* prepare the expected server accept response */ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); lws_b64_encode_string(hash, 20, wsi->u.hdr.ah->initial_handshake_hash_base64, sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); } /* give userland a chance to append, eg, cookies */ wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12); p += sprintf(p, "\x0d\x0a"); return p; }
int kex_ecdh(struct per_session_data__sshd *pss, uint8_t *reply, uint32_t *plen) { uint8_t pri_key[64], temp[64], payload_sig[64 + 32], a, *lp, kbi[64]; struct lws_kex *kex = pss->kex; struct lws_genhash_ctx ctx; unsigned long long smlen; uint8_t *p = reply + 5; uint32_t be, kbi_len; uint8_t servkey[256]; char keyt[33]; int r, c; r = get_gen_server_key_25519(pss, servkey, sizeof(servkey)); if (!r) { lwsl_err("%s: Failed to get or gen server key\n", __func__); return 1; } r = ed25519_key_parse(servkey, r, keyt, sizeof(keyt), pss->K_S /* public key */, pri_key); if (r) { lwsl_notice("%s: server key parse failed: %d\n", __func__, r); return 1; } keyt[32] = '\0'; lwsl_info("Server key type: %s\n", keyt); /* * 1) Generate ephemeral key pair [ eph_pri_key | kex->Q_S ] * 2) Compute shared secret. * 3) Generate and sign exchange hash. * * 1) A 32 bytes private key should be generated for each new * connection, using a secure PRNG. The following actions * must be done on the private key: * * mysecret[0] &= 248; * mysecret[31] &= 127; * mysecret[31] |= 64; */ lws_get_random(pss->vhd->context, kex->eph_pri_key, LWS_SIZE_EC25519); kex->eph_pri_key[0] &= 248; kex->eph_pri_key[31] &= 127; kex->eph_pri_key[31] |= 64; /* * 2) The public key is calculated using the cryptographic scalar * multiplication: * * const unsigned char privkey[32]; * unsigned char pubkey[32]; * * crypto_scalarmult (pubkey, privkey, basepoint); */ crypto_scalarmult_curve25519(kex->Q_S, kex->eph_pri_key, basepoint); a = 0; for (r = 0; r < sizeof(kex->Q_S); r++) a |= kex->Q_S[r]; if (!a) { lwsl_notice("all zero pubkey\n"); return SSH_DISCONNECT_KEY_EXCHANGE_FAILED; } /* * The shared secret, k, is defined in SSH specifications to be a big * integer. This number is calculated using the following procedure: * * X is the 32 bytes point obtained by the scalar multiplication of * the other side's public key and the local private key scalar. */ crypto_scalarmult_curve25519(pss->K, kex->eph_pri_key, kex->Q_C); /* * The whole 32 bytes of the number X are then converted into a big * integer k. This conversion follows the network byte order. This * step differs from RFC5656. */ kbi_len = lws_mpint_rfc4251(kbi, pss->K, LWS_SIZE_EC25519, 1); /* * The exchange hash H is computed as the hash of the concatenation of * the following: * * string V_C, the client's identification string (CR and LF * excluded) * string V_S, the server's identification string (CR and LF * excluded) * string I_C, the payload of the client's SSH_MSG_KEXINIT * string I_S, the payload of the server's SSH_MSG_KEXINIT * string K_S, the host key * mpint Q_C, exchange value sent by the client * mpint Q_S, exchange value sent by the server * mpint K, the shared secret * * However there are a lot of unwritten details in the hash * definition... */ if (lws_genhash_init(&ctx, LWS_GENHASH_TYPE_SHA256)) { lwsl_notice("genhash init failed\n"); return 1; } if (_genhash_update_len(&ctx, pss->V_C, strlen(pss->V_C))) goto hash_probs; if (_genhash_update_len(&ctx, pss->vhd->ops->server_string, /* aka V_S */ strlen(pss->vhd->ops->server_string))) goto hash_probs; if (_genhash_update_len(&ctx, kex->I_C, kex->I_C_payload_len)) goto hash_probs; if (_genhash_update_len(&ctx, kex->I_S, kex->I_S_payload_len)) goto hash_probs; /* * K_S (host public key) * * sum of name + key lengths and headers * name length: name * key length: key * ---> */ lws_p32((uint8_t *)&be, 8 + strlen(keyt) + LWS_SIZE_EC25519); if (lws_genhash_update(&ctx, (void *)&be, 4)) goto hash_probs; if (_genhash_update_len(&ctx, keyt, strlen(keyt))) goto hash_probs; if (_genhash_update_len(&ctx, pss->K_S, LWS_SIZE_EC25519)) goto hash_probs; /* <---- */ if (_genhash_update_len(&ctx, kex->Q_C, LWS_SIZE_EC25519)) goto hash_probs; if (_genhash_update_len(&ctx, kex->Q_S, LWS_SIZE_EC25519)) goto hash_probs; if (lws_genhash_update(&ctx, kbi, kbi_len)) goto hash_probs; if (lws_genhash_destroy(&ctx, temp)) goto hash_probs; /* * Sign the 32-byte SHA256 "exchange hash" in temp * The signature is itself 64 bytes */ smlen = LWS_SIZE_EC25519 + 64; if (crypto_sign_ed25519(payload_sig, &smlen, temp, LWS_SIZE_EC25519, pri_key)) return 1; #if 0 l = LWS_SIZE_EC25519; n = crypto_sign_ed25519_open(temp, &l, payload_sig, smlen, pss->K_S); lwsl_notice("own sig sanity check says %d\n", n); #endif /* sig [64] and payload [32] concatenated in payload_sig * * The server then responds with the following * * uint32 packet length (exl self + mac) * byte padding len * byte SSH_MSG_KEX_ECDH_REPLY * string server public host key and certificates (K_S) * string Q_S (exchange value sent by the server) * string signature of H * padding */ *p++ = SSH_MSG_KEX_ECDH_REPLY; /* server public host key and certificates (K_S) */ lp = p; p +=4; lws_sized_blob(&p, keyt, strlen(keyt)); lws_sized_blob(&p, pss->K_S, LWS_SIZE_EC25519); lws_p32(lp, p - lp - 4); /* Q_S (exchange value sent by the server) */ lws_sized_blob(&p, kex->Q_S, LWS_SIZE_EC25519); /* signature of H */ lp = p; p +=4; lws_sized_blob(&p, keyt, strlen(keyt)); lws_sized_blob(&p, payload_sig, 64); lws_p32(lp, p - lp - 4); /* end of message */ lws_pad_set_length(pss, reply, &p, &pss->active_keys_stc); *plen = p - reply; if (!pss->active_keys_stc.valid) memcpy(pss->session_id, temp, LWS_SIZE_EC25519); /* RFC4253 7.2: * * The key exchange produces two values: a shared secret K, * and an exchange hash H. Encryption and authentication * keys are derived from these. The exchange hash H from the * first key exchange is additionally used as the session * identifier, which is a unique identifier for this connection. * It is used by authentication methods as a part of the data * that is signed as a proof of possession of a private key. * Once computed, the session identifier is not changed, * even if keys are later re-exchanged. * * The hash alg used in the KEX must be used for key derivation. * * 1) Initial IV client to server: * * HASH(K || H || "A" || session_id) * * (Here K is encoded as mpint and "A" as byte and session_id * as raw data. "A" means the single character A, ASCII 65). * * */ for (c = 0; c < 3; c++) { kex_ecdh_dv(kex->keys_next_cts.key[c], LWS_SIZE_CHACHA256_KEY, kbi, kbi_len, temp, 'A' + (c * 2), pss->session_id); kex_ecdh_dv(kex->keys_next_stc.key[c], LWS_SIZE_CHACHA256_KEY, kbi, kbi_len, temp, 'B' + (c * 2), pss->session_id); } explicit_bzero(temp, sizeof(temp)); return 0; hash_probs: lws_genhash_destroy(&ctx, NULL); return 1; }
char * lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1) { char buf[128], hash[20], key_b64[40]; int n; #if !defined(LWS_WITHOUT_EXTENSIONS) const struct lws_extension *ext; int ext_count = 0; #endif /* * create the random key */ n = lws_get_random(wsi->context, hash, 16); if (n != 16) { lwsl_err("Unable to read from random dev %s\n", SYSTEM_RANDOM_FILEPATH); return NULL; } lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); p += sprintf(p, "Upgrade: websocket\x0d\x0a" "Connection: %sUpgrade\x0d\x0a" "Sec-WebSocket-Key: ", conn1); strcpy(p, key_b64); p += strlen(key_b64); p += sprintf(p, "\x0d\x0a"); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); /* tell the server what extensions we could support */ #if !defined(LWS_WITHOUT_EXTENSIONS) ext = wsi->vhost->ws.extensions; while (ext && ext->callback) { n = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, wsi->user_space, (char *)ext->name, 0); /* * zero return from callback means go ahead and allow * the extension, it's what we get if the callback is * unhandled */ if (n) { ext++; continue; } /* apply it */ if (ext_count) *p++ = ','; else p += sprintf(p, "Sec-WebSocket-Extensions: "); p += sprintf(p, "%s", ext->client_offer); ext_count++; ext++; } if (ext_count) p += sprintf(p, "\x0d\x0a"); #endif if (wsi->ws->ietf_spec_revision) p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", wsi->ws->ietf_spec_revision); /* prepare the expected server accept response */ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); lws_b64_encode_string(hash, 20, wsi->http.ah->initial_handshake_hash_base64, sizeof(wsi->http.ah->initial_handshake_hash_base64)); return p; }