diff --git a/_examples/print-request/main.go b/_examples/print-request/main.go index 498f07f..944f8f1 100644 --- a/_examples/print-request/main.go +++ b/_examples/print-request/main.go @@ -18,7 +18,11 @@ func main() { fmt.Fprintf(w, "ProtoMajor: %d\n", r.ProtoMajor) fmt.Fprintf(w, "ProtoMinor: %d\n", r.ProtoMinor) fmt.Fprintf(w, "RemoteAddr: %q\n", r.RemoteAddr) - fmt.Fprintf(w, "TLSInfo: %#v\n", r.TLSInfo) + fmt.Fprintf(w, "TLSInfo:\n") + fmt.Fprintf(w, " Protocol: %s\n", r.TLSInfo.Protocol) + fmt.Fprintf(w, " CipherOpenSSLName: %s\n", r.TLSInfo.CipherOpenSSLName) + fmt.Fprintf(w, " JA3MD5: %#x\n", r.TLSInfo.JA3MD5) + fmt.Fprintf(w, " ClientHello: %#x\n", r.TLSInfo.ClientHello) fmt.Fprintf(w, "\n") diff --git a/fsthttp/request.go b/fsthttp/request.go index 2af3d20..a5c9d17 100644 --- a/fsthttp/request.go +++ b/fsthttp/request.go @@ -134,7 +134,7 @@ func NewRequest(method string, uri string, body io.Reader) (*Request, error) { } // _parseRequestURI can be set by SetParseRequestURI -var _parseRequestURI func(string)(*url.URL, error) = url.ParseRequestURI +var _parseRequestURI func(string) (*url.URL, error) = url.ParseRequestURI func newClientRequest() (*Request, error) { abiReq, abiReqBody, err := fastly.BodyDownstreamGet() @@ -206,6 +206,11 @@ func newClientRequest() (*Request, error) { if err != nil { return nil, fmt.Errorf("get TLS cipher name: %w", err) } + + tlsInfo.JA3MD5, err = fastly.DownstreamTLSJA3MD5() + if err != nil { + return nil, fmt.Errorf("get TLS JA3 MD5: %w", err) + } } // Setting the fsthttp.Request Host field to the url.URL Host field is @@ -559,6 +564,10 @@ type TLSInfo struct { // connection. The value returned will be consistent with the OpenSSL name // for the cipher suite. CipherOpenSSLName string + + // JA3MD5 contains the bytes of the JA3 signature of the client TLS request. + // See https://www.fastly.com/blog/the-state-of-tls-fingerprinting-whats-working-what-isnt-and-whats-next + JA3MD5 []byte } // DecompressResponseOptions control the auto decompress response behaviour. diff --git a/internal/abi/fastly/hostcalls_noguest.go b/internal/abi/fastly/hostcalls_noguest.go index 95b175b..41e41f0 100644 --- a/internal/abi/fastly/hostcalls_noguest.go +++ b/internal/abi/fastly/hostcalls_noguest.go @@ -83,6 +83,10 @@ func DownstreamTLSClientHello() ([]byte, error) { return nil, fmt.Errorf("not implemented") } +func DownstreamTLSJA3MD5() ([]byte, error) { + return nil, fmt.Errorf("not implemented") +} + func NewHTTPRequest() (*HTTPRequest, error) { return nil, fmt.Errorf("not implemented") } diff --git a/internal/abi/fastly/http_guest.go b/internal/abi/fastly/http_guest.go index 889b24c..d9d1e10 100644 --- a/internal/abi/fastly/http_guest.go +++ b/internal/abi/fastly/http_guest.go @@ -350,6 +350,35 @@ func DownstreamTLSClientHello() ([]byte, error) { } } +// witx: +// +// (@interface func (export "downstream_tls_ja3_md5") +// ;; must be a 16-byte array +// (param $cja3_md5_out (@witx pointer (@witx char8))) +// (result $err (expected $num_bytes (error $fastly_status))) +// ) +// +//go:wasmimport fastly_http_req downstream_tls_ja3_md5 +//go:noescape +func fastlyHTTPReqDownstreamTLSJA3MD5( + cJA3MD5Out prim.Pointer[prim.Char8], + nwrittenOut prim.Pointer[prim.Usize], +) FastlyStatus + +// DownstreamTLSJA3MD5 returns the MD5 []byte representing the JA3 signature of the singleton downstream request, if any. +func DownstreamTLSJA3MD5() ([]byte, error) { + var p [16]byte + buf := prim.NewWriteBufferFromBytes(p[:]) + err := fastlyHTTPReqDownstreamTLSJA3MD5( + prim.ToPointer(buf.Char8Pointer()), + prim.ToPointer(buf.NPointer()), + ).toError() + if err != nil { + return nil, err + } + return buf.AsBytes(), nil +} + // witx: // // (@interface func (export "new")