@quantakrypto/qprobe 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -0
- package/THREAT-MODEL.md +83 -0
- package/dist/args.d.ts +19 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +94 -0
- package/dist/args.js.map +1 -0
- package/dist/attest.d.ts +31 -0
- package/dist/attest.d.ts.map +1 -0
- package/dist/attest.js +39 -0
- package/dist/attest.js.map +1 -0
- package/dist/classify.d.ts +19 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +105 -0
- package/dist/classify.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +118 -0
- package/dist/cli.js.map +1 -0
- package/dist/clienthello.d.ts +40 -0
- package/dist/clienthello.d.ts.map +1 -0
- package/dist/clienthello.js +182 -0
- package/dist/clienthello.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +22 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +51 -0
- package/dist/report.js.map +1 -0
- package/dist/smtp.d.ts +15 -0
- package/dist/smtp.d.ts.map +1 -0
- package/dist/smtp.js +103 -0
- package/dist/smtp.js.map +1 -0
- package/dist/ssh.d.ts +28 -0
- package/dist/ssh.d.ts.map +1 -0
- package/dist/ssh.js +118 -0
- package/dist/ssh.js.map +1 -0
- package/dist/target.d.ts +25 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +81 -0
- package/dist/target.js.map +1 -0
- package/dist/tls.d.ts +53 -0
- package/dist/tls.d.ts.map +1 -0
- package/dist/tls.js +109 -0
- package/dist/tls.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/x509.d.ts +28 -0
- package/dist/x509.d.ts.map +1 -0
- package/dist/x509.js +73 -0
- package/dist/x509.js.map +1 -0
- package/package.json +46 -0
package/dist/tls.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tls.js","sourceRoot":"","sources":["../src/tls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,OAAO,IAAI,UAAU,EAAkB,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GAErB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAkBnD,sFAAsF;AACtF,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAC7B,iFAAiF;IACjF,sEAAsE;IACtE,MAAM,GAAG,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,IAAI,SAAS;QAC3C,MAAM,EAAE,MAAM,EAAE,YAAY,IAAI,MAAM,EAAE,IAAI;QAC5C,QAAQ,EAAE,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACrD,OAAO,EAAE,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACpD,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QACzF,WAAW,EAAE,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACnE,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;QAC3C,aAAa,EAAE,GAAG,EAAE,MAAM;QAC1B,UAAU,EAAE,GAAG,EAAE,GAAG;KACrB,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,IAAY,EACZ,OAAoD,EAAE;IAEtD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,CAAgB,EAAE,EAAE;YAClC,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,CAAC;QACF,kEAAkE;QAClE,MAAM,IAAI,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,UAAU,CAAC;YACxB,IAAI;YACJ,IAAI;YACJ,UAAU;YACV,kBAAkB,EAAE,KAAK,EAAE,6CAA6C;YACxE,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE;YAC9B,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,IAAY,EACZ,OAAoD,EAAE;IAEtD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,CAAgB,EAAE,EAAE;YAClC,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACxB,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC/E,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAClC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YAClC,MAAM,EAAE,GAAgC,eAAe,CAAC,GAAG,CAAC,CAAC;YAC7D,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI;oBAAE,MAAM,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACvF,OAAO;YACT,CAAC;YACD,MAAM,CAAC;gBACL,cAAc,EAAE,EAAE,CAAC,aAAa,KAAK,oBAAoB;gBACzD,aAAa,EAAE,EAAE,CAAC,aAAa;gBAC/B,mBAAmB,EAAE,EAAE,CAAC,mBAAmB;gBAC3C,iBAAiB,EAAE,EAAE,CAAC,iBAAiB;aACxC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * TLS endpoint inspection. Two engines:\n * 1. `probeTlsNegotiated` — a normal `node:tls` handshake to read what the server\n * actually negotiates: TLS version, cipher suite, ephemeral key-exchange group\n * (the harvestable part), and the leaf certificate's public-key type/size.\n * 2. `probeHybridSupport` — a raw ClientHello (clienthello.ts) advertising\n * X25519MLKEM768, to detect PQC-HYBRID support that Node's bundled OpenSSL\n * cannot itself negotiate.\n *\n * `engine disposes`: we read the negotiated reality and disconnect; we never\n * modify the endpoint.\n */\nimport { connect as tlsConnect, type TLSSocket } from \"node:tls\";\nimport { connect as netConnect } from \"node:net\";\nimport {\n buildClientHello,\n readServerHello,\n GROUP_X25519MLKEM768,\n type ServerHelloInfo,\n} from \"./clienthello.js\";\nimport { certSignatureAlgorithm } from \"./x509.js\";\n\nexport interface TlsNegotiated {\n protocol?: string;\n cipher?: string;\n /** Ephemeral key-exchange group, e.g. \"X25519\", \"P-256\", \"DH\". */\n kexGroup?: string;\n kexType?: string;\n /** Leaf certificate public-key summary. */\n certKeyType?: string;\n certKeyBits?: number;\n certSubject?: string;\n /** How the leaf certificate is SIGNED (the CA's algorithm) — e.g. \"RSA\", \"ECDSA\". */\n certSigFamily?: string;\n certSigOid?: string;\n error?: string;\n}\n\n/** Read the negotiated parameters + leaf-cert posture from a connected TLS socket. */\nexport function extractNegotiated(socket: TLSSocket): TlsNegotiated {\n const cipher = socket.getCipher();\n const kex = socket.getEphemeralKeyInfo();\n const cert = socket.getPeerCertificate(false);\n const cn = cert?.subject?.CN;\n // node:tls exposes the public-key type but not how the leaf was SIGNED; read the\n // signature algorithm (the forgeable-at-Q-day part) from the raw DER.\n const sig = cert?.raw ? certSignatureAlgorithm(cert.raw) : undefined;\n return {\n protocol: socket.getProtocol() ?? undefined,\n cipher: cipher?.standardName ?? cipher?.name,\n kexGroup: kex && \"name\" in kex ? kex.name : undefined,\n kexType: kex && \"type\" in kex ? kex.type : undefined,\n certKeyType: cert?.asn1Curve ? `EC(${cert.asn1Curve})` : cert?.pubkey ? \"RSA\" : undefined,\n certKeyBits: typeof cert?.bits === \"number\" ? cert.bits : undefined,\n certSubject: Array.isArray(cn) ? cn[0] : cn,\n certSigFamily: sig?.family,\n certSigOid: sig?.oid,\n };\n}\n\n/** Perform a normal TLS handshake and read the negotiated parameters. */\nexport function probeTlsNegotiated(\n host: string,\n port: number,\n opts: { servername?: string; timeoutMs?: number } = {},\n): Promise<TlsNegotiated> {\n const timeoutMs = opts.timeoutMs ?? 8000;\n return new Promise((resolve) => {\n let done = false;\n const finish = (r: TlsNegotiated) => {\n if (done) return;\n done = true;\n socket.destroy();\n resolve(r);\n };\n // SNI must not be an IP literal (RFC 6066); omit it for bare IPs.\n const isIp = /^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host) || host.includes(\":\");\n const servername = opts.servername ?? (isIp ? undefined : host);\n const socket = tlsConnect({\n host,\n port,\n servername,\n rejectUnauthorized: false, // we inspect posture; we do not assert trust\n minVersion: \"TLSv1.2\",\n });\n socket.setTimeout(timeoutMs);\n socket.on(\"timeout\", () => finish({ error: \"timeout\" }));\n socket.on(\"error\", (e) => finish({ error: e.message }));\n socket.on(\"secureConnect\", () => {\n finish(extractNegotiated(socket));\n });\n });\n}\n\nexport interface HybridSupport {\n /** True only when the server SELECTED the hybrid group (proof of support). */\n hybridSelected: boolean;\n selectedGroup?: number;\n isHelloRetryRequest?: boolean;\n negotiatedVersion?: number;\n error?: string;\n}\n\n/**\n * Send a raw ClientHello advertising X25519MLKEM768 and read the group the server\n * selects. A server that supports+prefers the hybrid group answers with a\n * HelloRetryRequest selecting 0x11EC — positive proof of PQC-hybrid support.\n */\nexport function probeHybridSupport(\n host: string,\n port: number,\n opts: { servername?: string; timeoutMs?: number } = {},\n): Promise<HybridSupport> {\n const timeoutMs = opts.timeoutMs ?? 8000;\n return new Promise((resolve) => {\n let buf = Buffer.alloc(0);\n let done = false;\n const finish = (r: HybridSupport) => {\n if (done) return;\n done = true;\n socket.destroy();\n resolve(r);\n };\n const socket = netConnect({ host, port });\n socket.setTimeout(timeoutMs);\n socket.on(\"connect\", () => {\n socket.write(buildClientHello({ serverName: opts.servername ?? host }));\n });\n socket.on(\"timeout\", () => finish({ hybridSelected: false, error: \"timeout\" }));\n socket.on(\"error\", (e) => finish({ hybridSelected: false, error: e.message }));\n socket.on(\"data\", (chunk: Buffer) => {\n buf = Buffer.concat([buf, chunk]);\n const sh: ServerHelloInfo | undefined = readServerHello(buf);\n if (!sh) {\n if (buf.length > 64 * 1024) finish({ hybridSelected: false, error: \"no ServerHello\" });\n return;\n }\n finish({\n hybridSelected: sh.selectedGroup === GROUP_X25519MLKEM768,\n selectedGroup: sh.selectedGroup,\n isHelloRetryRequest: sh.isHelloRetryRequest,\n negotiatedVersion: sh.negotiatedVersion,\n });\n });\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC","sourcesContent":["/** qProbe version, surfaced in JSON output. Keep in sync with package.json. */\nexport const VERSION = \"0.1.0\";\n"]}
|
package/dist/x509.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, hand-rolled DER/X.509 parsing to extract a certificate's SIGNATURE
|
|
3
|
+
* algorithm — i.e. the algorithm the issuing CA used to sign it. `node:tls`
|
|
4
|
+
* exposes the leaf's public-key type but not how it was signed, and the signature
|
|
5
|
+
* is the forgeable-at-Q-day part, so we read it from the raw DER.
|
|
6
|
+
*
|
|
7
|
+
* Certificate ::= SEQUENCE {
|
|
8
|
+
* tbsCertificate SEQUENCE, -- skipped
|
|
9
|
+
* signatureAlgorithm AlgorithmIdentifier -- SEQUENCE { OID, params } -- read
|
|
10
|
+
* signatureValue BIT STRING }
|
|
11
|
+
*
|
|
12
|
+
* Pure byte work over Buffers; unit-tested with crafted DER. No crypto performed.
|
|
13
|
+
*/
|
|
14
|
+
import type { AlgorithmFamily } from "@quantakrypto/core";
|
|
15
|
+
/** Decode a DER OID value (the bytes inside the OID TLV) to dotted-decimal. */
|
|
16
|
+
export declare function decodeOid(bytes: Buffer): string;
|
|
17
|
+
/** Map a signature-algorithm OID to a classical family (or undefined). */
|
|
18
|
+
export declare function oidToSignatureFamily(oid: string): AlgorithmFamily | undefined;
|
|
19
|
+
export interface CertSignature {
|
|
20
|
+
oid: string;
|
|
21
|
+
family?: AlgorithmFamily;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract the signatureAlgorithm OID (and mapped family) from a DER certificate.
|
|
25
|
+
* Returns undefined if the DER can't be parsed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function certSignatureAlgorithm(der: Buffer): CertSignature | undefined;
|
|
28
|
+
//# sourceMappingURL=x509.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x509.d.ts","sourceRoot":"","sources":["../src/x509.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA4B1D,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAa/C;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAM7E;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAc7E"}
|
package/dist/x509.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Read one DER TLV header starting at `off`. Throws on truncation. */
|
|
2
|
+
function readTlv(buf, off) {
|
|
3
|
+
if (off + 2 > buf.length)
|
|
4
|
+
throw new RangeError("truncated DER");
|
|
5
|
+
const tag = buf[off];
|
|
6
|
+
let len = buf[off + 1];
|
|
7
|
+
let contentStart = off + 2;
|
|
8
|
+
if (len & 0x80) {
|
|
9
|
+
const n = len & 0x7f;
|
|
10
|
+
if (n === 0 || n > 4 || contentStart + n > buf.length)
|
|
11
|
+
throw new RangeError("bad DER length");
|
|
12
|
+
len = 0;
|
|
13
|
+
for (let i = 0; i < n; i++)
|
|
14
|
+
len = (len << 8) | buf[contentStart + i];
|
|
15
|
+
contentStart += n;
|
|
16
|
+
}
|
|
17
|
+
const contentEnd = contentStart + len;
|
|
18
|
+
if (contentEnd > buf.length)
|
|
19
|
+
throw new RangeError("DER length overruns buffer");
|
|
20
|
+
return { tag, contentStart, contentEnd, next: contentEnd };
|
|
21
|
+
}
|
|
22
|
+
/** Decode a DER OID value (the bytes inside the OID TLV) to dotted-decimal. */
|
|
23
|
+
export function decodeOid(bytes) {
|
|
24
|
+
if (bytes.length === 0)
|
|
25
|
+
return "";
|
|
26
|
+
const first = bytes[0];
|
|
27
|
+
const arcs = [Math.floor(first / 40), first % 40];
|
|
28
|
+
let value = 0;
|
|
29
|
+
for (let i = 1; i < bytes.length; i++) {
|
|
30
|
+
value = (value << 7) | (bytes[i] & 0x7f);
|
|
31
|
+
if ((bytes[i] & 0x80) === 0) {
|
|
32
|
+
arcs.push(value);
|
|
33
|
+
value = 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return arcs.join(".");
|
|
37
|
+
}
|
|
38
|
+
/** Map a signature-algorithm OID to a classical family (or undefined). */
|
|
39
|
+
export function oidToSignatureFamily(oid) {
|
|
40
|
+
if (oid.startsWith("1.2.840.113549.1.1"))
|
|
41
|
+
return "RSA"; // PKCS#1 *WithRSAEncryption + RSASSA-PSS
|
|
42
|
+
if (oid.startsWith("1.2.840.10045.4"))
|
|
43
|
+
return "ECDSA"; // ecdsa-with-SHA*
|
|
44
|
+
if (oid === "1.3.101.112" || oid === "1.3.101.113")
|
|
45
|
+
return "EdDSA"; // Ed25519 / Ed448
|
|
46
|
+
if (oid.startsWith("1.2.840.10040.4.3"))
|
|
47
|
+
return "DSA"; // dsa-with-sha1
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract the signatureAlgorithm OID (and mapped family) from a DER certificate.
|
|
52
|
+
* Returns undefined if the DER can't be parsed.
|
|
53
|
+
*/
|
|
54
|
+
export function certSignatureAlgorithm(der) {
|
|
55
|
+
try {
|
|
56
|
+
const cert = readTlv(der, 0); // outer Certificate SEQUENCE
|
|
57
|
+
if (cert.tag !== 0x30)
|
|
58
|
+
return undefined;
|
|
59
|
+
const tbs = readTlv(der, cert.contentStart); // tbsCertificate SEQUENCE (skip)
|
|
60
|
+
const sigAlg = readTlv(der, tbs.next); // signatureAlgorithm SEQUENCE
|
|
61
|
+
if (sigAlg.tag !== 0x30)
|
|
62
|
+
return undefined;
|
|
63
|
+
const oidTlv = readTlv(der, sigAlg.contentStart); // OID
|
|
64
|
+
if (oidTlv.tag !== 0x06)
|
|
65
|
+
return undefined;
|
|
66
|
+
const oid = decodeOid(der.subarray(oidTlv.contentStart, oidTlv.contentEnd));
|
|
67
|
+
return { oid, family: oidToSignatureFamily(oid) };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=x509.js.map
|
package/dist/x509.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x509.js","sourceRoot":"","sources":["../src/x509.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal, hand-rolled DER/X.509 parsing to extract a certificate's SIGNATURE\n * algorithm — i.e. the algorithm the issuing CA used to sign it. `node:tls`\n * exposes the leaf's public-key type but not how it was signed, and the signature\n * is the forgeable-at-Q-day part, so we read it from the raw DER.\n *\n * Certificate ::= SEQUENCE {\n * tbsCertificate SEQUENCE, -- skipped\n * signatureAlgorithm AlgorithmIdentifier -- SEQUENCE { OID, params } -- read\n * signatureValue BIT STRING }\n *\n * Pure byte work over Buffers; unit-tested with crafted DER. No crypto performed.\n */\nimport type { AlgorithmFamily } from \"@quantakrypto/core\";\n\n/** A DER TLV header: tag, content offset, content length. */\ninterface Tlv {\n tag: number;\n contentStart: number;\n contentEnd: number;\n next: number;\n}\n\n/** Read one DER TLV header starting at `off`. Throws on truncation. */\nfunction readTlv(buf: Buffer, off: number): Tlv {\n if (off + 2 > buf.length) throw new RangeError(\"truncated DER\");\n const tag = buf[off];\n let len = buf[off + 1];\n let contentStart = off + 2;\n if (len & 0x80) {\n const n = len & 0x7f;\n if (n === 0 || n > 4 || contentStart + n > buf.length) throw new RangeError(\"bad DER length\");\n len = 0;\n for (let i = 0; i < n; i++) len = (len << 8) | buf[contentStart + i];\n contentStart += n;\n }\n const contentEnd = contentStart + len;\n if (contentEnd > buf.length) throw new RangeError(\"DER length overruns buffer\");\n return { tag, contentStart, contentEnd, next: contentEnd };\n}\n\n/** Decode a DER OID value (the bytes inside the OID TLV) to dotted-decimal. */\nexport function decodeOid(bytes: Buffer): string {\n if (bytes.length === 0) return \"\";\n const first = bytes[0];\n const arcs = [Math.floor(first / 40), first % 40];\n let value = 0;\n for (let i = 1; i < bytes.length; i++) {\n value = (value << 7) | (bytes[i] & 0x7f);\n if ((bytes[i] & 0x80) === 0) {\n arcs.push(value);\n value = 0;\n }\n }\n return arcs.join(\".\");\n}\n\n/** Map a signature-algorithm OID to a classical family (or undefined). */\nexport function oidToSignatureFamily(oid: string): AlgorithmFamily | undefined {\n if (oid.startsWith(\"1.2.840.113549.1.1\")) return \"RSA\"; // PKCS#1 *WithRSAEncryption + RSASSA-PSS\n if (oid.startsWith(\"1.2.840.10045.4\")) return \"ECDSA\"; // ecdsa-with-SHA*\n if (oid === \"1.3.101.112\" || oid === \"1.3.101.113\") return \"EdDSA\"; // Ed25519 / Ed448\n if (oid.startsWith(\"1.2.840.10040.4.3\")) return \"DSA\"; // dsa-with-sha1\n return undefined;\n}\n\nexport interface CertSignature {\n oid: string;\n family?: AlgorithmFamily;\n}\n\n/**\n * Extract the signatureAlgorithm OID (and mapped family) from a DER certificate.\n * Returns undefined if the DER can't be parsed.\n */\nexport function certSignatureAlgorithm(der: Buffer): CertSignature | undefined {\n try {\n const cert = readTlv(der, 0); // outer Certificate SEQUENCE\n if (cert.tag !== 0x30) return undefined;\n const tbs = readTlv(der, cert.contentStart); // tbsCertificate SEQUENCE (skip)\n const sigAlg = readTlv(der, tbs.next); // signatureAlgorithm SEQUENCE\n if (sigAlg.tag !== 0x30) return undefined;\n const oidTlv = readTlv(der, sigAlg.contentStart); // OID\n if (oidTlv.tag !== 0x06) return undefined;\n const oid = decodeOid(der.subarray(oidTlv.contentStart, oidTlv.contentEnd));\n return { oid, family: oidToSignatureFamily(oid) };\n } catch {\n return undefined;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quantakrypto/qprobe",
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "qProbe — actively inspect live TLS/SSH endpoints you OWN for post-quantum readiness (hybrid KEX, classical certs). Gated behind an ownership attestation. Zero runtime dependencies.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Dandelion Labs <hello@dandelionlabs.io> (https://dandelionlabs.io)",
|
|
7
|
+
"homepage": "https://github.com/quantakrypto/pqc-tools/tree/main/packages/qprobe#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/quantakrypto/pqc-tools.git",
|
|
11
|
+
"directory": "packages/qprobe"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/quantakrypto/pqc-tools/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"bin": {
|
|
18
|
+
"qprobe": "./dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"THREAT-MODEL.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@quantakrypto/core": "0.4.4"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -b",
|
|
44
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
45
|
+
}
|
|
46
|
+
}
|