Skip to content

Commit b826b1a

Browse files
committed
Check our JA3 fingerprints against ja3.zone
1 parent 432c494 commit b826b1a

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

test/test-util.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as stream from 'stream';
2+
13
export type Deferred<T> = Promise<T> & {
24
resolve(value: T): void,
35
reject(e: Error): void
@@ -14,4 +16,14 @@ export function getDeferred<T>(): Deferred<T> {
1416
result.reject = rejectCallback!;
1517

1618
return result;
19+
}
20+
21+
export async function streamToBuffer(stream: stream.Readable): Promise<Buffer> {
22+
const data: Buffer[] = [];
23+
stream.on('data', (d) => data.push(d));
24+
25+
return new Promise((resolve, reject) => {
26+
stream.on('end', () => resolve(Buffer.concat(data)));
27+
stream.on('error', reject);
28+
});
1729
}

test/test.spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as https from 'https';
44
import { makeDestroyable, DestroyableServer } from 'destroyable-server';
55

66
import { expect } from 'chai';
7-
import { getDeferred } from './test-util';
7+
import { getDeferred, streamToBuffer } from './test-util';
88

99
import {
1010
getTlsFingerprintData,
@@ -89,4 +89,41 @@ describe("Read-TLS-Fingerprint", () => {
8989
]);
9090
});
9191

92+
it("calculates the same fingerprint as ja3.zone", async () => {
93+
server = makeDestroyable(new net.Server());
94+
95+
server.listen();
96+
await new Promise((resolve) => server.on('listening', resolve));
97+
98+
let incomingSocketPromise = getDeferred<net.Socket>();
99+
server.on('connection', (socket) => incomingSocketPromise.resolve(socket));
100+
101+
const port = (server.address() as net.AddressInfo).port;
102+
https.request({
103+
host: 'localhost',
104+
port
105+
}).on('error', () => {}); // Socket will fail, since server never responds, that's OK
106+
107+
const incomingSocket = await incomingSocketPromise;
108+
const ourFingerprint = await getTlsFingerprintAsJa3(incomingSocket);
109+
110+
const remoteFingerprint = await new Promise((resolve, reject) => {
111+
const response = https.get('https://check.ja3.zone/');
112+
response.on('response', async (resp) => {
113+
if (resp.statusCode !== 200) reject(new Error(`Unexpected ${resp.statusCode} from ja3.zon`));
114+
115+
try {
116+
const rawData = await streamToBuffer(resp);
117+
const data = JSON.parse(rawData.toString());
118+
resolve(data.hash);
119+
} catch (e) {
120+
reject(e);
121+
}
122+
});
123+
response.on('error', reject);
124+
});
125+
126+
expect(ourFingerprint).to.equal(remoteFingerprint);
127+
});
128+
92129
});

0 commit comments

Comments
 (0)