mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-22 18:12:10 +03:00
Compare commits
19
Commits
proxyprotocol
..
ech
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a0425f4f | ||
|
|
34645a62b2 | ||
|
|
6d50082993 | ||
|
|
6219527ce4 | ||
|
|
0dd580f45b | ||
|
|
63e15bb55c | ||
|
|
6f3dcfb365 | ||
|
|
f8e031ecb9 | ||
|
|
387ef13f52 | ||
|
|
b6c8e116af | ||
|
|
4427a9570f | ||
|
|
3c237b3c9a | ||
|
|
2914fe7b5c | ||
|
|
829a7299e1 | ||
|
|
f2e86a3df7 | ||
|
|
f64f0c5ba5 | ||
|
|
6d1e95cc52 | ||
|
|
b9de23534e | ||
|
|
55872427f2 |
No files matched your search
@@ -1,4 +1,41 @@
|
||||
vNEXT:
|
||||
ech branch:
|
||||
This is an experimental version and does not work.
|
||||
Do not use it unless you are experimenting with ECH
|
||||
and an experimental wolfSSL.
|
||||
|
||||
A lot of context is discussed here:
|
||||
https://github.com/wolfSSL/wolfssl/issues/10485
|
||||
|
||||
What it does:
|
||||
- It creates a public ECH configuration for the
|
||||
first public name (configure in the `listen`
|
||||
section of the configuration file). This gets
|
||||
printed in a file configured in `ech_conf_sink`
|
||||
(see ech_test.cfg example).
|
||||
- Upon incoming connections, it will call an
|
||||
imaginary wolfSSL_GetEchClientHelloInner().
|
||||
The resulting packet is hexdumped.
|
||||
- It will also use wolfSSL to decode the SNI and
|
||||
print it (but does not yet use it to forward).
|
||||
|
||||
Most things happen in tls_ech.c, in particular there
|
||||
is a stub of GetEchClientHelloInner() that needs to
|
||||
be removed when the function becomes available in
|
||||
wolfSSL.
|
||||
|
||||
TODO:
|
||||
Change configure script to check for appropriate
|
||||
wolfSSL version.
|
||||
Create ECH configurations for each public name
|
||||
Link decoded InnerHello to SNI extraction code
|
||||
....
|
||||
Error cases still to be worked out (see
|
||||
conversations on github)
|
||||
Profit!
|
||||
|
||||
|
||||
|
||||
v2.3.0:
|
||||
Added `max_connections` setting to `listen` and
|
||||
`protocol` configuration; see the
|
||||
[guide](doc/max_connections.md) for more
|
||||
@@ -6,7 +43,8 @@ vNEXT:
|
||||
|
||||
accept() is now disabled for some time when ulimit -n is reached.
|
||||
|
||||
Support proxyprotocol on incoming connections.
|
||||
Support proxyprotocol on incoming connections. See
|
||||
the [guide](doc/proxyprotocol.md) for information.
|
||||
|
||||
Fix proxyprotocol target field (Thanks to Github user 404-Not-Found)
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
|
||||
LIBS=-lm -lpcre2-8 @LIBS@
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o tls_ech.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
|
||||
OBJS_A=libsslh.a
|
||||
FORK_OBJS=sslh-fork.o $(OBJS_A)
|
||||
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
|
||||
|
||||
@@ -25,12 +25,6 @@
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
#ifndef SA_NOCLDWAIT
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
/* Make use of systemd socket activation
|
||||
* */
|
||||
#ifdef SYSTEMD
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
#define FD_SETSIZE 4096
|
||||
#endif
|
||||
|
||||
/* Cygwin does not define some symbols
|
||||
* */
|
||||
#ifndef SA_NOCLDWAIT
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
@@ -41,6 +48,7 @@
|
||||
#include "config.h"
|
||||
#include "version.h"
|
||||
|
||||
#undef MAX
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
|
||||
@@ -18,4 +18,7 @@
|
||||
/* libbsd, to change process name */
|
||||
#undef HAVE_LIBBSD
|
||||
|
||||
/* wolfssl, to support Encrypted Client Hello (ECH) */
|
||||
#undef HAVE_WOLFSSL
|
||||
|
||||
#endif
|
||||
@@ -3329,6 +3329,47 @@ then :
|
||||
LIBS="$LIBS -lbsd"
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wolfSSL_Init in -lwolfssl" >&5
|
||||
printf %s "checking for wolfSSL_Init in -lwolfssl... " >&6; }
|
||||
if test ${ac_cv_lib_wolfssl_wolfSSL_Init+y}
|
||||
then :
|
||||
printf %s "(cached) " >&6
|
||||
else $as_nop
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lwolfssl $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
char wolfSSL_Init ();
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
return wolfSSL_Init ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"
|
||||
then :
|
||||
ac_cv_lib_wolfssl_wolfSSL_Init=yes
|
||||
else $as_nop
|
||||
ac_cv_lib_wolfssl_wolfSSL_Init=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.beam \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wolfssl_wolfSSL_Init" >&5
|
||||
printf "%s\n" "$ac_cv_lib_wolfssl_wolfSSL_Init" >&6; }
|
||||
if test "x$ac_cv_lib_wolfssl_wolfSSL_Init" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_WOLFSSL 1" >>confdefs.h
|
||||
LIBS="$LIBS -lwolfssl"
|
||||
fi
|
||||
|
||||
|
||||
ac_header= ac_cache=
|
||||
for ac_item in $ac_header_c_list
|
||||
|
||||
@@ -11,6 +11,9 @@ AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ]
|
||||
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], [])
|
||||
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], [])
|
||||
|
||||
dnl TODO: check that wolfssl contains wolfSSL_GetEchClientHelloInner instead of just existing
|
||||
AC_CHECK_LIB([wolfssl], [wolfSSL_Init], [AC_DEFINE(HAVE_WOLFSSL) LIBS="$LIBS -lwolfssl" ], [])
|
||||
|
||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
||||
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
|
||||
|
||||
|
||||
+53
-17
@@ -33,6 +33,8 @@ olegstepura](https://github.com/yrutschle/sslh/issues/448)
|
||||
# ... (make sure to mount config as you like)
|
||||
command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
|
||||
state: started
|
||||
# ⚠️ Important: Never add a `network:` here, otherwise this setup will NOT work.
|
||||
# Adding a network disconnects the sslh container from the pod's shared localhost.
|
||||
|
||||
- name: "Create the caddy container"
|
||||
containers.podman.podman_container:
|
||||
@@ -95,26 +97,60 @@ protocols:
|
||||
);
|
||||
```
|
||||
|
||||
I omitted caddy configs here as it's not important. Some unrelated container configs were also dropped.
|
||||
In the example above `sslh`, `caddy` and `ssh-proxy` are 3 containers in the same pod, all listening on `localhost`. SSLH has to listen on `444` because caddy already listens on `443` and it's more complex to reconfigure caddy port because of let's encrypt (caddy itself "thinks" it is bound to your host interface).
|
||||
I omitted Caddy configs here since they're irrelevant, and some unrelated container configs were also dropped for clarity.
|
||||
|
||||
Scheme of port mapping is (all containers share same `localhost`):
|
||||
In the example above, `sslh`, `caddy`, and `ssh-proxy` are **three containers inside the same Podman pod**, all sharing the same **pod-local `localhost`**.
|
||||
|
||||
`sslh` listens on port **444** instead of **443**, because `caddy` already binds to port `443`, and reconfiguring Caddy is more complex due to its Let's Encrypt integration (Caddy itself "thinks" it’s bound to your host interface).
|
||||
|
||||
#### Port mapping scheme
|
||||
All containers **share the same `localhost`** inside the pod:
|
||||
```
|
||||
host 443 → pod 444 (sslh) → pod 443 (caddy)
|
||||
```
|
||||
- podman connects host `443` port to pod's `444` port
|
||||
- `sslh` listens `444` on `localhost`, reroutes tls to `caddy` on `localhost:443`
|
||||
- `caddy` listens `443` on `localhost` (reverse-proxies to other apps on private network)
|
||||
Reverse-proxied services get correct IP in `X-Forwarded-For` header.
|
||||
|
||||
Takeaways:
|
||||
- `net.ipv4.conf.default.route_localnet` is setup only in container, not on host, which is nice.
|
||||
- same with iptables and route rules applied by `init` script in sslh container
|
||||
- `sslh` proxy to other services in the private network will not work (because transparent mode is enabled) even if that other service does not need real IP
|
||||
- with transparent mode all services that `sslh` will connect to should be attached to this pod making it listen on `localhost` of the pod
|
||||
- ports of containers should not clash
|
||||
- ports should be published on pod level, not on containers
|
||||
- containers should not be configured to connect to custom networks
|
||||
- because of the above proxying ssh to host will also not work out of the box, `sslh` should connect to `localhost` and not to a random IP. While adding another proxy as a container to the same pod sounds like an overkill I don't see any other solution, so `socat` is used as an additional proxy.
|
||||
- `reverse_proxy` by caddy to other containers in the same custom network (as pod is attached to) works (e.g. caddy can connect to other IPs from custom network). `socat` can also connect to host and/or other containers in custom network.
|
||||
|
||||
- **Podman** maps the host’s `443` → pod’s `444`.
|
||||
- **SSHL** listens on `localhost:444` inside the pod and forwards TLS traffic to `caddy` on `localhost:443`.
|
||||
- **Caddy** listens on `localhost:443` and reverse-proxies to other services in the private network.
|
||||
|
||||
This ensures that **reverse-proxied services** receive the correct client IP in the `X-Forwarded-For` header.
|
||||
|
||||
---
|
||||
|
||||
### Important note
|
||||
|
||||
⚠️ Do **not** set a `network:` parameter for the `sslh` container when it belongs to a Podman pod.
|
||||
Setting a custom network **forces Podman to detach the container from the pod’s shared network namespace** and creates a **separate `localhost`** for that container, breaking transparent routing.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
|
||||
state: started
|
||||
# ⚠️ Never add a `network:` here!
|
||||
# Adding a network disconnects the sslh container from the pod's shared localhost,
|
||||
# causing transparent routing to fail.
|
||||
```
|
||||
|
||||
### Key takeaways
|
||||
|
||||
- `net.ipv4.conf.default.route_localnet` and iptables rules are applied **inside the `sslh` container only**, not on the host.
|
||||
- The `sslh` **init** script manages all required `iptables` and routing rules inside of the container.
|
||||
- **Transparent mode restrictions**:
|
||||
- Any service `sslh` proxies traffic to **must** be inside the same pod and bound to its **pod-local `localhost`**.
|
||||
- Container ports must not conflict within the pod.
|
||||
- Ports should be published **at the pod level**, **not per-container**.
|
||||
- Containers inside this pod **must not** be attached to any custom networks.
|
||||
- Because of transparent routing, `sslh` cannot directly proxy SSH to the host IP.
|
||||
A workaround is to run a lightweight **socat** container in the same pod:
|
||||
`TCP-LISTEN:222 → TCP:host.containers.internal:22`
|
||||
- Caddy’s `reverse_proxy` **to other containers** in the **custom network attached to the pod** still works properly.
|
||||
- `socat` can also connect to both the **host** and **other containers** inside the custom network without issues.
|
||||
|
||||
### Tested setup
|
||||
|
||||
- Confirmed working on **Debian Trixie**
|
||||
- Using **Podman 5.4.2**
|
||||
- Pinned `podman` and related dependencies (`conmon`, `slirp4netns`, `aardvark-dns`, etc.) from **Debian testing**,
|
||||
since **stable** often ships outdated versions that can break this setup.
|
||||
@@ -0,0 +1,45 @@
|
||||
# sslh configuration for saaz:
|
||||
# ssh to localhost
|
||||
# https to nextcloud
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
user: "yves";
|
||||
pidfile: "/tmp/sslh.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Where to write the ECH configurations
|
||||
ech_conf_sink: "/tmp/ech";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||
|
||||
|
||||
verbose: 1;
|
||||
|
||||
|
||||
# Change hostname with your external address name.
|
||||
listen:
|
||||
(
|
||||
{ host: "inet"; port: "4443"; },
|
||||
{ host: "chaton"; port: "4445"; }
|
||||
);
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
{ name: "tls"; sni_hostnames: [
|
||||
"inet.rutschle.net", "lists.rutschle.net"
|
||||
];
|
||||
host: "localhost"; port: "4453"; },
|
||||
{ name: "tls"; sni_hostnames: [
|
||||
"chaton.rutschle.net"
|
||||
];
|
||||
host: "localhost"; port: "4455"; }
|
||||
);
|
||||
|
||||
|
||||
on-timeout: "ssh";
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Fri Aug 29 20:43:29 2025.
|
||||
* on Wed Sep 10 15:49:49 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Fri Aug 29 20:43:29 2025.
|
||||
* on Wed Sep 10 15:49:49 2025.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
|
||||
@@ -55,6 +55,9 @@ syslog_facility: "auth";
|
||||
# is_unix: listen in the UNIX socket domain (default is TCP)
|
||||
# keepalive: set TCP_KEEPALIVE
|
||||
# max_connections: drop incoming connections after this count
|
||||
# proxyprotocol: [true|false]: this endpoint expects
|
||||
# proxyprotocol headers on incoming connections. Refer to
|
||||
# doc/proxyprotocol.md
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; max_connections: 5; },
|
||||
|
||||
@@ -155,6 +155,10 @@ static int distance(int current_index, hash* h, hash_item item)
|
||||
return current_index - wanted_index + h->hash_size;
|
||||
}
|
||||
|
||||
int hash_insertable(hash* h)
|
||||
{
|
||||
return (h->item_cnt < h->hash_size);
|
||||
}
|
||||
|
||||
int hash_insert(hash* h, hash_item new)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ typedef int (*hash_cmp_item_fn)(hash_item item1, hash_item item2);
|
||||
|
||||
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item);
|
||||
|
||||
int hash_insertable(hash* h); /* Is it possible to insert: any space left? */
|
||||
int hash_insert(hash* h, hash_item new);
|
||||
int hash_remove(hash* h, hash_item item);
|
||||
|
||||
|
||||
@@ -162,8 +162,6 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
|
||||
if (len < 1)
|
||||
return PROBE_NEXT;
|
||||
|
||||
printf("opcode: %d\n", (p[0] & OVPN_OPCODE_MASK) >> 3);
|
||||
|
||||
if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 &&
|
||||
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2 &&
|
||||
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V3
|
||||
|
||||
+18
-5
@@ -46,9 +46,9 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
|
||||
watchers_del_read(fd_info->watchers, cnx->q[i].fd);
|
||||
watchers_del_write(fd_info->watchers, cnx->q[i].fd);
|
||||
close(cnx->q[i].fd);
|
||||
if (cnx->q[i].deferred_data)
|
||||
free(cnx->q[i].deferred_data);
|
||||
}
|
||||
if (cnx->q[i].begin_deferred_data)
|
||||
free(cnx->q[i].begin_deferred_data);
|
||||
}
|
||||
|
||||
if (cnx->type == SOCK_DGRAM)
|
||||
@@ -152,6 +152,13 @@ void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
|
||||
|
||||
typedef struct pid2proto* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
int has_space_to_fork(struct loop_info* fd_info)
|
||||
{
|
||||
return hash_insertable(fd_info->pid2proto);
|
||||
}
|
||||
|
||||
|
||||
void remember_child_data(struct loop_info* fd_info,
|
||||
struct connection* cnx, pid_t pid)
|
||||
{
|
||||
@@ -160,7 +167,10 @@ void remember_child_data(struct loop_info* fd_info,
|
||||
pid2proto->proto = cnx->proto;
|
||||
pid2proto->endpoint = cnx->endpoint;
|
||||
if (hash_insert(fd_info->pid2proto, pid2proto)) {
|
||||
/* TODO something if it fails */
|
||||
print_message(msg_int_error, "hash_insert for pid %d failed\n", pid);
|
||||
/* This should not happen as called is supposed to check that space is
|
||||
* available in the hash */
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +178,11 @@ static int max_forking_connections(void)
|
||||
{
|
||||
int max_cnx = 0;
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
if (cfg.protocols[i].fork)
|
||||
max_cnx += cfg.protocols[i].max_connections;
|
||||
if (cfg.protocols[i].fork) {
|
||||
if (cfg.protocols[i].max_connections_is_present)
|
||||
max_cnx += cfg.protocols[i].max_connections;
|
||||
else max_cnx += 512; /* A magic number that's hopefully high enough */
|
||||
}
|
||||
}
|
||||
return max_cnx;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ void loop_init(struct loop_info* loop, int num_addr_listen);
|
||||
void remember_child_data(struct loop_info* fd_info,
|
||||
struct connection* cnx, pid_t pid);
|
||||
void decrease_forked_connection(struct loop_info* loop, pid_t pid);
|
||||
int has_space_to_fork(struct loop_info* loop);
|
||||
|
||||
/* These must be declared in the loop handler, sslh-ev or sslh-select */
|
||||
void watchers_add_read(watchers* w, int fd);
|
||||
|
||||
+62
-16
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Fri Aug 29 20:43:29 2025.
|
||||
* on Sat Jul 18 17:34:37 2026.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
@@ -482,6 +482,8 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_chroot;
|
||||
struct arg_str* sslhcfg_syslog_facility;
|
||||
struct arg_str* sslhcfg_logfile;
|
||||
struct arg_str* sslhcfg_ech_conf_sink;
|
||||
struct arg_str* sslhcfg_ech_public_name;
|
||||
struct arg_str* sslhcfg_on_timeout;
|
||||
struct arg_str* sslhcfg_prefix;
|
||||
struct arg_str* sslhcfg_listen;
|
||||
@@ -926,7 +928,7 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg[] = {
|
||||
|
||||
|
||||
@@ -1314,6 +1316,38 @@ static struct config_desc table_sslhcfg[] = {
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "ech_conf_sink",
|
||||
/* type */ CFG_STRING,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_ech_conf_sink,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, ech_conf_sink),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_item, ech_conf_sink_is_present),
|
||||
/* size */ sizeof(char*),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "ech_public_name",
|
||||
/* type */ CFG_STRING,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_ech_public_name,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, ech_public_name),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ offsetof(struct sslhcfg_item, ech_public_name_is_present),
|
||||
/* size */ sizeof(char*),
|
||||
/* array_type */ -1,
|
||||
/* mandatory */ 0,
|
||||
/* optional */ 1,
|
||||
/* default_val*/ .default_val.def_string = NULL
|
||||
},
|
||||
|
||||
{
|
||||
/* name */ "on_timeout",
|
||||
/* type */ CFG_STRING,
|
||||
@@ -1500,7 +1534,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: listen */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_listen,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.base_entry = & table_sslhcfg [28],
|
||||
.targets = sslhcfg_listen_targets,
|
||||
|
||||
|
||||
@@ -1512,7 +1546,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssh */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssh,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_ssh_targets,
|
||||
|
||||
|
||||
@@ -1524,7 +1558,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tls */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tls,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_tls_targets,
|
||||
|
||||
|
||||
@@ -1536,7 +1570,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssl */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssl,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_ssl_targets,
|
||||
|
||||
|
||||
@@ -1548,7 +1582,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: openvpn */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_openvpn,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_openvpn_targets,
|
||||
|
||||
|
||||
@@ -1560,7 +1594,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tinc */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tinc,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_tinc_targets,
|
||||
|
||||
|
||||
@@ -1572,7 +1606,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: wireguard */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_wireguard,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_wireguard_targets,
|
||||
|
||||
|
||||
@@ -1584,7 +1618,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: xmpp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_xmpp,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_xmpp_targets,
|
||||
|
||||
|
||||
@@ -1596,7 +1630,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: http */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_http,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_http_targets,
|
||||
|
||||
|
||||
@@ -1608,7 +1642,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: adb */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_adb,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_adb_targets,
|
||||
|
||||
|
||||
@@ -1620,7 +1654,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: socks5 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_socks5,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_socks5_targets,
|
||||
|
||||
|
||||
@@ -1632,7 +1666,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: syslog */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_syslog,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_syslog_targets,
|
||||
|
||||
|
||||
@@ -1644,7 +1678,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: msrdp */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_msrdp,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_msrdp_targets,
|
||||
|
||||
|
||||
@@ -1656,7 +1690,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: anyprot */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_anyprot,
|
||||
.base_entry = & table_sslhcfg [27],
|
||||
.base_entry = & table_sslhcfg [29],
|
||||
.targets = sslhcfg_anyprot_targets,
|
||||
|
||||
|
||||
@@ -2345,6 +2379,8 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
|
||||
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
|
||||
sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
|
||||
sslhcfg_ech_conf_sink = arg_strn(NULL, "ech-conf-sink", "<str>", 0, 1, "File where to write ECH configurations"),
|
||||
sslhcfg_ech_public_name = arg_strn(NULL, "ech-public-name", "<str>", 0, 1, "Public name for ECH client-facing proxy (usually, sslh's address)"),
|
||||
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
|
||||
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
|
||||
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
|
||||
@@ -2621,6 +2657,16 @@ void sslhcfg_fprint(
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "ech_conf_sink: %s", sslhcfg->ech_conf_sink);
|
||||
if (! sslhcfg->ech_conf_sink_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "ech_public_name: %s", sslhcfg->ech_public_name);
|
||||
if (! sslhcfg->ech_public_name_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
|
||||
fprintf(out, "\n");
|
||||
indent(out, depth);
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Fri Aug 29 20:43:29 2025.
|
||||
* on Sat Jul 18 17:34:37 2026.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2024 Yves Rutschle
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "probe.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
struct sslhcfg_listen_item {
|
||||
@@ -114,6 +115,10 @@ struct sslhcfg_item {
|
||||
char* syslog_facility;
|
||||
int logfile_is_present;
|
||||
char* logfile;
|
||||
int ech_conf_sink_is_present;
|
||||
char* ech_conf_sink;
|
||||
int ech_public_name_is_present;
|
||||
char* ech_public_name;
|
||||
char* on_timeout;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
|
||||
@@ -347,6 +347,8 @@ int main(int argc, char *argv[], char* envp[])
|
||||
/* Open log file for writing */
|
||||
setup_logfile();
|
||||
|
||||
ech_init(listen_sockets, num_addr_listen);
|
||||
|
||||
if (cfg.user || cfg.chroot)
|
||||
drop_privileges(cfg.user, cfg.chroot);
|
||||
setup_landlock();
|
||||
|
||||
+4
-2
@@ -131,6 +131,7 @@ static void sigchld_process(struct loop_info* loop)
|
||||
received_sigchld = 0;
|
||||
do {
|
||||
chld = waitpid(-1, NULL, WNOHANG);
|
||||
if ((chld == -1) && (errno == ECHILD)) return;
|
||||
CHECK_RES_RETURN(chld, "waitpid", );
|
||||
if (chld) {
|
||||
decrease_forked_connection(loop, chld);
|
||||
@@ -221,8 +222,9 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
if ((res == -1) && ((errno != EAGAIN) && (errno != EINTR)))
|
||||
print_message(msg_system_error, "%s:%d:%d:%s\n",
|
||||
__FILE__, __LINE__, errno, strerror(errno)); \
|
||||
|
||||
/* Check main socket for new connections */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
|
||||
@@ -18,6 +18,7 @@ includes: (
|
||||
"probe.h",
|
||||
" <sys/types.h>",
|
||||
" <sys/socket.h>",
|
||||
" <wolfssl/ssl.h>",
|
||||
" <netdb.h>"
|
||||
);
|
||||
|
||||
@@ -85,6 +86,8 @@ config: {
|
||||
description: "Facility to syslog to"; },
|
||||
{ name: "logfile"; type: "string"; optional: true;
|
||||
description: "Log messages to a file" },
|
||||
{ name: "ech_conf_sink"; type: "string"; optional: true;
|
||||
description: "File where to write ECH configurations" },
|
||||
|
||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||
description: "Target to connect to when timing out"; },
|
||||
|
||||
+12
-5
@@ -165,6 +165,7 @@ struct connection* accept_new_connection(struct listen_endpoint* endpoint, struc
|
||||
print_message(msg_fd, "accepting from %d\n", listen_socket);
|
||||
|
||||
in_socket = accept(listen_socket, 0, 0);
|
||||
if ((in_socket == -1) && (errno == EAGAIN)) return NULL; /* Do not log if we're just retring next iteration */
|
||||
CHECK_RES_RETURN(in_socket, "accept", NULL);
|
||||
|
||||
res = set_nonblock(in_socket);
|
||||
@@ -298,6 +299,12 @@ static void connect_proxy(struct connection *cnx)
|
||||
/* Forks a shoveler process for one protocol */
|
||||
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
||||
pid_t pid;
|
||||
|
||||
if (!has_space_to_fork(fd_info)) {
|
||||
print_message(msg_connections_error, "forking for %s: out of hash space\n", cnx->proto->name);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pid = fork()) {
|
||||
case 0: /* child */
|
||||
/* TODO: close all file descriptors except 2 */
|
||||
@@ -311,11 +318,6 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
|
||||
watcher_sigchld(fd_info, cnx, pid);
|
||||
break;
|
||||
}
|
||||
/* Free file descriptor (used only in child), but do not reduce connection
|
||||
* counts */
|
||||
cnx->proto = NULL;
|
||||
cnx->endpoint = NULL;
|
||||
tidy_connection(cnx, fd_info);
|
||||
}
|
||||
|
||||
/* Process read activity on a socket in probe state
|
||||
@@ -349,6 +351,11 @@ void probing_read_process(struct connection* cnx,
|
||||
tidy_connection(cnx, fd_info);
|
||||
} else if (cnx->proto->fork) {
|
||||
fork_shoveling_process(fd_info, cnx);
|
||||
/* Free file descriptor (used only in child), but do not reduce connection
|
||||
* counts */
|
||||
cnx->proto = NULL;
|
||||
cnx->endpoint = NULL;
|
||||
tidy_connection(cnx, fd_info);
|
||||
} else {
|
||||
connect_queue(cnx, fd_info);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ static int parse_server_name_extension(const struct TLSProtocol *, const char *,
|
||||
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
|
||||
static int has_match(const char**, size_t, const char*, size_t);
|
||||
|
||||
|
||||
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
|
||||
* hello handshake, returning a status code
|
||||
*
|
||||
@@ -81,6 +82,11 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
if (data_len < TLS_HEADER_LEN)
|
||||
return TLS_ELENGTH;
|
||||
|
||||
/* If if it is an ECH, decrypt and match */
|
||||
ech_match_sni(data, data_len);
|
||||
|
||||
|
||||
|
||||
tls_content_type = data[0];
|
||||
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
|
||||
|
||||
@@ -44,5 +44,6 @@ struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, s
|
||||
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
|
||||
#define TLS_EPROTOCOL -5 /* Protocol error */
|
||||
|
||||
void ech_match_sni(const char* buffer_in, size_t buffer_len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
# tls_ech.c: support for RFC9849 TLS Encrypted Client Hello (ECH)
|
||||
#
|
||||
# Copyright (C) 2026 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#define WOLFSSL_USE_OPTION_H
|
||||
#include <wolfssl/options.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <wolfssl/wolfcrypt/random.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "tls.h"
|
||||
|
||||
WOLFSSL_CTX* wolfssl_ctx;
|
||||
|
||||
|
||||
// Hard-coded server certificate (PEM format)
|
||||
// from snakeoil -- these serves no purpose beyond initialising the context
|
||||
static const char* SERVER_CERT_PEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICyjCCAbKgAwIBAgIUZrCzD4tEIu1r9EhkOn96raiupe4wDQYJKoZIhvcNAQEL\n"
|
||||
"BQAwDzENMAsGA1UEAwwEc2FhejAeFw0yMTA1MjgyMDM0MjRaFw0zMTA1MjYyMDM0\n"
|
||||
"MjRaMA8xDTALBgNVBAMMBHNhYXowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
|
||||
"AoIBAQC5s1UhjcmsrRnSdJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6J\n"
|
||||
"MAL1MbPtX0pfR+yBILWUdn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoe\n"
|
||||
"gD9FE3lE895GIhEv7Jd9bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bK\n"
|
||||
"fJzuOxi7oIeINLEiR0Z4hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2\n"
|
||||
"QFMByfkEmAGp9Br+2tquRUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd\n"
|
||||
"6G6TLenPbQPEaac5UgGZ7rJvL9idAgMBAAGjHjAcMAkGA1UdEwQCMAAwDwYDVR0R\n"
|
||||
"BAgwBoIEc2FhejANBgkqhkiG9w0BAQsFAAOCAQEAZPasi1Hu7q4S9wJsbIzcGj7m\n"
|
||||
"ZCAxNdm3AINeV3mxfhToeqtXSoa2sujCEgwCuiYi0OE8wmseLO8e1xIsRc/NEpOl\n"
|
||||
"h9u5OupV2Xbf7nuKzPl31YQ7kv+90mnixizf6CFpHXAdU3WEZ33+fMtS+lV/ZBYo\n"
|
||||
"Nh9GGNJ7Grv5KIKu/b2cR403pRF1hGrjWABrIDZO3ZCtNgRp/My1Qr+/04rTs8VF\n"
|
||||
"WyDshOe0u6Xo//M3sCMbx5+62BopCMmAu2TxiPew0OTM8eDa3IBbc2kLGxp8c4J+\n"
|
||||
"EYpAKTZbkhs/6SleNMC4BOEmZQ1tk6D0N0u4Y0b1IKEjgx7HqwZ6mIdmbU1lZQ==\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static const char* SERVER_KEY_PEM =
|
||||
"-----BEGIN PRIVATE KEY-----\n"
|
||||
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5s1UhjcmsrRnS\n"
|
||||
"dJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6JMAL1MbPtX0pfR+yBILWU\n"
|
||||
"dn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoegD9FE3lE895GIhEv7Jd9\n"
|
||||
"bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bKfJzuOxi7oIeINLEiR0Z4\n"
|
||||
"hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2QFMByfkEmAGp9Br+2tqu\n"
|
||||
"RUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd6G6TLenPbQPEaac5UgGZ\n"
|
||||
"7rJvL9idAgMBAAECggEAbXNQctViD596/7rryhze+KztsI/UNsVU6uH3T+LN1XxU\n"
|
||||
"jurnRVNFV4JU3DDrHV9tIDQw3pXCxJzlyRpKHDWGNgTZU2fI6sZGuX+4w1Apd8ss\n"
|
||||
"3Y5mEkGYyaslF/MY13rR2B0wXf/IR4nYA/ssiaBjLcGqHHnQOVseHNgtdXFPXW9n\n"
|
||||
"5IXJ2PSeaFW0Z13vbTQm9Cx2kCBKL4J3TymhkuSW3DUHxmlDfWUK14lkyXCZIZ3i\n"
|
||||
"TbXQAXhwTrz3wqbG++iEP4MZM0Ka7/+WXqrsxA/yCirb+HnMewxwBKzGvkym2I2H\n"
|
||||
"3BkjDn9exDgTePD7i0w22UYCF5uezDB9YOiPVZ634QKBgQDyKIgxD7XMMJezeg0Y\n"
|
||||
"PaG+Eig/hE27mm2pyTQSd6MMZ1HpbDUpLQ/GO3Npy+bKS0/5eefNT4PWVcmpRL4W\n"
|
||||
"blNIfixkcZq1a6wax9Nhk89jXZdhBinO8gfXFOiANFDS7B4fEXeTrfhmuryqUw9m\n"
|
||||
"L6GJ0jk/H6fs6ads15y+O7A6tQKBgQDEUKlZg4qXxNMNKq7l+v7R6IH8VrNLt0I/\n"
|
||||
"f4fhpW3IhmPJCRuOFOS0UpDPYjTeu14i3EFcpe0D+DVoQiggBTAh3I/qMHvRko5g\n"
|
||||
"DT83YHp2/ec0AY0ocwnLwye224t6GF7S8R8vhG1FkIBk6DTJgWFZLDtY+dekZugz\n"
|
||||
"IHvgtxSPSQKBgHWD7fPBJ/xbaIMUq94jqqZOsXGBhyePncBTgA2mOV3/leStOm8t\n"
|
||||
"CwasOyoQZYOuYLU0z6T2/Ye6Qg7+6TCBgbEgafKknuuDwRWN+6rSzEXwWVIgZ5Gi\n"
|
||||
"KuPZparxuHdjVorFMz5borxXys7tV//DBaWYe2eCuT9jdHiBefNni/IxAoGAWHTn\n"
|
||||
"+deVqVEcsAAdkq8IBtk3SNZgL6vBhA8Y0QnTb9luOPWLnve3HbFeYrOjkwDmJ9sK\n"
|
||||
"8I4rP/ClT/cPUW5FA9z4U9PI1uOsl4cghvlH9Tnu5bYVPranIVMsH+7I6Bj3ESFo\n"
|
||||
"peaLvh6gW9dtgaZ0kNOnYi4hhoh/9Bmc9+JGt1ECgYAbGSQLa21L4Hx3NvMparVQ\n"
|
||||
"bGjN26igvqKmhBaKrH8YolOlhEw/mCwTlg9zI7jrhdO0VClPrB8IVBoL+6HvrT21\n"
|
||||
"LJM31J3ddqME9/5fQgKMQsD60kRDD6iV/iK2S6TOUjxJn1f6MuLmGSOHZYcbdBPZ\n"
|
||||
"DyBKgbz/xGV6katjXvg74A==\n"
|
||||
"-----END PRIVATE KEY-----\n";
|
||||
|
||||
|
||||
|
||||
#if 1
|
||||
/* Will be provided by wolfSSL */
|
||||
int wolfSSL_GetEchClientHelloInner(
|
||||
WOLFSSL* ssl, /* Connected to an existing context with the ECH configs. Stores hpke state */
|
||||
const byte* buffer_in, /* The full ClientHelloOuter */
|
||||
word32 buffer_len, /* How many bytes in the buffer */
|
||||
byte* buffer_out,
|
||||
word32* buffer_out_len
|
||||
)
|
||||
{
|
||||
char* inner = "some clienthelloinner";
|
||||
|
||||
*buffer_out_len = strlen(inner);
|
||||
memcpy(buffer_out, inner, *buffer_out_len);
|
||||
print_message(msg_fd, "GetEchClientHelloInner set\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void ech_get_client_hello_inner(WOLFSSL_CTX* ctx, const byte* data, size_t datalen)
|
||||
{
|
||||
WOLFSSL* ssl = NULL;
|
||||
|
||||
printf("wolfSSL_new(%p)\n", ctx);
|
||||
ssl = wolfSSL_new(ctx);
|
||||
if (ssl == NULL) {
|
||||
fprintf(stderr, "Failed to create WOLFSSL object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable certificate verification on this connection
|
||||
wolfSSL_set_verify(ssl, WOLFSSL_VERIFY_NONE, NULL);
|
||||
|
||||
byte client_hello_inner[1024];
|
||||
word32 ch_len = sizeof(client_hello_inner);;
|
||||
|
||||
int res = wolfSSL_GetEchClientHelloInner(
|
||||
ssl,
|
||||
data, /* The full ClientHelloOuter */
|
||||
datalen, /* How many bytes in the buffer */
|
||||
(byte*)&client_hello_inner,
|
||||
&ch_len
|
||||
);
|
||||
client_hello_inner[ch_len] = 0;
|
||||
if (!res) {
|
||||
print_message(msg_fd, "GetEchClientHelloInner: Innerlen: %d\n", ch_len);
|
||||
hexdump(msg_fd, (char*)client_hello_inner, ch_len);
|
||||
print_message(msg_fd, "/GetEchClientHelloInner\n");
|
||||
}
|
||||
|
||||
|
||||
byte sni_data[1024];
|
||||
unsigned int sni_len = sizeof(sni_data);
|
||||
memset(sni_data, 'A', sni_len);
|
||||
|
||||
printf("SNI_GetFromBuffer\n");
|
||||
wolfSSL_SNI_GetFromBuffer(data, datalen, 0, (byte*)&sni_data, &sni_len);
|
||||
sni_data[sni_len] = 0;
|
||||
printf("sni_len = %d\n", sni_len);
|
||||
printf("sni: %s\n", sni_data);
|
||||
}
|
||||
|
||||
/* See if the context matches */
|
||||
void ech_match_sni(const char* buffer_in, size_t buffer_len)
|
||||
{
|
||||
ech_get_client_hello_inner(wolfssl_ctx, (byte*)buffer_in, buffer_len);
|
||||
}
|
||||
|
||||
|
||||
/* Appends a configuration to the DNS configuration file */
|
||||
static void append_conf(const char* sni, const char* ech)
|
||||
{
|
||||
FILE* f = fopen(cfg.ech_conf_sink, "a");
|
||||
|
||||
if (!f) {
|
||||
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
int res = fprintf(f, "%s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\"\n", sni, ech);
|
||||
printf("append: %s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\" : %d\n", sni, ech, res);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
/* Erases the configuration file */
|
||||
static void reset_conf(void)
|
||||
{
|
||||
FILE* f = fopen(cfg.ech_conf_sink, "w");
|
||||
|
||||
if (!f) {
|
||||
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
printf("erased and opend %s\n", cfg.ech_conf_sink);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Generate a keypair for a config; later this will be moved to a separate
|
||||
* process, and sslh will just read it */
|
||||
static void ech_genkey(WOLFSSL_CTX* ctx, const char* sni)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("ech_genkey for ctx %p\n", ctx);
|
||||
|
||||
print_message(msg_config, "Generating ECH config for outer SNI: %s\n", sni);
|
||||
ret = wolfSSL_CTX_GenerateEchConfig(ctx, sni, 0, 0, 0);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
print_message(msg_system_error, "Failed to generate ECH config: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
byte echConfig[512];
|
||||
word32 echConfigLen = sizeof(echConfig);
|
||||
ret = wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
print_message(msg_system_error, "Failed to get ECH config: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char out[512];
|
||||
word32 outLen = sizeof(out);
|
||||
if (Base64_Encode_NoNl(echConfig, echConfigLen, (byte*)out, &outLen) != 0) {
|
||||
print_message(msg_system_error, "Failed to encode to base64: %d\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
append_conf(sni, out);
|
||||
}
|
||||
|
||||
/* For each public names, create a keypair and configuration.
|
||||
* (Later, this will load the configurations from some place else */
|
||||
static void ech_create_configs(WOLFSSL_CTX** ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
reset_conf();
|
||||
|
||||
*ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
|
||||
if (!*ctx) {
|
||||
print_message(msg_system_error, "Failed to create WOLFSSL_CTX\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load certificate from memory
|
||||
ret = wolfSSL_CTX_use_certificate_buffer(*ctx,
|
||||
(const unsigned char*)SERVER_CERT_PEM,
|
||||
strlen(SERVER_CERT_PEM),
|
||||
WOLFSSL_FILETYPE_PEM);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to load certificate: %d\n", ret);
|
||||
fprintf(stderr, "Please replace SERVER_CERT_PEM with a valid certificate\n");
|
||||
wolfSSL_CTX_free(*ctx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load private key from memory
|
||||
ret = wolfSSL_CTX_use_PrivateKey_buffer(*ctx,
|
||||
(const unsigned char*)SERVER_KEY_PEM,
|
||||
strlen(SERVER_KEY_PEM),
|
||||
WOLFSSL_FILETYPE_PEM);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "Failed to load private key: %d\n", ret);
|
||||
fprintf(stderr, "Please replace SERVER_KEY_PEM with a valid private key\n");
|
||||
wolfSSL_CTX_free(*ctx);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Certificate and key loaded successfully\n");
|
||||
|
||||
ech_genkey(*ctx, cfg.listen[0].host); /* For now this only works on the first host */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Initialises the ECH subsystem: create wolfSSL context, create ECH configs,
|
||||
* etc */
|
||||
|
||||
void ech_init()
|
||||
{
|
||||
wolfSSL_Init();
|
||||
|
||||
ech_create_configs(&wolfssl_ctx);
|
||||
}
|
||||
Reference in new issue
Block a user