repo: tlswrap action: commit revision: path_from: revision_from: 2d0a4485eb028d224682f73ec9440b53250dc52f: path_to: revision_to:
commit 2d0a4485eb028d224682f73ec9440b53250dc52f Author: epochDate: Sat Apr 19 09:16:15 2025 +0000 moved the ja3 code into its own file diff --git a/Makefile b/Makefile
--- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ tlswrap: LDLIBS=-lssl -lcrypto tlswrap-ja3: CFLAGS=-DJA3 -pedantic -Wall tlswrap-ja3: LDLIBS=-lssl -lcrypto +tlswrap-ja3: tlswrap-ja3.o ja3.o tlswrap-no-shutdown: CFLAGS=-DNOSHUTDOWN -pedantic -Wall tlswrap-no-shutdown: LDLIBS=-lssl -lcrypto diff --git a/ja3.c b/ja3.c new file mode 100644 index 0000000000000000000000000000000000000000..2ee62235f78c6fe13fbc73d545a7c58a4c8e19e6 --- /dev/null +++ b/ja3.c @@ -0,0 +1,217 @@ +#include+#include +#include +#include +#include + +#include "ja3.h" + +/* + 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; +} + + +// 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 + + + // these functions are deprecated too. + /*MD5_CTX c; + unsigned char p[16]; + MD5_Init(&c); + MD5_Update(&c, s, strlen(s)); + MD5_Final(p, &c);*/ + + // looked up what the non-deprecated way was supposed to be. + // https://github.com/openssl/openssl/discussions/23493 +#if OPENSSL_VERSION_MAJOR == 3 + unsigned char p[16]; + if(!EVP_Q_digest( NULL, "MD5", NULL, s, strlen(s), p, NULL )) { + syslog(LOG_DAEMON|LOG_ERR, "MD5 hashing failed: %s",strerror(errno)); + } +#else + // MD5() is deprecated. + unsigned char *p=MD5((unsigned char *)s,strlen(s),NULL); +#endif + + 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; +} diff --git a/ja3.h b/ja3.h new file mode 100644 index 0000000000000000000000000000000000000000..8b1d51db8b556c9f0990629dce571cd08c994b0d --- /dev/null +++ b/ja3.h @@ -0,0 +1,24 @@ +#ifndef _TLSWRAP_JA3_H_ +#define _TLSWRAP_JA3_H_ + +// + +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; +}; + +int tlswrap_SSL_early_cb_fn(SSL *s, int *al, void *arg); +void ja3_shit(struct ja3 *j); +#endif diff --git a/tlswrap.c b/tlswrap.c
--- a/tlswrap.c +++ b/tlswrap.c @@ -22,6 +22,10 @@ #include#include +#ifdef JA3 +#include "ja3.h" +#endif + //#define TRAFFIC_LOGGING "1.2.3.4" // use this to debug exact request and response data going on. no timestamps inside yet. //#define FORCE_SNI //whether SNI is required to connect to this server. @@ -224,236 +228,6 @@ 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 - - - // these functions are deprecated too. - /*MD5_CTX c; - unsigned char p[16]; - MD5_Init(&c); - MD5_Update(&c, s, strlen(s)); - MD5_Final(p, &c);*/ - - // looked up what the non-deprecated way was supposed to be. - // https://github.com/openssl/openssl/discussions/23493 -#if OPENSSL_VERSION_MAJOR == 3 - unsigned char p[16]; - if(!EVP_Q_digest( NULL, "MD5", NULL, s, strlen(s), p, NULL )) { - syslog(LOG_DAEMON|LOG_ERR, "MD5 hashing failed: %s",strerror(errno)); - } -#else - // MD5() is deprecated. - unsigned char *p=MD5((unsigned char *)s,strlen(s),NULL); -#endif - - 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 #endif /* #ifndef NONTLS */ void signal_handler(int sig) {
-----END OF PAGE-----