Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
a3957273 committed Apr 13, 2024
2 parents 0cfb67b + edc23a8 commit f6c5a04
Show file tree
Hide file tree
Showing 21 changed files with 1,586 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of

## Details

### [10.16.0] - 2024-04-12
- Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789]

### [10.15.0] - 2024-04-02
- Fix Ciphersaber2 key concatenation [@zb3] | [#1765]
- Fix DeriveEVPKey's array parsing [@zb3] | [#1767]
Expand Down Expand Up @@ -418,6 +421,7 @@ All major and minor version changes will be documented in this file. Details of
## [4.0.0] - 2016-11-28
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)

[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0
[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0
[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0
[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0
Expand Down Expand Up @@ -744,3 +748,5 @@ All major and minor version changes will be documented in this file. Details of
[#1504]: https://github.com/gchq/CyberChef/issues/1504
[#512]: https://github.com/gchq/CyberChef/issues/512
[#1732]: https://github.com/gchq/CyberChef/issues/1732
[#1789]: https://github.com/gchq/CyberChef/issues/1789

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "10.15.1",
"version": "10.16.0",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
Expand Down
7 changes: 6 additions & 1 deletion src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@
"Hex to PEM",
"Hex to Object Identifier",
"Object Identifier to Hex",
"PEM to JWK",
"JWK to PEM",
"Generate PGP Key Pair",
"PGP Encrypt",
"PGP Decrypt",
Expand All @@ -180,7 +182,9 @@
"RSA Encrypt",
"RSA Decrypt",
"Parse SSH Host Key",
"Parse CSR"
"Parse CSR",
"Public Key from Certificate",
"Public Key from Private Key"
]
},
{
Expand Down Expand Up @@ -238,6 +242,7 @@
"JA3 Fingerprint",
"JA3S Fingerprint",
"JA4 Fingerprint",
"JA4Server Fingerprint",
"HASSH Client Fingerprint",
"HASSH Server Fingerprint",
"Format MAC addresses",
Expand Down
118 changes: 108 additions & 10 deletions src/core/lib/JA4.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export function toJA4(bytes) {
let tlsr = {};
try {
tlsr = parseTLSRecord(bytes);
if (tlsr.handshake.value.handshakeType.value !== 0x01) {
throw new Error();
}
} catch (err) {
throw new OperationError("Data is not a valid TLS Client Hello. QUIC is not yet supported.\n" + err);
}
Expand All @@ -48,16 +51,7 @@ export function toJA4(bytes) {
break;
}
}
switch (version) {
case 0x0304: version = "13"; break; // TLS 1.3
case 0x0303: version = "12"; break; // TLS 1.2
case 0x0302: version = "11"; break; // TLS 1.1
case 0x0301: version = "10"; break; // TLS 1.0
case 0x0300: version = "s3"; break; // SSL 3.0
case 0x0200: version = "s2"; break; // SSL 2.0
case 0x0100: version = "s1"; break; // SSL 1.0
default: version = "00"; // Unknown
}
version = tlsVersionMapper(version);

/* SNI
If the SNI extension (0x0000) exists, then the destination of the connection is a domain, or “d” in the fingerprint.
Expand Down Expand Up @@ -99,6 +93,7 @@ export function toJA4(bytes) {
if (ext.type.value === "application_layer_protocol_negotiation") {
alpn = parseFirstALPNValue(ext.value.data);
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
if (alpn.charCodeAt(0) > 127) alpn = "99";
break;
}
}
Expand Down Expand Up @@ -164,3 +159,106 @@ export function toJA4(bytes) {
"JA4_ro": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphersRaw}_${originalExtensionsRaw}`,
};
}


/**
* Calculate the JA4Server from a given TLS Server Hello Stream
* @param {Uint8Array} bytes
* @returns {string}
*/
export function toJA4S(bytes) {
let tlsr = {};
try {
tlsr = parseTLSRecord(bytes);
if (tlsr.handshake.value.handshakeType.value !== 0x02) {
throw new Error();
}
} catch (err) {
throw new OperationError("Data is not a valid TLS Server Hello. QUIC is not yet supported.\n" + err);
}

/* QUIC
“q” or “t”, which denotes whether the hello packet is for QUIC or TCP.
TODO: Implement QUIC
*/
const ptype = "t";

/* TLS Version
TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version
is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then
the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
should be ignored.
*/
let version = tlsr.version.value;
for (const ext of tlsr.handshake.value.extensions.value) {
if (ext.type.value === "supported_versions") {
version = parseHighestSupportedVersion(ext.value.data);
break;
}
}
version = tlsVersionMapper(version);

/* Number of Extensions
2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”.
If there’s > 99, which there should never be, then output “99”.
*/
let extLen = tlsr.handshake.value.extensions.value.length;
extLen = extLen > 99 ? "99" : extLen.toString().padStart(2, "0");

/* ALPN Extension Chosen Value
The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.
If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint.
*/
let alpn = "00";
for (const ext of tlsr.handshake.value.extensions.value) {
if (ext.type.value === "application_layer_protocol_negotiation") {
alpn = parseFirstALPNValue(ext.value.data);
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
if (alpn.charCodeAt(0) > 127) alpn = "99";
break;
}
}

/* Chosen Cipher
The hex value of the chosen cipher suite
*/
const cipher = toHexFast(tlsr.handshake.value.cipherSuite.data);

/* Extension hash
A 12 character truncated sha256 hash of the list of extensions.
The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited.
*/
const extensionsList = [];
for (const ext of tlsr.handshake.value.extensions.value) {
extensionsList.push(toHexFast(ext.type.data));
}
const extensionsRaw = extensionsList.join(",");
const extensionsHash = runHash(
"sha256",
Utils.strToArrayBuffer(extensionsRaw)
).substring(0, 12);

return {
"JA4S": `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsHash}`,
"JA4S_r": `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsRaw}`,
};
}


/**
* Takes a TLS version value and returns a JA4 TLS version string
* @param {Uint8Array} version - Two byte array of version number
* @returns {string}
*/
function tlsVersionMapper(version) {
switch (version) {
case 0x0304: return "13"; // TLS 1.3
case 0x0303: return "12"; // TLS 1.2
case 0x0302: return "11"; // TLS 1.1
case 0x0301: return "10"; // TLS 1.0
case 0x0300: return "s3"; // SSL 3.0
case 0x0200: return "s2"; // SSL 2.0
case 0x0100: return "s1"; // SSL 1.0
default: return "00"; // Unknown
}
}
109 changes: 105 additions & 4 deletions src/core/lib/TLS.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,11 @@ function parseHandshake(bytes) {

// Handshake type
h.handshakeType = {
description: "Client Hello",
description: "Handshake Type",
length: 1,
data: b.getBytes(1),
value: s.readInt(1)
};
if (h.handshakeType.value !== 0x01)
throw new OperationError("Not a Client Hello.");

// Handshake length
h.handshakeLength = {
Expand All @@ -86,8 +84,33 @@ function parseHandshake(bytes) {
value: s.readInt(3)
};
if (s.length !== h.handshakeLength.value + 4)
throw new OperationError("Not enough data in Client Hello.");
throw new OperationError("Not enough data in Handshake message.");


switch (h.handshakeType.value) {
case 0x01:
h.handshakeType.description = "Client Hello";
parseClientHello(s, b, h);
break;
case 0x02:
h.handshakeType.description = "Server Hello";
parseServerHello(s, b, h);
break;
default:
throw new OperationError("Not a known handshake message.");
}

return h;
}

/**
* Parse a TLS Client Hello
* @param {Stream} s
* @param {Stream} b
* @param {Object} h
* @returns {JSON}
*/
function parseClientHello(s, b, h) {
// Hello version
h.helloVersion = {
description: "Client Hello Version",
Expand Down Expand Up @@ -171,6 +194,79 @@ function parseHandshake(bytes) {
return h;
}

/**
* Parse a TLS Server Hello
* @param {Stream} s
* @param {Stream} b
* @param {Object} h
* @returns {JSON}
*/
function parseServerHello(s, b, h) {
// Hello version
h.helloVersion = {
description: "Server Hello Version",
length: 2,
data: b.getBytes(2),
value: s.readInt(2)
};

// Random
h.random = {
description: "Server Random",
length: 32,
data: b.getBytes(32),
value: s.getBytes(32)
};

// Session ID Length
h.sessionIDLength = {
description: "Session ID Length",
length: 1,
data: b.getBytes(1),
value: s.readInt(1)
};

// Session ID
h.sessionID = {
description: "Session ID",
length: h.sessionIDLength.value,
data: b.getBytes(h.sessionIDLength.value),
value: s.getBytes(h.sessionIDLength.value)
};

// Cipher Suite
h.cipherSuite = {
description: "Selected Cipher Suite",
length: 2,
data: b.getBytes(2),
value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown"
};

// Compression Method
h.compressionMethod = {
description: "Selected Compression Method",
length: 1,
data: b.getBytes(1),
value: s.readInt(1) // TODO: Compression method name here
};

// Extensions Length
h.extensionsLength = {
description: "Extensions Length",
length: 2,
data: b.getBytes(2),
value: s.readInt(2)
};

// Extensions
h.extensions = {
description: "Extensions",
length: h.extensionsLength.value,
data: b.getBytes(h.extensionsLength.value),
value: parseExtensions(s.getBytes(h.extensionsLength.value))
};
}

/**
* Parse Cipher Suites
* @param {Uint8Array} bytes
Expand Down Expand Up @@ -748,6 +844,11 @@ export const GREASE_VALUES = [
export function parseHighestSupportedVersion(bytes) {
const s = new Stream(bytes);

// The Server Hello supported_versions extension simply contains the chosen version
if (s.length === 2) {
return s.readInt(2);
}

// Length
let i = s.readInt(1);

Expand Down
Loading

0 comments on commit f6c5a04

Please sign in to comment.