-- Leo's gemini proxy

-- Connecting to git.thebackupbox.net:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

repo: tlswrap
action: commit
revision:
path_from:
revision_from: cbef2b1f80939434162179a559c0d52cdd35c218:
path_to:
revision_to:

git.thebackupbox.net

tlswrap

git://git.thebackupbox.net/tlswrap

commit cbef2b1f80939434162179a559c0d52cdd35c218
Author: epoch <epoch@thebackupbox.net>
Date:   Mon Apr 3 22:59:42 2023 +0000

    hacked in some optional ja3 support

diff --git a/Makefile b/Makefile

index cad8319e005c854a4477f6227a74a71319492fe8..

index ..4fb4e683e5d3f0ff26c39ff632c12da550e70ffa 100644

--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,16 @@ PREFIX:=/usr/local

 .PHONY: install all

-all: tlswrap
+all: tlswrap-ja3 tlswrap

 tlswrap: CFLAGS=-pedantic -Wall
 tlswrap: LDLIBS=-lssl -lcrypto
 tlswrap: tlswrap.c

+tlswrap-ja3: CFLAGS=-DJA3 -pedantic -Wall
+tlswrap-ja3: LDLIBS=-lssl -lcrypto
+tlswrap-ja3: tlswrap-ja3.c
+
 install: all
 	install -Dt $(PREFIX)/bin tlswrap
+	install -Dt $(PREFIX)/bin tlswrap-ja3
diff --git a/tlswrap-ja3.c b/tlswrap-ja3.c
new file mode 120000
index 0000000000000000000000000000000000000000..f43f4a8e5fc1176b54d277a58bc193571590bd85
--- /dev/null
+++ b/tlswrap-ja3.c
@@ -0,0 +1 @@
+tlswrap.c
\ No newline at end of file
diff --git a/tlswrap.c b/tlswrap.c

index 36d23b0461b61d41e04bd5bc13cb0dc91ba399b7..

index ..c49d330bd75122991eacedda5b87475120807ca8 100644

--- a/tlswrap.c
+++ b/tlswrap.c
@@ -7,6 +7,7 @@
 #include <netdb.h>
 #include <netinet/in.h>

+#include <openssl/md5.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <errno.h>
@@ -184,6 +185,217 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) {
   return 1;
 }

+//#define JA3
+#ifdef JA3
+
+/*
+   hey, epoch, if you ever feel like finishing the JA3 shit, link below
+   https://github.com/fooinha/nginx-ssl-ja3/tree/master/src
+ */
+
+
+static const int nid_list[] = {
+    NID_sect163k1,        /* sect163k1 (1) */
+    NID_sect163r1,        /* sect163r1 (2) */
+    NID_sect163r2,        /* sect163r2 (3) */
+    NID_sect193r1,        /* sect193r1 (4) */
+    NID_sect193r2,        /* sect193r2 (5) */
+    NID_sect233k1,        /* sect233k1 (6) */
+    NID_sect233r1,        /* sect233r1 (7) */
+    NID_sect239k1,        /* sect239k1 (8) */
+    NID_sect283k1,        /* sect283k1 (9) */
+    NID_sect283r1,        /* sect283r1 (10) */
+    NID_sect409k1,        /* sect409k1 (11) */
+    NID_sect409r1,        /* sect409r1 (12) */
+    NID_sect571k1,        /* sect571k1 (13) */
+    NID_sect571r1,        /* sect571r1 (14) */
+    NID_secp160k1,        /* secp160k1 (15) */
+    NID_secp160r1,        /* secp160r1 (16) */
+    NID_secp160r2,        /* secp160r2 (17) */
+    NID_secp192k1,        /* secp192k1 (18) */
+    NID_X9_62_prime192v1, /* secp192r1 (19) */
+    NID_secp224k1,        /* secp224k1 (20) */
+    NID_secp224r1,        /* secp224r1 (21) */
+    NID_secp256k1,        /* secp256k1 (22) */
+    NID_X9_62_prime256v1, /* secp256r1 (23) */
+    NID_secp384r1,        /* secp384r1 (24) */
+    NID_secp521r1,        /* secp521r1 (25) */
+    NID_brainpoolP256r1,  /* brainpoolP256r1 (26) */
+    NID_brainpoolP384r1,  /* brainpoolP384r1 (27) */
+    NID_brainpoolP512r1,  /* brainpool512r1 (28) */
+    NID_X25519,           /* X25519 (29) */
+    NID_X448,             /* X448 (30) */
+};
+
+
+static unsigned short
+tlswrap_ssl_ja3_nid_to_cid(int nid)
+{
+    unsigned char i;
+    unsigned char sz = (sizeof(nid_list) / sizeof(nid_list[0]));
+
+    for (i = 0; i < sz; i++) {
+        if (nid == nid_list[i]) {
+            return i+1;
+        }
+    }
+
+    if (nid == NID_ffdhe2048) {
+        return 0x100;
+    }
+    if (nid == NID_ffdhe3072) {
+        return 0x101;
+    }
+    if (nid == NID_ffdhe4096) {
+        return 0x102;
+    }
+    if (nid == NID_ffdhe6144) {
+        return 0x103;
+    }
+    if (nid == NID_ffdhe8192) {
+        return 0x104;
+    }
+
+    return nid;
+}
+
+
+struct ja3 {
+    int version;
+
+    size_t ciphers_sz;
+    unsigned short *ciphers;
+
+    size_t extensions_sz;
+    unsigned int *extensions;
+
+    size_t curves_sz;
+    unsigned short *curves;
+
+    size_t point_formats_sz;
+    unsigned char *point_formats;
+};
+
+// I probably could just export these values to env vars and do the actual concatenation and md5 externally
+void ja3_shit(struct ja3 *j) { // how do we get ssl version?
+  int sz=4096;
+  char *s = malloc(sz);
+  int offset=snprintf(s,sz,"%u,",j->version);
+  int i;
+
+  for(i=0;i < j->ciphers_sz;i++) offset += snprintf(s+offset,sz,"%u-",ntohs(j->ciphers[i]));
+  if(j->ciphers_sz) { offset-- ; } s[offset]=','; offset++;
+
+  for(i=0;i < j->extensions_sz;i++) offset += snprintf(s+offset,sz,"%u-",j->extensions[i]);
+  if(j->extensions_sz) { offset-- ; } s[offset]=','; offset++;
+
+  for(i=0;i < j->curves_sz;i++) offset += snprintf(s+offset,sz,"%u-",j->curves[i]);
+  if(j->curves_sz) { offset-- ; } s[offset]=','; offset++;
+
+  for(i=0;i < j->point_formats_sz;i++) offset += snprintf(s+offset,sz,"%u-",j->point_formats[i]);
+  if(j->point_formats_sz) { offset-- ; } s[offset]='\0'; offset++;
+  // I tested the hashing code. it works compared to https://github.com/salesforce/ja3/tree/master/python
+  unsigned char *p=MD5((unsigned char *)s,strlen(s),NULL);
+  char q[33];
+  for(i=0;i<16;i++) {
+    snprintf(q+(i*2),sizeof(q),"%02x",p[i]);
+  }
+  q[33]=0;
+  //printf("input: %s\n",s);
+  //printf("md5sum: %s\n",q);
+//  syslog(LOG_DAEMON|LOG_CRIT,"ja3:%s",s);
+//  syslog(LOG_DAEMON|LOG_CRIT,"ja3 hashed: %s",q); //let's put these into env vars. :D
+  setenv("SSL_JA3",s,1);
+  setenv("SSL_JA3_DIGEST",q,1);
+  // MD5 the string
+}
+
+void tlswrap_SSL_client_features(struct ja3 *j, SSL *s) {
+
+    unsigned short                *ciphers_out = NULL;
+    int                           *curves_out = NULL;
+    int                           *point_formats_out = NULL;
+    size_t                         i = 0;
+    size_t                         len = 0, sz = 0;
+
+    if (j == NULL) return;
+    j->version = SSL_version(s);
+
+    /* Cipher suites */
+    j->ciphers = NULL;
+    j->ciphers_sz = SSL_get0_raw_cipherlist(s, &ciphers_out);
+    j->ciphers_sz /= 2;
+
+    if (j->ciphers_sz && ciphers_out) {
+        len = j->ciphers_sz * sizeof(unsigned short);
+        j->ciphers = malloc(len);
+        memcpy(j->ciphers, ciphers_out, len);
+    }
+
+    /* Elliptic curve points */
+
+    j->curves_sz = 0;
+    sz = SSL_get1_curves(s, NULL);
+    if (sz) {
+        len = sz * sizeof(unsigned int);
+        curves_out = malloc(len);
+        if (curves_out != NULL) {
+            memset(curves_out, 0, len);
+            SSL_get1_curves(s, curves_out);
+//	    for(i=0 ; i < sz; i++ ){
+//                if( ! (curves_out[i] & 0x1000000)) { //unknown. skip.
+//                     j->curves_sz++;
+//		}
+//	    }
+            j->curves_sz = sz;
+            len = j->curves_sz * sizeof(unsigned short);
+            j->curves = malloc(len);
+            if (j->curves != NULL) {
+                for (i = 0; i < sz; i++) {
+                     //if( ! (curves_out[i] & 0x1000000)) { //unknown. skip?
+                         j->curves[i] = (unsigned short) tlswrap_ssl_ja3_nid_to_cid( curves_out[i]);
+		         //syslog(LOG_DAEMON|LOG_CRIT,"%d",curves_out[i]);
+		     //}
+                }
+            }
+            free(curves_out);
+        }
+    }
+
+    /* Elliptic curve point formats */
+    j->point_formats_sz = SSL_get0_ec_point_formats(s, &point_formats_out);
+    if (j->point_formats_sz && point_formats_out != NULL) {
+        len = j->point_formats_sz * sizeof(unsigned char);
+        j->point_formats = malloc(len);
+        if (j->point_formats != NULL) {
+            memcpy(j->point_formats, point_formats_out, len);
+        }
+    }
+}
+
+int tlswrap_SSL_early_cb_fn(SSL *s, int *al, void *arg) {
+    int                            got_extensions;
+    int                           *ext_out;
+    size_t                         ext_len;
+    struct ja3 *j = arg;
+    //syslog(LOG_DAEMON|LOG_CRIT,"got into the early callback.");
+    got_extensions = SSL_client_hello_get1_extensions_present(s,
+                                                       &ext_out,
+                                                       &ext_len);
+    if (!got_extensions) return 1;
+    if (!ext_out) return 1;
+    if (!ext_len) return 1;
+    j->extensions = malloc(sizeof(int) * ext_len);
+    if (j->extensions != NULL) {
+        j->extensions_sz = ext_len;
+        memcpy(j->extensions, ext_out, sizeof(int) * ext_len);
+    }
+    OPENSSL_free(ext_out);
+    //syslog(LOG_DAEMON|LOG_CRIT,"got to the end of the early callback.");
+    return 1;
+}
+#endif
+
 int main(int argc,char *argv[]) {
   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);
@@ -204,6 +416,11 @@ int main(int argc,char *argv[]) {
   if(argc == 0
     || !strcmp(argv[0],"--help")
     || !strcmp(argv[0],"-h")) {
+#ifdef JA3
+    fprintf(argc?stdout:stderr,"JA3 support was built into this version of tlswrap.\n");
+#else
+    fprintf(argc?stdout:stderr,"JA3 support was NOT built into this version of tlswrap.\n");
+#endif
     fprintf(argc?stdout:stderr,"usage: tlswrap [--help|-h][--verify-mode integer] <cert_chain_file> <priv_key_file> <absolute_path_to_exe> [<arg1>] [<arg2>] [...]\n");
     fprintf(argc?stdout:stderr,"verify mode flags (can be or'd together):\n");
     fprintf(argc?stdout:stderr,"SSL_VERIFY_NONE: %d (no other flags may be set)\n",SSL_VERIFY_NONE);
@@ -278,7 +495,9 @@ int main(int argc,char *argv[]) {
   pipe(b);
   pipe(c);

-  SSL_CTX_set_verify(ctx, verify_mode, verify_callback); SSL_CTX_set_ecdh_auto(ctx, 1);
+
+  SSL_CTX_set_verify(ctx, verify_mode, verify_callback);
+  SSL_CTX_set_ecdh_auto(ctx, 1);

   if(SSL_CTX_use_certificate_chain_file(ctx, cert_chain_file) <= 0) {
     syslog(LOG_DAEMON|LOG_ERR,"failed to load cert chain file: %s",cert_chain_file);
@@ -292,6 +511,11 @@ int main(int argc,char *argv[]) {
   SSL_CTX_set_tlsext_servername_callback(ctx,sni_cb);
   ssl = SSL_new(ctx);

+#ifdef JA3
+  struct ja3 ja3;
+  SSL_CTX_set_client_hello_cb(ctx, tlswrap_SSL_early_cb_fn, &ja3); // JA3
+#endif
+
   SSL_set_rfd(ssl, 0);
   SSL_set_wfd(ssl, 1);
   int err;
@@ -330,6 +554,10 @@ int main(int argc,char *argv[]) {
     //fprintf(stderr,"SSL_accept() failed. %s\n",ERR_lib_error_string(SSL_get_error(ssl,err)));
     return 1;
   }
+#ifdef JA3
+  tlswrap_SSL_client_features(&ja3,ssl);
+  ja3_shit(&ja3);
+#endif
   //fprintf(stderr,"made it here\n");
   syslog(LOG_DAEMON|LOG_DEBUG,"accepted a connection!");
   char buffer[65535];//fuck it. let's make it big.
@@ -393,8 +621,8 @@ int main(int argc,char *argv[]) {
   close(a[0]);
   close(b[1]);
   close(c[1]);
-  unsigned int error_code;
-  unsigned int error_code_size = sizeof(error_code);
+  //unsigned int error_code;
+  //unsigned int error_code_size = sizeof(error_code);
   //syslog(LOG_DAEMON|LOG_DEBUG,"entering select loop");
   //fprintf(stderr,"made it here\n");
   for(;FD_ISSET(b[0],&master) || FD_ISSET(c[0],&master);) { //a select() brick that reads from ssl and writes to subprocess and reads from subprocess and writes to ssl
@@ -455,3 +683,5 @@ int main(int argc,char *argv[]) {
   SSL_free(ssl);
   EVP_cleanup();
 }
+
+

-----END OF PAGE-----

-- Response ended

-- Page fetched on Sun Jun 2 17:39:21 2024