diff --git a/documentation/docs/globals/FetchEvent/FetchEvent.mdx b/documentation/docs/globals/FetchEvent/FetchEvent.mdx index f380d715da..a582fd7566 100644 --- a/documentation/docs/globals/FetchEvent/FetchEvent.mdx +++ b/documentation/docs/globals/FetchEvent/FetchEvent.mdx @@ -31,6 +31,14 @@ It provides the [`event.respondWith()`](./prototype/respondWith.mdx) method, whi - : Either `null` or an ArrayBuffer containing the raw client certificate in the mutual TLS handshake message. It is in PEM format. Returns an empty ArrayBuffer if this is not mTLS or available. - `FetchEvent.client.tlsClientHello` _**readonly**_ - : Either `null` or an ArrayBuffer containing the raw bytes sent by the client in the TLS ClientHello message. + - `FetchEvent.client.tlsJA4` _**readonly**_ + - : Either `null` or a string representation of the JA4 fingerprint of the TLS ClientHello message. + - `FetchEvent.client.h2Fingerprint` _**readonly**_ + - : Either `null` or a string representation of the HTTP/2 fingerprint for HTTP/2 connections. Returns `null` for HTTP/1.1 connections. + - `FetchEvent.client.ohFingerprint` _**readonly**_ + - : Either `null` or a string representation of the Original Header fingerprint based on the order and presence of request headers. + - `FetchEvent.client.tlsClientServername` _**readonly**_ + - : Either `null` or a string representation of the SNI (Server Name Indication) hostname from the TLS handshake. - `FetchEvent.server` _**readonly**_ - : Information about the server receiving the request for the Fastly Compute service. - `FetchEvent.server.address` _**readonly**_ diff --git a/integration-tests/js-compute/fixtures/app/src/client.js b/integration-tests/js-compute/fixtures/app/src/client.js index d41b1fc9bc..5c7624119d 100644 --- a/integration-tests/js-compute/fixtures/app/src/client.js +++ b/integration-tests/js-compute/fixtures/app/src/client.js @@ -74,3 +74,53 @@ routes.set('/client/tlsProtocol', (event) => { ); } }); + +routes.set('/client/tlsJA4', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.tlsJA4, null); + } else { + strictEqual( + typeof event.client.tlsJA4, + 'string', + 'typeof event.client.tlsJA4', + ); + } +}); + +routes.set('/client/h2Fingerprint', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.h2Fingerprint, null); + } else { + // h2Fingerprint may be null for HTTP/1.1 connections + const fp = event.client.h2Fingerprint; + strictEqual( + fp === null || typeof fp === 'string', + true, + 'event.client.h2Fingerprint is null or string', + ); + } +}); + +routes.set('/client/ohFingerprint', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.ohFingerprint, null); + } else { + strictEqual( + typeof event.client.ohFingerprint, + 'string', + 'typeof event.client.ohFingerprint', + ); + } +}); + +routes.set('/client/tlsClientServername', (event) => { + if (isRunningLocally()) { + strictEqual(event.client.tlsClientServername, null); + } else { + strictEqual( + typeof event.client.tlsClientServername, + 'string', + 'typeof event.client.tlsClientServername', + ); + } +}); diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 6c4763e156..cc81ece127 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -449,6 +449,10 @@ "GET /client/tlsClientCertificate": {}, "GET /client/tlsCipherOpensslName": {}, "GET /client/tlsProtocol": {}, + "GET /client/tlsJA4": {}, + "GET /client/h2Fingerprint": {}, + "GET /client/ohFingerprint": {}, + "GET /client/tlsClientServername": {}, "GET /config-store": { "flake": true }, diff --git a/runtime/fastly/builtins/fetch-event.cpp b/runtime/fastly/builtins/fetch-event.cpp index 835c7f73ee..94fa28b695 100644 --- a/runtime/fastly/builtins/fetch-event.cpp +++ b/runtime/fastly/builtins/fetch-event.cpp @@ -48,6 +48,22 @@ JSString *ja3(JSObject *obj) { JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::JA3)); return val.isString() ? val.toString() : nullptr; } +JSString *ja4(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::JA4)); + return val.isString() ? val.toString() : nullptr; +} +JSString *h2Fingerprint(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::H2Fingerprint)); + return val.isString() ? val.toString() : nullptr; +} +JSString *ohFingerprint(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::OHFingerprint)); + return val.isString() ? val.toString() : nullptr; +} +JSString *servername(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::Servername)); + return val.isString() ? val.toString() : nullptr; +} JSObject *clientHello(JSObject *obj) { JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::ClientHello)); return val.isObject() ? val.toObjectOrNull() : nullptr; @@ -61,8 +77,14 @@ JSString *protocol(JSObject *obj) { return val.isString() ? val.toString() : nullptr; } +host_api::HttpReq get_request_handle(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ClientInfo::Slots::RequestHandle)); + return host_api::HttpReq{static_cast(val.toInt32())}; +} + static JSString *retrieve_client_address(JSContext *cx, JS::HandleObject self) { - auto res = host_api::HttpReq::downstream_client_ip_addr(); + auto req = get_request_handle(self); + auto res = req.downstream_client_ip_addr(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return nullptr; @@ -159,7 +181,8 @@ bool ClientInfo::tls_cipher_openssl_name_get(JSContext *cx, unsigned argc, JS::V JS::RootedString result(cx, cipher(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_cipher_openssl_name(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_cipher_openssl_name(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -185,7 +208,8 @@ bool ClientInfo::tls_ja3_md5_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedString result(cx, ja3(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_ja3_md5(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_ja3_md5(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -208,12 +232,91 @@ bool ClientInfo::tls_ja3_md5_get(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } +bool ClientInfo::tls_ja4_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, ja4(self)); + if (!result) { + auto req = get_request_handle(self); + auto res = req.downstream_tls_ja4(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto ja4_str = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, ja4_str.ptr.get(), ja4_str.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::JA4), + JS::StringValue(result)); + } + args.rval().setString(result); + return true; +} + +bool ClientInfo::h2_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, h2Fingerprint(self)); + if (!result) { + auto req = get_request_handle(self); + auto res = req.downstream_client_h2_fingerprint(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto h2fp_str = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, h2fp_str.ptr.get(), h2fp_str.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::H2Fingerprint), + JS::StringValue(result)); + } + args.rval().setString(result); + return true; +} + +bool ClientInfo::oh_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, ohFingerprint(self)); + if (!result) { + auto req = get_request_handle(self); + auto res = req.downstream_client_oh_fingerprint(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto ohfp_str = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, ohfp_str.ptr.get(), ohfp_str.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::OHFingerprint), + JS::StringValue(result)); + } + args.rval().setString(result); + return true; +} + bool ClientInfo::tls_client_hello_get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(0); JS::RootedObject buffer(cx, clientHello(self)); if (!buffer) { - auto res = host_api::HttpReq::http_req_downstream_tls_client_hello(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_client_hello(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -248,7 +351,8 @@ bool ClientInfo::tls_client_certificate_get(JSContext *cx, unsigned argc, JS::Va JS::RootedObject buffer(cx, clientCert(self)); if (!buffer) { - auto res = host_api::HttpReq::http_req_downstream_tls_raw_client_certificate(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_raw_client_certificate(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -284,7 +388,8 @@ bool ClientInfo::tls_protocol_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedString result(cx, protocol(self)); if (!result) { - auto res = host_api::HttpReq::http_req_downstream_tls_protocol(); + auto req = get_request_handle(self); + auto res = req.downstream_tls_protocol(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -305,6 +410,33 @@ bool ClientInfo::tls_protocol_get(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } +bool ClientInfo::tls_client_servername_get(JSContext *cx, unsigned argc, JS::Value *vp) { + METHOD_HEADER(0); + + JS::RootedString result(cx, servername(self)); + if (!result) { + auto req = get_request_handle(self); + auto res = req.downstream_tls_client_servername(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (!res.unwrap().has_value()) { + args.rval().setNull(); + return true; + } + + auto sni = std::move(res.unwrap().value()); + result.set(JS_NewStringCopyN(cx, sni.ptr.get(), sni.len)); + JS::SetReservedSlot(self, static_cast(ClientInfo::Slots::Servername), + JS::StringValue(result)); + } + + args.rval().setString(result); + return true; +} + const JSFunctionSpec ClientInfo::static_methods[] = { JS_FS_END, }; @@ -322,14 +454,23 @@ const JSPropertySpec ClientInfo::properties[] = { JS_PSG("geo", geo_get, JSPROP_ENUMERATE), JS_PSG("tlsCipherOpensslName", tls_cipher_openssl_name_get, JSPROP_ENUMERATE), JS_PSG("tlsProtocol", tls_protocol_get, JSPROP_ENUMERATE), + JS_PSG("tlsClientServername", tls_client_servername_get, JSPROP_ENUMERATE), JS_PSG("tlsJA3MD5", tls_ja3_md5_get, JSPROP_ENUMERATE), + JS_PSG("tlsJA4", tls_ja4_get, JSPROP_ENUMERATE), + JS_PSG("h2Fingerprint", h2_fingerprint_get, JSPROP_ENUMERATE), + JS_PSG("ohFingerprint", oh_fingerprint_get, JSPROP_ENUMERATE), JS_PSG("tlsClientCertificate", tls_client_certificate_get, JSPROP_ENUMERATE), JS_PSG("tlsClientHello", tls_client_hello_get, JSPROP_ENUMERATE), JS_PS_END, }; -JSObject *ClientInfo::create(JSContext *cx) { - return JS_NewObjectWithGivenProto(cx, &class_, proto_obj); +JSObject *ClientInfo::create(JSContext *cx, uint32_t req_handle) { + JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + if (!obj) + return nullptr; + JS::SetReservedSlot(obj, static_cast(Slots::RequestHandle), + JS::Int32Value(req_handle)); + return obj; } namespace { @@ -339,8 +480,14 @@ JSString *server_address(JSObject *obj) { return val.isString() ? val.toString() : nullptr; } +host_api::HttpReq get_server_request_handle(JSObject *obj) { + JS::Value val = JS::GetReservedSlot(obj, static_cast(ServerInfo::Slots::RequestHandle)); + return host_api::HttpReq{static_cast(val.toInt32())}; +} + static JSString *retrieve_server_address(JSContext *cx, JS::HandleObject self) { - auto res = host_api::HttpReq::downstream_server_ip_addr(); + auto req = get_server_request_handle(self); + auto res = req.downstream_server_ip_addr(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return nullptr; @@ -389,8 +536,13 @@ const JSPropertySpec ServerInfo::properties[] = { JS_PS_END, }; -JSObject *ServerInfo::create(JSContext *cx) { - return JS_NewObjectWithGivenProto(cx, &class_, proto_obj); +JSObject *ServerInfo::create(JSContext *cx, uint32_t req_handle) { + JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + if (!obj) + return nullptr; + JS::SetReservedSlot(obj, static_cast(Slots::RequestHandle), + JS::Int32Value(req_handle)); + return obj; } namespace { @@ -452,7 +604,10 @@ bool FetchEvent::client_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::GetReservedSlot(self, static_cast(Slots::ClientInfo))); if (clientInfo.isUndefined()) { - JS::RootedObject obj(cx, ClientInfo::create(cx)); + JS::RootedObject request( + cx, &JS::GetReservedSlot(self, static_cast(Slots::Request)).toObject()); + uint32_t req_handle = Request::request_handle(request).handle; + JS::RootedObject obj(cx, ClientInfo::create(cx, req_handle)); if (!obj) return false; clientInfo.setObject(*obj); @@ -470,7 +625,10 @@ bool FetchEvent::server_get(JSContext *cx, unsigned argc, JS::Value *vp) { JS::GetReservedSlot(self, static_cast(Slots::ServerInfo))); if (serverInfo.isUndefined()) { - JS::RootedObject obj(cx, ServerInfo::create(cx)); + JS::RootedObject request( + cx, &JS::GetReservedSlot(self, static_cast(Slots::Request)).toObject()); + uint32_t req_handle = Request::request_handle(request).handle; + JS::RootedObject obj(cx, ServerInfo::create(cx, req_handle)); if (!obj) { return false; } diff --git a/runtime/fastly/builtins/fetch-event.h b/runtime/fastly/builtins/fetch-event.h index 27f2348e0d..c398b4cd01 100644 --- a/runtime/fastly/builtins/fetch-event.h +++ b/runtime/fastly/builtins/fetch-event.h @@ -15,19 +15,28 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { static bool tls_protocol_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_client_hello_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_ja3_md5_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool tls_ja4_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool h2_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool oh_fingerprint_get(JSContext *cx, unsigned argc, JS::Value *vp); static bool tls_client_certificate_get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool tls_client_servername_get(JSContext *cx, unsigned argc, JS::Value *vp); public: static constexpr const char *class_name = "ClientInfo"; enum class Slots { + RequestHandle, Address, GeoInfo, Cipher, Protocol, ClientHello, JA3, + JA4, + H2Fingerprint, + OHFingerprint, ClientCert, + Servername, Count, }; static const JSFunctionSpec static_methods[]; @@ -35,7 +44,7 @@ class ClientInfo final : public builtins::BuiltinNoConstructor { static const JSFunctionSpec methods[]; static const JSPropertySpec properties[]; - static JSObject *create(JSContext *cx); + static JSObject *create(JSContext *cx, uint32_t req_handle); }; class ServerInfo final : public builtins::BuiltinNoConstructor { @@ -45,6 +54,7 @@ class ServerInfo final : public builtins::BuiltinNoConstructor { static constexpr const char *class_name = "ServerInfo"; enum class Slots { + RequestHandle, Address, Count, }; @@ -53,7 +63,7 @@ class ServerInfo final : public builtins::BuiltinNoConstructor { static const JSFunctionSpec methods[]; static const JSPropertySpec properties[]; - static JSObject *create(JSContext *cx); + static JSObject *create(JSContext *cx, uint32_t req_handle); }; void dispatch_fetch_event(HandleObject event); diff --git a/runtime/fastly/host-api/fastly.h b/runtime/fastly/host-api/fastly.h index d1e2e86d8c..c24409c37e 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -511,26 +511,39 @@ int req_auto_decompress_response_set(uint32_t req_handle, int tag); * Otherwise, if `nwritten` will is `16`, the value in `octets` is an IPv6 address. * Otherwise, `nwritten` will be `0`, and no address is available. */ -WASM_IMPORT("fastly_http_req", "downstream_client_ip_addr") -int req_downstream_client_ip_addr_get(uint8_t *octets, size_t *nwritten); +// fastly_http_downstream module - APIs that take request handle +WASM_IMPORT("fastly_http_downstream", "downstream_client_ip_addr") +int downstream_client_ip_addr(uint32_t req_handle, uint8_t *octets, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_server_ip_addr") -int req_downstream_server_ip_addr_get(uint8_t *octets, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_server_ip_addr") +int downstream_server_ip_addr(uint32_t req_handle, uint8_t *octets, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_cipher_openssl_name") -int req_downstream_tls_cipher_openssl_name(char *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_cipher_openssl_name") +int downstream_tls_cipher_openssl_name(uint32_t req_handle, char *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_protocol") -int req_downstream_tls_protocol(char *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_protocol") +int downstream_tls_protocol(uint32_t req_handle, char *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_client_hello") -int req_downstream_tls_client_hello(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_client_hello") +int downstream_tls_client_hello(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_raw_client_certificate") -int req_downstream_tls_raw_client_certificate(uint8_t *ret, size_t ret_len, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_raw_client_certificate") +int downstream_tls_raw_client_certificate(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); -WASM_IMPORT("fastly_http_req", "downstream_tls_ja3_md5") -int req_downstream_tls_ja3_md5(uint8_t *ret, size_t *nwritten); +WASM_IMPORT("fastly_http_downstream", "downstream_tls_ja3_md5") +int downstream_tls_ja3_md5(uint32_t req_handle, uint8_t *ret, size_t *nwritten); + +WASM_IMPORT("fastly_http_downstream", "downstream_tls_ja4") +int downstream_tls_ja4(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); + +WASM_IMPORT("fastly_http_downstream", "downstream_client_h2_fingerprint") +int downstream_client_h2_fingerprint(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); + +WASM_IMPORT("fastly_http_downstream", "downstream_client_oh_fingerprint") +int downstream_client_oh_fingerprint(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); + +WASM_IMPORT("fastly_http_downstream", "downstream_tls_client_servername") +int downstream_tls_client_servername(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten); WASM_IMPORT("fastly_http_req", "new") int req_new(uint32_t *req_handle_out); diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index e435174464..d9546bafc3 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -1634,7 +1634,7 @@ Result HttpReq::downstream_client_ip_addr() { fastly::fastly_world_list_u8 octets; octets.ptr = static_cast(cabi_malloc(16, 1)); fastly::fastly_host_error err; - if (!convert_result(fastly::req_downstream_client_ip_addr_get(octets.ptr, &octets.len), &err)) { + if (!convert_result(fastly::downstream_client_ip_addr(this->handle, octets.ptr, &octets.len), &err)) { cabi_free(octets.ptr); res.emplace_err(err); } else { @@ -1651,7 +1651,7 @@ Result HttpReq::downstream_server_ip_addr() { fastly::fastly_world_list_u8 octets; octets.ptr = static_cast(cabi_malloc(16, 1)); fastly::fastly_host_error err; - if (!convert_result(fastly::req_downstream_server_ip_addr_get(octets.ptr, &octets.len), &err)) { + if (!convert_result(fastly::downstream_server_ip_addr(this->handle, octets.ptr, &octets.len), &err)) { cabi_free(octets.ptr); res.emplace_err(err); } else { @@ -1661,8 +1661,8 @@ Result HttpReq::downstream_server_ip_addr() { return res; } -// http-req-downstream-tls-cipher-openssl-name: func() -> result -Result> HttpReq::http_req_downstream_tls_cipher_openssl_name() { +// downstream-tls-cipher-openssl-name: func(req_handle) -> result +Result> HttpReq::downstream_tls_cipher_openssl_name() { TRACE_CALL() Result> res; @@ -1670,12 +1670,14 @@ Result> HttpReq::http_req_downstream_tls_cipher_openss fastly::fastly_world_string ret; auto default_size = 128; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_cipher_openssl_name(reinterpret_cast(ret.ptr), - default_size, &ret.len); + auto status = fastly::downstream_tls_cipher_openssl_name(this->handle, + reinterpret_cast(ret.ptr), + default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_cipher_openssl_name(reinterpret_cast(ret.ptr), - ret.len, &ret.len); + status = fastly::downstream_tls_cipher_openssl_name(this->handle, + reinterpret_cast(ret.ptr), + ret.len, &ret.len); } if (!convert_result(status, &err)) { @@ -1692,8 +1694,8 @@ Result> HttpReq::http_req_downstream_tls_cipher_openss return res; } -// http-req-downstream-tls-protocol: func() -> result -Result> HttpReq::http_req_downstream_tls_protocol() { +// downstream-tls-protocol: func(req_handle) -> result +Result> HttpReq::downstream_tls_protocol() { TRACE_CALL() Result> res; @@ -1701,12 +1703,13 @@ Result> HttpReq::http_req_downstream_tls_protocol() { fastly::fastly_world_string ret; auto default_size = 32; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_protocol(reinterpret_cast(ret.ptr), default_size, - &ret.len); + auto status = fastly::downstream_tls_protocol(this->handle, + reinterpret_cast(ret.ptr), default_size, + &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = - fastly::req_downstream_tls_protocol(reinterpret_cast(ret.ptr), ret.len, &ret.len); + status = fastly::downstream_tls_protocol(this->handle, + reinterpret_cast(ret.ptr), ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1722,8 +1725,8 @@ Result> HttpReq::http_req_downstream_tls_protocol() { return res; } -// http-req-downstream-tls-client-hello: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_client_hello() { +// downstream-tls-client-hello: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_client_hello() { TRACE_CALL() Result> res; @@ -1731,10 +1734,10 @@ Result> HttpReq::http_req_downstream_tls_client_hello() fastly::fastly_host_error err; auto default_size = 512; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_client_hello(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_tls_client_hello(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_client_hello(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_tls_client_hello(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { @@ -1751,8 +1754,8 @@ Result> HttpReq::http_req_downstream_tls_client_hello() return res; } -// http-req-downstream-tls-raw-client-certificate: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_raw_client_certificate() { +// downstream-tls-raw-client-certificate: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_raw_client_certificate() { TRACE_CALL() Result> res; @@ -1760,10 +1763,10 @@ Result> HttpReq::http_req_downstream_tls_raw_client_cer fastly::fastly_host_error err; auto default_size = 4096; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_raw_client_certificate(ret.ptr, default_size, &ret.len); + auto status = fastly::downstream_tls_raw_client_certificate(this->handle, ret.ptr, default_size, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_raw_client_certificate(ret.ptr, ret.len, &ret.len); + status = fastly::downstream_tls_raw_client_certificate(this->handle, ret.ptr, ret.len, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1779,8 +1782,8 @@ Result> HttpReq::http_req_downstream_tls_raw_client_cer return res; } -// http-req-downstream-tls-ja3-md5: func() -> result, error> -Result> HttpReq::http_req_downstream_tls_ja3_md5() { +// downstream-tls-ja3-md5: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_ja3_md5() { TRACE_CALL() Result> res; @@ -1788,10 +1791,10 @@ Result> HttpReq::http_req_downstream_tls_ja3_md5() { fastly::fastly_host_error err; auto default_size = 16; ret.ptr = static_cast(cabi_malloc(default_size, 4)); - auto status = fastly::req_downstream_tls_ja3_md5(ret.ptr, &ret.len); + auto status = fastly::downstream_tls_ja3_md5(this->handle, ret.ptr, &ret.len); if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); - status = fastly::req_downstream_tls_ja3_md5(ret.ptr, &ret.len); + status = fastly::downstream_tls_ja3_md5(this->handle, ret.ptr, &ret.len); } if (!convert_result(status, &err)) { cabi_free(ret.ptr); @@ -1807,6 +1810,118 @@ Result> HttpReq::http_req_downstream_tls_ja3_md5() { return res; } +// downstream-tls-ja4: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_ja4() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 128; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::downstream_tls_ja4(this->handle, ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::downstream_tls_ja4(this->handle, ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + +// downstream-client-h2-fingerprint: func(req_handle) -> result, error> +Result> HttpReq::downstream_client_h2_fingerprint() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 128; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::downstream_client_h2_fingerprint(this->handle, ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::downstream_client_h2_fingerprint(this->handle, ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + +// downstream-client-oh-fingerprint: func(req_handle) -> result, error> +Result> HttpReq::downstream_client_oh_fingerprint() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 128; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::downstream_client_oh_fingerprint(this->handle, ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::downstream_client_oh_fingerprint(this->handle, ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + +// downstream-tls-client-servername: func(req_handle) -> result, error> +Result> HttpReq::downstream_tls_client_servername() { + TRACE_CALL() + Result> res; + + fastly::fastly_host_error err; + fastly::fastly_world_string ret; + auto default_size = 256; + ret.ptr = static_cast(cabi_malloc(default_size, 4)); + auto status = fastly::downstream_tls_client_servername(this->handle, ret.ptr, default_size, &ret.len); + if (status == FASTLY_HOST_ERROR_BUFFER_LEN) { + ret.ptr = static_cast(cabi_realloc(ret.ptr, default_size, 4, ret.len)); + status = fastly::downstream_tls_client_servername(this->handle, ret.ptr, ret.len, &ret.len); + } + if (!convert_result(status, &err)) { + cabi_free(ret.ptr); + if (error_is_optional_none(err)) { + res.emplace(std::nullopt); + } else { + res.emplace_err(err); + } + } else { + res.emplace(make_host_string(ret)); + } + + return res; +} + bool HttpReq::is_valid() const { return this->handle != HttpReq::invalid; } Result HttpReq::get_version() const { diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index 74afd94ae4..e984de8a69 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -558,20 +558,38 @@ class HttpReq final : public HttpBase { /// Fetch the downstream request/body pair static Result downstream_get(); - /// Get the downstream ip address. - static Result downstream_client_ip_addr(); + /// Get the downstream client ip address. + Result downstream_client_ip_addr(); - static Result downstream_server_ip_addr(); + /// Get the downstream server ip address. + Result downstream_server_ip_addr(); - static Result> http_req_downstream_tls_cipher_openssl_name(); + /// Get the TLS cipher OpenSSL name. + Result> downstream_tls_cipher_openssl_name(); - static Result> http_req_downstream_tls_protocol(); + /// Get the TLS protocol version. + Result> downstream_tls_protocol(); - static Result> http_req_downstream_tls_client_hello(); + /// Get the TLS ClientHello. + Result> downstream_tls_client_hello(); - static Result> http_req_downstream_tls_raw_client_certificate(); + /// Get the raw client certificate. + Result> downstream_tls_raw_client_certificate(); - static Result> http_req_downstream_tls_ja3_md5(); + /// Get the JA3 MD5 hash. + Result> downstream_tls_ja3_md5(); + + /// Get the JA4 fingerprint. + Result> downstream_tls_ja4(); + + /// Get the HTTP/2 fingerprint. + Result> downstream_client_h2_fingerprint(); + + /// Get the Original Header fingerprint. + Result> downstream_client_oh_fingerprint(); + + /// Get the TLS client servername (SNI). + Result> downstream_tls_client_servername(); Result auto_decompress_gzip(); diff --git a/types/globals.d.ts b/types/globals.d.ts index 84b0b52aff..b40ddadbc5 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -436,8 +436,12 @@ declare interface ClientInfo { readonly address: string; readonly geo: import('fastly:geolocation').Geolocation | null; readonly tlsJA3MD5: string | null; + readonly tlsJA4: string | null; + readonly h2Fingerprint: string | null; + readonly ohFingerprint: string | null; readonly tlsCipherOpensslName: string | null; readonly tlsProtocol: string | null; + readonly tlsClientServername: string | null; readonly tlsClientCertificate: ArrayBuffer | null; readonly tlsClientHello: ArrayBuffer | null; }