├── nginx.conf ├── openresty-ja3.tar.gz ├── openssl-1.1.1l.tar.gz ├── readme.md └── resty └── ssl.lua /nginx.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes auto; 3 | 4 | error_log logs/error.log; 5 | pid logs/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include mime.types; 15 | default_type application/octet-stream; 16 | 17 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | # '$status $body_bytes_sent "$http_referer" ' 19 | # '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | #access_log logs/access.log main; 22 | 23 | sendfile on; 24 | #tcp_nopush on; 25 | 26 | #keepalive_timeout 0; 27 | keepalive_timeout 65; 28 | 29 | #gzip on; 30 | lua_code_cache off; 31 | 32 | server { 33 | listen 443 ssl; 34 | ssl_session_cache shared:SSL:50m; 35 | ssl_session_timeout 30m; 36 | ssl_certificate ssl.pem; 37 | ssl_certificate_key ssl.key; 38 | access_by_lua_block { 39 | local ssl = require("resty.ssl") 40 | local cjson = require("cjson") 41 | ngx.header["Content-Type"] = "text/html" 42 | local ja3 = ssl.ja3() 43 | ja3.addr = ngx.var.remote_addr 44 | ngx.say(cjson.encode(ja3)) 45 | } 46 | 47 | location / { 48 | root html; 49 | index index.html index.htm; 50 | } 51 | 52 | error_page 500 502 503 504 /50x.html; 53 | location = /50x.html { 54 | root html; 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /openresty-ja3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vela-security/openresty-ssl-ja3/HEAD/openresty-ja3.tar.gz -------------------------------------------------------------------------------- /openssl-1.1.1l.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vela-security/openresty-ssl-ja3/HEAD/openssl-1.1.1l.tar.gz -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # openresty ssl ja3 2 | openresty ssl ja3 的指纹识别扩展 3 | 4 | ## 安装 5 | ```bash 6 | git clone https://github.com/vela-security/openresty-ssl-ja3.git 7 | 8 | cd openresty-ssl-ja3 9 | 10 | bash build.sh 11 | ``` 12 | 13 | ## 配置 14 | ```nginx 15 | listen 443 ssl; 16 | ssl_session_cache shared:SSL:50m; 17 | ssl_session_timeout 30m; 18 | ssl_certificate ssl.pem; 19 | ssl_certificate_key ssl.key; 20 | access_by_lua_block { 21 | local ssl = require("resty.ssl") 22 | local cjson = require("cjson") 23 | ngx.header["Content-Type"] = "text/html" 24 | local ja3 = ssl.ja3() 25 | ja3.addr = ngx.var.remote_addr 26 | ngx.say(cjson.encode(ja3)) 27 | } 28 | ``` 29 | 30 | ## 说明 31 | - openresty nginx 中有代码改动 主要包含 ngx_event_openssl.c 和 ngx_event_openssl.h 32 | - openresty lua nginx 添加了 ngx_http_lua_ssl_ja3.c 和 ngx_http_lua_ssl_ja3.h 33 | - openssl 添加了hello 包信息握手的回调函数 34 | 35 | ## 字段说名 36 | ```json 37 | { 38 | "last": -2, // last time 39 | "verion": 771, //版本 40 | "ciphers": "4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53", 41 | "client_start": "", //client_v_start 42 | "curves": "29-23-24", 43 | "client_remain": "", 44 | "extensions": "23-65281-10-11-35-16-5-13-18-51-45-43-27-21", 45 | "point_formats": "0", 46 | "session": "", 47 | "session_reused": ".", 48 | "protocol": "TLSv1.2", 49 | "curves_raw": "0x7a7a:X25519:prime256v1:secp384r1", 50 | "issuer_legacy": "", 51 | "subject": "", 52 | "subject_legacy": "", 53 | "handshaked": true, 54 | "renegotiation": false, 55 | "handshake_rejected": false, 56 | "server": "", 57 | "ciphers_raw": "0x5a5a:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA", 58 | "issuer_raw": "", 59 | "fp_raw": "", 60 | "serial": "", 61 | "fp": "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0", //指纹信息 62 | "cipher_name": "ECDHE-RSA-AES128-GCM-SHA256", 63 | "client_end": "", 64 | "hash": "b592adaa596bb72a5c1ccdbecae52e3f" //JA3指纹信息 65 | 66 | } 67 | 68 | ``` 69 | 70 | ## 参考 71 | [https://github.com/fooinha/nginx-ssl-ja3](https://github.com/fooinha/nginx-ssl-ja3) -------------------------------------------------------------------------------- /resty/ssl.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local base = require("resty.core.base") 4 | local ffi = require('ffi') 5 | local C = ffi.C 6 | local ffi_str = ffi.string 7 | local ffi_typeof = ffi.typeof 8 | local ffi_cast = ffi.cast 9 | local new_tab = require "table.new" 10 | local ngx_md5 = ngx.md5 11 | 12 | ffi.cdef[[ 13 | typedef struct { 14 | int version; 15 | size_t ciphers_sz; 16 | unsigned short *ciphers; 17 | 18 | size_t extensions_sz; 19 | unsigned short *extensions; 20 | 21 | size_t curves_sz; 22 | unsigned short *curves; 23 | 24 | size_t point_formats_sz; 25 | unsigned char *point_formats; 26 | 27 | ngx_str_t cause; 28 | ngx_str_t issuer; 29 | ngx_str_t issuer_legacy; 30 | ngx_str_t subject; 31 | ngx_str_t subject_legacy; 32 | ngx_str_t session; 33 | ngx_str_t session_reused; 34 | ngx_str_t protocol; 35 | ngx_str_t curves_v; 36 | ngx_str_t ciphers_v; 37 | ngx_str_t cipher_name; 38 | ngx_str_t server_name; 39 | ngx_str_t fingerprint; 40 | ngx_str_t serial_number; 41 | ngx_str_t client_verify; 42 | ngx_str_t client_start; 43 | ngx_str_t client_end; 44 | ngx_str_t client_remain; 45 | 46 | int last; 47 | unsigned in_ocsp:1; 48 | unsigned handshaked:1; 49 | unsigned renegotiation:1; 50 | unsigned handshake_rejected:1; 51 | 52 | } ngx_ssl_ffi_ja3_t; 53 | 54 | ngx_ssl_ffi_ja3_t *ngx_http_lua_ffi_ssl_ja3(ngx_http_request_t *); 55 | ngx_str_t ngx_http_lua_ffi_ssl_ja3_fp(ngx_http_request_t * , ngx_ssl_ffi_ja3_t *); 56 | 57 | ]] 58 | 59 | --[[ 60 | local ssl = require("resty.ssl") 61 | 62 | local ja3 = ssl.ja3() 63 | ja3.version 64 | ja3.ciphers 65 | ja3.session 66 | ja3.server_name 67 | ja3.fp 68 | ja3.fingerprint 69 | ja3.last 70 | ja3.handshake 71 | ]] 72 | 73 | local function join(ptr , size) 74 | local len = tonumber(size) 75 | 76 | if len == 0 then 77 | return "" 78 | end 79 | 80 | local sum = "" 81 | 82 | local arr = new_tab( 0 , len * 2 - 1) 83 | local idx = 1 84 | for i = 0 , len - 1 do 85 | if i == 0 then 86 | sum = sum .. tonumber(ptr[i]) 87 | else 88 | sum = sum .."-" .. tonumber(ptr[i]) 89 | end 90 | end 91 | 92 | return sum 93 | end 94 | 95 | local function unpack(cdata) --cdata: ngx_str_t 96 | return ffi_str(cdata.data , tonumber(cdata.len)) 97 | end 98 | 99 | local function to_bool(cdata) 100 | return tonumber(cdata) == 1 101 | end 102 | 103 | function _M.ja3() 104 | local r = base.get_request() 105 | local cdata = C.ngx_http_lua_ffi_ssl_ja3(r) 106 | 107 | if cdata == ngx.NULL then 108 | return nil 109 | end 110 | 111 | local fp = C.ngx_http_lua_ffi_ssl_ja3_fp(r , cdata) 112 | 113 | local fv = ffi_str(fp.data , fp.len) 114 | 115 | local hash = ngx_md5(fv) 116 | 117 | local ja3 = { 118 | last = tonumber(cdata.last), 119 | 120 | verion = tonumber(cdata.version), 121 | 122 | ciphers = join(cdata.ciphers , cdata.ciphers_sz), 123 | 124 | curves = join(cdata.curves , cdata.curves_sz), 125 | 126 | extensions = join(cdata.extensions, cdata.extensions_sz), 127 | 128 | point_formats = join(cdata.point_formats ,cdata.point_formats_sz), 129 | 130 | 131 | session = unpack(cdata.session), 132 | session_reused = unpack(cdata.session_reused), 133 | 134 | protocol = unpack(cdata.protocol), 135 | 136 | curves_raw = unpack(cdata.curves_v), 137 | ciphers_raw = unpack(cdata.ciphers_v), 138 | cipher_name = unpack(cdata.cipher_name), 139 | 140 | client_start = unpack(cdata.client_start), 141 | client_end = unpack(cdata.client_end), 142 | client_remain = unpack(cdata.client_remain), 143 | 144 | server = unpack(cdata.server_name), 145 | 146 | serial = unpack(cdata.serial_number), 147 | 148 | fp_raw = unpack(cdata.fingerprint), 149 | issuer_raw = unpack(cdata.issuer), 150 | issuer_legacy = unpack(cdata.issuer_legacy), 151 | 152 | subject = unpack(cdata.subject), 153 | subject_legacy = unpack(cdata.subject_legacy), 154 | 155 | 156 | handshaked = to_bool(cdata.handshaked), 157 | 158 | renegotiation = to_bool(cdata.renegotiation), 159 | 160 | handshake_rejected = to_bool(cdata.handshake_rejected), 161 | 162 | fp = fv, 163 | 164 | hash = hash, 165 | 166 | --cause = ffi_str(cdata.cause.data , tonumber(cdata.cause.len)), 167 | } 168 | 169 | 170 | return ja3 171 | 172 | end 173 | 174 | return _M 175 | --------------------------------------------------------------------------------