Added metadata extraction for UUID strings. This includes expanding on the operation to process versions 1,6,7.
Added option to include metadata Added tests and file to test index
This commit is contained in:
parent
093346859d
commit
5d033efb35
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author ko80240 [csk.dev@proton.me]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
@ -8,6 +8,7 @@ import * as uuid from "uuid";
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* Analyse UUID operation
|
||||
@ -22,27 +23,128 @@ class AnalyseUUID extends Operation {
|
||||
|
||||
this.name = "Analyse UUID";
|
||||
this.module = "Crypto";
|
||||
this.description = "Tries to determine information about a given UUID and suggests which version may have been used to generate it";
|
||||
this.description = "Operation for extracting metadata and detecting the version of a given UUID.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
name: "Include Metadata",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {string} input - Expects a valid UUID string
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
input = input.trim();
|
||||
|
||||
let uuidVersion, uuidBytes;
|
||||
try {
|
||||
const uuidVersion = uuid.version(input);
|
||||
return "UUID version: " + uuidVersion;
|
||||
uuidVersion = uuid.version(input); // Re-using the uuid library to extract version
|
||||
uuidBytes = uuid.parse(input); // Re-using the uuid library to parse bytes
|
||||
} catch (error) {
|
||||
throw new OperationError("Invalid UUID");
|
||||
}
|
||||
|
||||
const [includeMetadata] = args;
|
||||
const dv = new DataView(uuidBytes.buffer, uuidBytes.byteOffset, uuidBytes.byteLength); // Dataview helps handle the multi-byte ints
|
||||
const uuidInteger = (dv.getBigUint64(0) << 64n) | dv.getBigUint64(8);
|
||||
|
||||
const sections = [`Version:\n${uuidVersion}`];
|
||||
|
||||
if (includeMetadata) {
|
||||
const parser = UUID_PARSERS[uuidVersion];
|
||||
const decoded = parser?.(uuidBytes, dv);
|
||||
sections.push(formatDecoded(decoded));
|
||||
}
|
||||
|
||||
sections.push(`UUID Integer:\n${uuidInteger}`);
|
||||
|
||||
return sections.filter(Boolean).join("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
export default AnalyseUUID;
|
||||
|
||||
/**
|
||||
* Metadata can be extracted for versions 1, 6, and 7.
|
||||
* Enum-like frozen mapping of UUID version to parser function.
|
||||
*/
|
||||
const UUID_PARSERS = Object.freeze({
|
||||
1: parsev1v6,
|
||||
6: parsev1v6,
|
||||
7: parsev7,
|
||||
});
|
||||
|
||||
/**
|
||||
* Versions 1 and 6. Note 6 is a re-order of 1.
|
||||
* Version 1 == layout: timeLow(32) | timeMid(16) | timeHi(12)
|
||||
* Version 6 == layout: timeHi(32) | timeMid(16) | timeLow(12)
|
||||
*/
|
||||
function parsev1v6(uuidBytes, dv) {
|
||||
const isV1 = (uuidBytes[6] >> 4) === 1;
|
||||
|
||||
const timeStamp =
|
||||
isV1 ? (
|
||||
(BigInt(dv.getUint16(6) & 0x0fff) << 48n) | // mask off version bits
|
||||
(BigInt(dv.getUint16(4)) << 32n) |
|
||||
BigInt(dv.getUint32(0))
|
||||
) : (
|
||||
(BigInt(dv.getUint32(0)) << 28n) |
|
||||
(BigInt(dv.getUint16(4)) << 12n) |
|
||||
(BigInt(dv.getUint16(6) & 0x0fff))
|
||||
);
|
||||
|
||||
// Convert to Unix time
|
||||
const milliseconds =
|
||||
Number(
|
||||
(timeStamp - 122192928000000000n) / 10000n
|
||||
);
|
||||
|
||||
return {
|
||||
timestamp: milliseconds,
|
||||
isoTimestamp: new Date(milliseconds).toISOString(),
|
||||
clock: ((uuidBytes[8] & 0x3f) << 8) | uuidBytes[9],
|
||||
node: toHex(uuidBytes.slice(10), ":").toUpperCase()
|
||||
};
|
||||
}
|
||||
|
||||
/** Version 7 */
|
||||
function parsev7(uuidBytes, dv) {
|
||||
const milliseconds = Number((BigInt(dv.getUint32(0)) << 16n) | BigInt(dv.getUint16(4)));
|
||||
|
||||
return {
|
||||
timestamp: milliseconds,
|
||||
isoTimestamp: new Date(milliseconds).toISOString(),
|
||||
randA: ((uuidBytes[6] & 0x0f) << 8) | uuidBytes[7],
|
||||
randB: toHex(uuidBytes.slice(8), "").toUpperCase()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats metadata
|
||||
*
|
||||
* @param {Object|undefined} decoded
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatDecoded(decoded) {
|
||||
if (!decoded) return "No metadata available. Only versions 1, 6, 7 are supported.";
|
||||
|
||||
return Object.entries({
|
||||
"Timestamp": decoded.timestamp,
|
||||
"Timestamp (ISO)": decoded.isoTimestamp,
|
||||
"Node": decoded.node,
|
||||
"Clock": decoded.clock,
|
||||
"Rand A": decoded.randA,
|
||||
"Rand B": decoded.randB
|
||||
})
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([label, value]) => `${label}:\n${value}`)
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
43
tatus
Normal file
43
tatus
Normal file
@ -0,0 +1,43 @@
|
||||
[33me5305a4f[m[33m ([m[1;36mHEAD -> [m[1;32mfeature/analyseUUID[m[33m)[m Feature: Added metadata extraction for AnalyseUUID operation with tests and test index.
|
||||
[33m53583814[m[33m ([m[1;31morigin/master[m[33m, [m[1;31morigin/HEAD[m[33m, [m[1;32mmaster[m[33m)[m Regular Expression operation email address regex: Support IPv4 domains (#2167)
|
||||
[33mf646065c[m Rewriting fixCryptoApiImports and fixSnackbarMarkup to js to make it OS agnostic (#2298)
|
||||
[33m2eb8810b[m chore (deps): bump basic-ftp from 5.2.1 to 5.2.2 (#2317)
|
||||
[33md8830e2b[m chore (deps): bump axios from 1.13.6 to 1.15.0 (#2316)
|
||||
[33m7b9ff751[m chore (deps): bump webpack from 5.105.4 to 5.106.0 (#2315)
|
||||
[33mc68cc5a0[m chore (deps): bump basic-ftp from 5.2.0 to 5.2.1 (#2313)
|
||||
[33m39f97a2a[m Update vulnerable dependencies (#2311)
|
||||
[33m42adf5b1[m[33m ([m[1;33mtag: v10.23.0[m[33m)[m Bump v10.23.0 (#2310)
|
||||
[33m167cc398[m Properly escape HTML entities in sampleDelim to avoid XSS issue (#2307)
|
||||
[33m33314ac6[m chore (deps): bump lodash from 4.17.23 to 4.18.1 (#2304)
|
||||
[33me1352065[m chore (deps): bump @codemirror/view from 6.40.0 to 6.41.0 (#2305)
|
||||
[33meea8bcd2[m chore (deps): bump the patch-updates group with 2 updates (#2303)
|
||||
[33m7d9fd4b2[m chore (deps): bump @xmldom/xmldom from 0.8.11 to 0.8.12 (#2302)
|
||||
[33m80286f1e[m chore (deps): bump picomatch (#2299)
|
||||
[33mc00824a8[m chore (deps): bump node-forge from 1.3.3 to 1.4.0 (#2297)
|
||||
[33mf9184d39[m chore (deps): bump the patch-updates group with 3 updates (#2296)
|
||||
[33m088da9de[m chore (deps) bump chromedriver from 130.0.4 to 146.0.6 (#2292)
|
||||
[33m6aa98b4a[m ParseEthernetFrame - Fix vlan calculation (#2295)
|
||||
[33mb0fa1f8d[m Add pull request template with AI usage disclosure (#2279)
|
||||
[33mff63ec97[m fix: return empty output for zero-length To Modhex input (#2249)
|
||||
[33m9cf82cc1[m Added tab focus to top banner and navigation to About/Support Modal (#1733)
|
||||
[33m78d40eab[m Add Parse Ethernet frame Operation, allow Parse IPv4 Header to cascade (#1722)
|
||||
[33m2b370b96[m Selection and Deselection of autobake checkbox using keyboard (#1727)
|
||||
[33m38a0adaf[m chore (deps): bump @babel/runtime from 7.28.6 to 7.29.2 (#2263)
|
||||
[33m8c4a55b2[m Add more helpful error for when numerical ingredient is left empty (#1540)
|
||||
[33ma1f92082[m chore (deps): bump @codemirror/view from 6.39.17 to 6.40.0 (#2262)
|
||||
[33m32ff3cd5[m Bump flatted from 3.3.2 to 3.4.2 (#2266)
|
||||
[33m290f824e[m feat: add Raw option for Jq operation (#2237)
|
||||
[33m7f4f90e4[m chore (deps): bump core-js from 3.48.0 to 3.49.0 (#2261)
|
||||
[33m607acbd2[m chore (deps): bump the patch-updates group with 6 updates (#2260)
|
||||
[33mf7bbb330[m Add Extract Audio Metadata operation (#2170)
|
||||
[33md9dacf6b[m Fix Jq issue (#2210)
|
||||
[33m19bc8169[m Configure dependabot updates (#2259)
|
||||
[33meef8d55d[m fix(A1Z26): return empty string instead of empty array for empty input (#2257)
|
||||
[33m4deaa0de[m Fix broken Docker link in README (#2250)
|
||||
[33md195a51e[m Update some dependencies, including a number causing npm audit warnings (#2236)
|
||||
[33m47ba0585[m Bump axios from 1.7.9 to 1.13.6 (#2234)
|
||||
[33m64224dac[m Bump jws from 3.2.2 to 3.2.3 (#2235)
|
||||
[33m76bf4f44[m Bump pbkdf2 from 3.1.2 to 3.1.5 (#2229)
|
||||
[33mc39ae52f[m Bump form-data from 4.0.1 to 4.0.5 (#2228)
|
||||
[33m3f91df23[m Bump basic-ftp from 5.0.5 to 5.2.0 (#2231)
|
||||
[33ma19261d5[m feat: add ARM disassembler operation (#2156)
|
||||
@ -589,8 +589,7 @@ Password: 282760`;
|
||||
...[1, 3, 4, 5, 6, 7].map(version => it(`Analyze UUID v${version}`, () => {
|
||||
const uuid = chef.generateUUID("", { "version": `v${version}` }).toString();
|
||||
const result = chef.analyseUUID(uuid).toString();
|
||||
const expected = `UUID version: ${version}`;
|
||||
assert.strictEqual(result, expected);
|
||||
assert.ok(result.startsWith(`Version:\n${version}\n`), `Expected output to start with "Version:\\n${version}\\n", got: ${result}`);
|
||||
})),
|
||||
|
||||
it("Generate UUID using defaults", () => {
|
||||
@ -598,7 +597,7 @@ Password: 282760`;
|
||||
assert.ok(uuid);
|
||||
|
||||
const analysis = chef.analyseUUID(uuid).toString();
|
||||
assert.strictEqual(analysis, "UUID version: 4");
|
||||
assert.ok(analysis.startsWith("Version:\n4\n"), `Expected output to start with "Version:\\n4\\n", got: ${analysis}`);
|
||||
}),
|
||||
|
||||
it("Gzip, Gunzip", () => {
|
||||
|
||||
@ -16,6 +16,7 @@ import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
|
||||
import TestRegister from "../lib/TestRegister.mjs";
|
||||
import "./tests/A1Z26CipherDecode.mjs";
|
||||
import "./tests/AESKeyWrap.mjs";
|
||||
import "./tests/AnalyseUUID.mjs";
|
||||
import "./tests/AlternatingCaps.mjs";
|
||||
import "./tests/AvroToJSON.mjs";
|
||||
import "./tests/BaconCipher.mjs";
|
||||
|
||||
66
tests/operations/tests/AnalyseUUID.mjs
Normal file
66
tests/operations/tests/AnalyseUUID.mjs
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Analyse UUID tests
|
||||
*
|
||||
* @author ko80240 [csk.dev@proton.me]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
"name": "Analyse UUID: v1 UUID extracts timestamp, clock, and node",
|
||||
"input": "cefa1760-28ee-11f1-9f95-1fb76af3e239",
|
||||
"expectedOutput": "Version:\n1\n\nTimestamp:\n1774514156502\n\nTimestamp (ISO):\n2026-03-26T08:35:56.502Z\n\nNode:\n1F:B7:6A:F3:E2:39\n\nClock:\n8085\n\nUUID Integer:\n275119515460318071558429785403790975545",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Analyse UUID",
|
||||
"args": [true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Analyse UUID: v7 UUID extracts timestamp, randA, and randB",
|
||||
"input": "019d294a-af64-7728-9524-26da08f50708",
|
||||
"expectedOutput": "Version:\n7\n\nTimestamp:\n1774514253668\n\nTimestamp (ISO):\n2026-03-26T08:37:33.668Z\n\nRand A:\n1832\n\nRand B:\n952426DA08F50708\n\nUUID Integer:\n2145256098533991595556290452700595976",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Analyse UUID",
|
||||
"args": [true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Analyse UUID: v4 UUID should show no metadata - not possible",
|
||||
"input": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||
"expectedOutput": "Version:\n4\n\nNo metadata available. Only versions 1, 6, 7 are supported.\n\nUUID Integer:\n324969006592305634633390616021200786553",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Analyse UUID",
|
||||
"args": [true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Analyse UUID: if the 'Include Metadata' option is false it should return not metadata",
|
||||
"input": "cefa1760-28ee-11f1-9f95-1fb76af3e239",
|
||||
"expectedOutput": "Version:\n1\n\nUUID Integer:\n275119515460318071558429785403790975545",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Analyse UUID",
|
||||
"args": [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Analyse UUID: invalid UUID should return error message",
|
||||
"input": "not-a-uuid",
|
||||
"expectedOutput": "Invalid UUID",
|
||||
"recipeConfig": [
|
||||
{
|
||||
"op": "Analyse UUID",
|
||||
"args": [true]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user