@motebit/verify 0.7.0 → 1.1.0
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/LICENSE +198 -18
- package/NOTICE +19 -0
- package/README.md +92 -69
- package/dist/adapters.d.ts +132 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +74 -0
- package/dist/adapters.js.map +1 -0
- package/dist/cli.d.ts +45 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +317 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +25 -230
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -1513
- package/dist/index.js.map +1 -1
- package/package.json +38 -16
package/dist/adapters.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { androidKeystoreVerifier } from "@motebit/crypto-android-keystore";
|
|
2
|
+
import { deviceCheckVerifier, APPLE_APPATTEST_ROOT_PEM } from "@motebit/crypto-appattest";
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- consumed for one minor deprecation cycle so already-minted Play Integrity claims continue to verify; removed at @motebit/crypto-play-integrity@2.0.0.
|
|
4
|
+
import { playIntegrityVerifier } from "@motebit/crypto-play-integrity";
|
|
5
|
+
import { tpmVerifier } from "@motebit/crypto-tpm";
|
|
6
|
+
import { webauthnVerifier, DEFAULT_FIDO_ROOTS } from "@motebit/crypto-webauthn";
|
|
7
|
+
/** Motebit's canonical iOS / Android app identifier. */
|
|
8
|
+
const DEFAULT_BUNDLE_ID = "com.motebit.mobile";
|
|
9
|
+
/** Motebit's canonical Relying Party ID for WebAuthn credentials. */
|
|
10
|
+
const DEFAULT_WEBAUTHN_RP_ID = "motebit.com";
|
|
11
|
+
/**
|
|
12
|
+
* Build the full `HardwareAttestationVerifiers` object covering every
|
|
13
|
+
* canonical platform adapter. Pass the result to `verifyFile`:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { verifyFile } from "@motebit/verifier";
|
|
17
|
+
* import { buildHardwareVerifiers } from "@motebit/verify";
|
|
18
|
+
*
|
|
19
|
+
* const result = await verifyFile("cred.json", {
|
|
20
|
+
* hardwareAttestation: buildHardwareVerifiers({
|
|
21
|
+
* androidKeystoreExpectedAttestationApplicationId: appIdBytes,
|
|
22
|
+
* }),
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Pure function: every dependency is captured at factory time and the
|
|
27
|
+
* returned verifiers are idempotent across calls. The Android Keystore
|
|
28
|
+
* arm is wired only when `androidKeystoreExpectedAttestationApplicationId`
|
|
29
|
+
* is supplied — there is no canonical default for the leaf-cert
|
|
30
|
+
* package binding, by design.
|
|
31
|
+
*/
|
|
32
|
+
export function buildHardwareVerifiers(config) {
|
|
33
|
+
const appAttestBundleId = config?.appAttestBundleId ?? DEFAULT_BUNDLE_ID;
|
|
34
|
+
const playIntegrityPackageName = config?.playIntegrityPackageName ?? DEFAULT_BUNDLE_ID;
|
|
35
|
+
const webauthnRpId = config?.webauthnRpId ?? DEFAULT_WEBAUTHN_RP_ID;
|
|
36
|
+
const verifiers = {
|
|
37
|
+
deviceCheck: deviceCheckVerifier({
|
|
38
|
+
expectedBundleId: appAttestBundleId,
|
|
39
|
+
rootPem: config?.appAttestRootPem ?? APPLE_APPATTEST_ROOT_PEM,
|
|
40
|
+
}),
|
|
41
|
+
tpm: tpmVerifier({
|
|
42
|
+
...(config?.tpmRootPems !== undefined ? { rootPems: config.tpmRootPems } : {}),
|
|
43
|
+
}),
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- one-minor-cycle backward compat for already-minted Play Integrity credentials; removed at @motebit/crypto-play-integrity@2.0.0.
|
|
45
|
+
playIntegrity: playIntegrityVerifier({
|
|
46
|
+
expectedPackageName: playIntegrityPackageName,
|
|
47
|
+
...(config?.playIntegrityPinnedJwks !== undefined
|
|
48
|
+
? { pinnedJwks: config.playIntegrityPinnedJwks }
|
|
49
|
+
: {}),
|
|
50
|
+
...(config?.playIntegrityRequiredDeviceIntegrity !== undefined
|
|
51
|
+
? { requiredDeviceIntegrity: config.playIntegrityRequiredDeviceIntegrity }
|
|
52
|
+
: {}),
|
|
53
|
+
}),
|
|
54
|
+
webauthn: webauthnVerifier({
|
|
55
|
+
expectedRpId: webauthnRpId,
|
|
56
|
+
rootPems: config?.webauthnRootPems ?? DEFAULT_FIDO_ROOTS,
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
// Android Keystore is wired only when the operator has supplied the
|
|
60
|
+
// expected `attestationApplicationId`. Leaving it unwired makes the
|
|
61
|
+
// canonical dispatcher report "verifier not wired" with a clear
|
|
62
|
+
// message — preferable to passing a placeholder that would
|
|
63
|
+
// false-reject every real claim.
|
|
64
|
+
if (config?.androidKeystoreExpectedAttestationApplicationId !== undefined) {
|
|
65
|
+
verifiers.androidKeystore = androidKeystoreVerifier({
|
|
66
|
+
expectedAttestationApplicationId: config.androidKeystoreExpectedAttestationApplicationId,
|
|
67
|
+
...(config.androidKeystoreRootPems !== undefined
|
|
68
|
+
? { rootPems: config.androidKeystoreRootPems }
|
|
69
|
+
: {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return verifiers;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,qNAAqN;AACrN,OAAO,EAAE,qBAAqB,EAAmB,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAyEhF,wDAAwD;AACxD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAC/C,qEAAqE;AACrE,MAAM,sBAAsB,GAAG,aAAa,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAqC;IAErC,MAAM,iBAAiB,GAAG,MAAM,EAAE,iBAAiB,IAAI,iBAAiB,CAAC;IACzE,MAAM,wBAAwB,GAAG,MAAM,EAAE,wBAAwB,IAAI,iBAAiB,CAAC;IACvF,MAAM,YAAY,GAAG,MAAM,EAAE,YAAY,IAAI,sBAAsB,CAAC;IAEpE,MAAM,SAAS,GAA0C;QACvD,WAAW,EAAE,mBAAmB,CAAC;YAC/B,gBAAgB,EAAE,iBAAiB;YACnC,OAAO,EAAE,MAAM,EAAE,gBAAgB,IAAI,wBAAwB;SAC9D,CAAC;QACF,GAAG,EAAE,WAAW,CAAC;YACf,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/E,CAAC;QACF,+LAA+L;QAC/L,aAAa,EAAE,qBAAqB,CAAC;YACnC,mBAAmB,EAAE,wBAAwB;YAC7C,GAAG,CAAC,MAAM,EAAE,uBAAuB,KAAK,SAAS;gBAC/C,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,uBAAuB,EAAE;gBAChD,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,EAAE,oCAAoC,KAAK,SAAS;gBAC5D,CAAC,CAAC,EAAE,uBAAuB,EAAE,MAAM,CAAC,oCAAoC,EAAE;gBAC1E,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;QACF,QAAQ,EAAE,gBAAgB,CAAC;YACzB,YAAY,EAAE,YAAY;YAC1B,QAAQ,EAAE,MAAM,EAAE,gBAAgB,IAAI,kBAAkB;SACzD,CAAC;KACH,CAAC;IAEF,oEAAoE;IACpE,oEAAoE;IACpE,gEAAgE;IAChE,2DAA2D;IAC3D,iCAAiC;IACjC,IAAI,MAAM,EAAE,+CAA+C,KAAK,SAAS,EAAE,CAAC;QAC1E,SAAS,CAAC,eAAe,GAAG,uBAAuB,CAAC;YAClD,gCAAgC,EAAE,MAAM,CAAC,+CAA+C;YACxF,GAAG,CAAC,MAAM,CAAC,uBAAuB,KAAK,SAAS;gBAC9C,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,uBAAuB,EAAE;gBAC9C,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `motebit-verify` CLI — the canonical motebit artifact verifier.
|
|
4
|
+
*
|
|
5
|
+
* Verifies identity files, execution receipts, credentials, and
|
|
6
|
+
* presentations against their embedded signatures. When a credential
|
|
7
|
+
* carries a `hardware_attestation` claim for `device_check` / `tpm` /
|
|
8
|
+
* `android_keystore` / `webauthn` (plus the deprecated `play_integrity`
|
|
9
|
+
* for backward compat with already-minted credentials), the bundled
|
|
10
|
+
* platform adapters verify the chain, extension, package binding, and
|
|
11
|
+
* identity binding end-to-end.
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* motebit-verify <file> # auto-detect, print human
|
|
15
|
+
* motebit-verify <file> --json # structured output
|
|
16
|
+
* motebit-verify <file> --expect credential
|
|
17
|
+
* motebit-verify <file> --clock-skew 30
|
|
18
|
+
*
|
|
19
|
+
* # Platform-specific overrides (all optional; defaults match
|
|
20
|
+
* # motebit's canonical identifiers).
|
|
21
|
+
* motebit-verify <file> \
|
|
22
|
+
* --bundle-id com.example.app \
|
|
23
|
+
* --android-attestation-application-id ./app-id.bin \
|
|
24
|
+
* --rp-id example.com
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Exit codes:
|
|
28
|
+
* 0 artifact verified (including any hardware-attestation channel)
|
|
29
|
+
* 1 artifact detected but signature / hardware-channel invalid
|
|
30
|
+
* 2 usage / I/O error
|
|
31
|
+
*
|
|
32
|
+
* Network-free by design. Every adapter pins its own trust anchor
|
|
33
|
+
* (Apple App Attest Root CA, FIDO roots, TPM vendor roots); Play
|
|
34
|
+
* Integrity's JWKS is fail-closed by default until an operator lands
|
|
35
|
+
* real bytes (see `@motebit/crypto-play-integrity`'s CLAUDE.md).
|
|
36
|
+
*
|
|
37
|
+
* Three-package lineage — mirrors how tools like `git` / `libgit2` or
|
|
38
|
+
* `cargo` / `tokio` separate the verb-tool from the library layer:
|
|
39
|
+
*
|
|
40
|
+
* @motebit/verify — this CLI (Apache-2.0, bundles all 4 adapters)
|
|
41
|
+
* @motebit/verifier — Apache-2.0 library (file I/O, human formatting)
|
|
42
|
+
* @motebit/crypto — Apache-2.0 primitives (verify, sign, suite dispatch)
|
|
43
|
+
*/
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `motebit-verify` CLI — the canonical motebit artifact verifier.
|
|
4
|
+
*
|
|
5
|
+
* Verifies identity files, execution receipts, credentials, and
|
|
6
|
+
* presentations against their embedded signatures. When a credential
|
|
7
|
+
* carries a `hardware_attestation` claim for `device_check` / `tpm` /
|
|
8
|
+
* `android_keystore` / `webauthn` (plus the deprecated `play_integrity`
|
|
9
|
+
* for backward compat with already-minted credentials), the bundled
|
|
10
|
+
* platform adapters verify the chain, extension, package binding, and
|
|
11
|
+
* identity binding end-to-end.
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* motebit-verify <file> # auto-detect, print human
|
|
15
|
+
* motebit-verify <file> --json # structured output
|
|
16
|
+
* motebit-verify <file> --expect credential
|
|
17
|
+
* motebit-verify <file> --clock-skew 30
|
|
18
|
+
*
|
|
19
|
+
* # Platform-specific overrides (all optional; defaults match
|
|
20
|
+
* # motebit's canonical identifiers).
|
|
21
|
+
* motebit-verify <file> \
|
|
22
|
+
* --bundle-id com.example.app \
|
|
23
|
+
* --android-attestation-application-id ./app-id.bin \
|
|
24
|
+
* --rp-id example.com
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Exit codes:
|
|
28
|
+
* 0 artifact verified (including any hardware-attestation channel)
|
|
29
|
+
* 1 artifact detected but signature / hardware-channel invalid
|
|
30
|
+
* 2 usage / I/O error
|
|
31
|
+
*
|
|
32
|
+
* Network-free by design. Every adapter pins its own trust anchor
|
|
33
|
+
* (Apple App Attest Root CA, FIDO roots, TPM vendor roots); Play
|
|
34
|
+
* Integrity's JWKS is fail-closed by default until an operator lands
|
|
35
|
+
* real bytes (see `@motebit/crypto-play-integrity`'s CLAUDE.md).
|
|
36
|
+
*
|
|
37
|
+
* Three-package lineage — mirrors how tools like `git` / `libgit2` or
|
|
38
|
+
* `cargo` / `tokio` separate the verb-tool from the library layer:
|
|
39
|
+
*
|
|
40
|
+
* @motebit/verify — this CLI (Apache-2.0, bundles all 4 adapters)
|
|
41
|
+
* @motebit/verifier — Apache-2.0 library (file I/O, human formatting)
|
|
42
|
+
* @motebit/crypto — Apache-2.0 primitives (verify, sign, suite dispatch)
|
|
43
|
+
*/
|
|
44
|
+
import { readFileSync } from "node:fs";
|
|
45
|
+
import { dirname, join } from "node:path";
|
|
46
|
+
import { fileURLToPath } from "node:url";
|
|
47
|
+
import { formatHuman, verifyFile } from "@motebit/verifier";
|
|
48
|
+
import { buildHardwareVerifiers } from "./adapters.js";
|
|
49
|
+
const EXPECT_VALUES = [
|
|
50
|
+
"identity",
|
|
51
|
+
"receipt",
|
|
52
|
+
"credential",
|
|
53
|
+
"presentation",
|
|
54
|
+
];
|
|
55
|
+
function parseArgs(argv) {
|
|
56
|
+
let file;
|
|
57
|
+
let json = false;
|
|
58
|
+
let expectedType;
|
|
59
|
+
let clockSkewSeconds;
|
|
60
|
+
let bundleId;
|
|
61
|
+
let androidPackage;
|
|
62
|
+
let androidAttestationApplicationIdPath;
|
|
63
|
+
let rpId;
|
|
64
|
+
let help = false;
|
|
65
|
+
let version = false;
|
|
66
|
+
let i = 0;
|
|
67
|
+
while (i < argv.length) {
|
|
68
|
+
const arg = argv[i];
|
|
69
|
+
switch (arg) {
|
|
70
|
+
case "-h":
|
|
71
|
+
case "--help":
|
|
72
|
+
help = true;
|
|
73
|
+
i++;
|
|
74
|
+
break;
|
|
75
|
+
case "-V":
|
|
76
|
+
case "--version":
|
|
77
|
+
version = true;
|
|
78
|
+
i++;
|
|
79
|
+
break;
|
|
80
|
+
case "--json":
|
|
81
|
+
json = true;
|
|
82
|
+
i++;
|
|
83
|
+
break;
|
|
84
|
+
case "--expect":
|
|
85
|
+
case "--expected-type": {
|
|
86
|
+
const value = argv[i + 1];
|
|
87
|
+
if (value === undefined)
|
|
88
|
+
return usage(`${arg} requires a value`);
|
|
89
|
+
if (!EXPECT_VALUES.includes(value)) {
|
|
90
|
+
return usage(`unknown --expect value "${value}" (valid: ${EXPECT_VALUES.join(", ")})`);
|
|
91
|
+
}
|
|
92
|
+
expectedType = value;
|
|
93
|
+
i += 2;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "--clock-skew": {
|
|
97
|
+
const value = argv[i + 1];
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
return usage("--clock-skew requires an integer seconds value");
|
|
100
|
+
const n = Number.parseInt(value, 10);
|
|
101
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
102
|
+
return usage(`--clock-skew must be a non-negative integer (got "${value}")`);
|
|
103
|
+
}
|
|
104
|
+
clockSkewSeconds = n;
|
|
105
|
+
i += 2;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "--bundle-id": {
|
|
109
|
+
const value = argv[i + 1];
|
|
110
|
+
if (value === undefined)
|
|
111
|
+
return usage("--bundle-id requires a value");
|
|
112
|
+
bundleId = value;
|
|
113
|
+
i += 2;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "--android-package": {
|
|
117
|
+
const value = argv[i + 1];
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
return usage("--android-package requires a value");
|
|
120
|
+
androidPackage = value;
|
|
121
|
+
i += 2;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case "--android-attestation-application-id": {
|
|
125
|
+
// Path to a binary file containing the raw bytes of the leaf
|
|
126
|
+
// cert's `attestationApplicationId` extension value. Operators
|
|
127
|
+
// capture this once at build time (deterministic from the
|
|
128
|
+
// package name + signing-cert SHA-256) and pin the result;
|
|
129
|
+
// the verifier byte-compares against the leaf's KeyDescription
|
|
130
|
+
// extension. File-only intentionally — typical AAID is 50-200
|
|
131
|
+
// bytes, unwieldy on the command line as hex.
|
|
132
|
+
const value = argv[i + 1];
|
|
133
|
+
if (value === undefined) {
|
|
134
|
+
return usage("--android-attestation-application-id requires a path to a binary file");
|
|
135
|
+
}
|
|
136
|
+
androidAttestationApplicationIdPath = value;
|
|
137
|
+
i += 2;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "--rp-id": {
|
|
141
|
+
const value = argv[i + 1];
|
|
142
|
+
if (value === undefined)
|
|
143
|
+
return usage("--rp-id requires a value");
|
|
144
|
+
rpId = value;
|
|
145
|
+
i += 2;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
default:
|
|
149
|
+
if (arg.startsWith("-"))
|
|
150
|
+
return usage(`unknown flag: ${arg}`);
|
|
151
|
+
if (file !== undefined) {
|
|
152
|
+
return usage(`expected exactly one file argument, got a second: "${arg}" (after "${file}")`);
|
|
153
|
+
}
|
|
154
|
+
file = arg;
|
|
155
|
+
i++;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (help)
|
|
160
|
+
return { mode: "help", json };
|
|
161
|
+
if (version)
|
|
162
|
+
return { mode: "version", json };
|
|
163
|
+
if (file === undefined)
|
|
164
|
+
return usage("missing file argument");
|
|
165
|
+
return {
|
|
166
|
+
mode: "verify",
|
|
167
|
+
file,
|
|
168
|
+
json,
|
|
169
|
+
...(expectedType !== undefined && { expectedType }),
|
|
170
|
+
...(clockSkewSeconds !== undefined && { clockSkewSeconds }),
|
|
171
|
+
...(bundleId !== undefined && { bundleId }),
|
|
172
|
+
...(androidPackage !== undefined && { androidPackage }),
|
|
173
|
+
...(androidAttestationApplicationIdPath !== undefined && {
|
|
174
|
+
androidAttestationApplicationIdPath,
|
|
175
|
+
}),
|
|
176
|
+
...(rpId !== undefined && { rpId }),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function usage(message) {
|
|
180
|
+
return { mode: "help", json: false, usageError: message };
|
|
181
|
+
}
|
|
182
|
+
function renderHelp() {
|
|
183
|
+
return [
|
|
184
|
+
"motebit-verify — hardware-attestation-aware verifier for Motebit credentials",
|
|
185
|
+
"",
|
|
186
|
+
"USAGE",
|
|
187
|
+
" motebit-verify <file> [options]",
|
|
188
|
+
"",
|
|
189
|
+
"OPTIONS",
|
|
190
|
+
" --json Print structured JSON instead of human-readable.",
|
|
191
|
+
" --expect <type> Require the artifact to be of the named type.",
|
|
192
|
+
" --clock-skew <seconds> Allow N seconds of clock skew.",
|
|
193
|
+
" --bundle-id <id> Override the expected iOS bundle ID for App Attest",
|
|
194
|
+
" (default: com.motebit.mobile).",
|
|
195
|
+
" --android-package <name> Override the expected Android package name for",
|
|
196
|
+
" the deprecated Play Integrity adapter",
|
|
197
|
+
" (default: com.motebit.mobile).",
|
|
198
|
+
" --android-attestation-application-id <path>",
|
|
199
|
+
" Path to a binary file containing the raw bytes",
|
|
200
|
+
" of the leaf cert's `attestationApplicationId`",
|
|
201
|
+
" extension value. REQUIRED to verify any",
|
|
202
|
+
" `android_keystore` credential — without it,",
|
|
203
|
+
" the Android Keystore arm is not wired and",
|
|
204
|
+
" the dispatcher reports 'verifier not wired'.",
|
|
205
|
+
" Capture once at build time from the registered",
|
|
206
|
+
" Android package + signing-cert hash; commit",
|
|
207
|
+
" alongside other pinned config.",
|
|
208
|
+
" --rp-id <id> Override the expected WebAuthn Relying Party ID",
|
|
209
|
+
" (default: motebit.com).",
|
|
210
|
+
" -h, --help Show this help.",
|
|
211
|
+
" -V, --version Print version.",
|
|
212
|
+
"",
|
|
213
|
+
"EXIT CODES",
|
|
214
|
+
" 0 Artifact verified (including hardware-attestation channel).",
|
|
215
|
+
" 1 Artifact invalid (signature, expiry, hardware-channel chain / nonce / bundle).",
|
|
216
|
+
" 2 Usage or I/O error.",
|
|
217
|
+
"",
|
|
218
|
+
"PLATFORMS WIRED (canonical)",
|
|
219
|
+
" device_check Apple App Attest (pinned Apple root)",
|
|
220
|
+
" tpm TPM 2.0 (pinned Infineon / Nuvoton / STMicro / Intel PTT roots)",
|
|
221
|
+
" android_keystore Android Hardware-Backed Keystore Attestation",
|
|
222
|
+
" (pinned Google attestation roots; requires",
|
|
223
|
+
" --android-attestation-application-id)",
|
|
224
|
+
" webauthn WebAuthn packed attestation (pinned Apple / Yubico / Microsoft)",
|
|
225
|
+
"",
|
|
226
|
+
"PLATFORMS WIRED (deprecated, removed at @motebit/crypto-play-integrity@2.0.0)",
|
|
227
|
+
" play_integrity Google Play Integrity (operator-supplied JWKS;",
|
|
228
|
+
" no global Google JWKS exists by Google's design.",
|
|
229
|
+
" See docs/doctrine/hardware-attestation.md § 'Three",
|
|
230
|
+
" architectural categories' for the structural reason.)",
|
|
231
|
+
].join("\n");
|
|
232
|
+
}
|
|
233
|
+
let cachedVersion;
|
|
234
|
+
function getPackageVersion() {
|
|
235
|
+
if (cachedVersion !== undefined)
|
|
236
|
+
return cachedVersion;
|
|
237
|
+
try {
|
|
238
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
239
|
+
const pkgPath = join(here, "..", "package.json");
|
|
240
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
241
|
+
cachedVersion = pkg.version ?? "0.0.0";
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
cachedVersion = "0.0.0";
|
|
245
|
+
}
|
|
246
|
+
return cachedVersion;
|
|
247
|
+
}
|
|
248
|
+
async function main() {
|
|
249
|
+
const args = parseArgs(process.argv.slice(2));
|
|
250
|
+
if (args.mode === "version") {
|
|
251
|
+
process.stdout.write(`${getPackageVersion()}\n`);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
if (args.mode === "help") {
|
|
255
|
+
const help = renderHelp();
|
|
256
|
+
if (args.usageError !== undefined) {
|
|
257
|
+
process.stderr.write(`motebit-verify: ${args.usageError}\n\n${help}\n`);
|
|
258
|
+
return 2;
|
|
259
|
+
}
|
|
260
|
+
process.stdout.write(`${help}\n`);
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
if (args.file === undefined) {
|
|
264
|
+
process.stderr.write(`motebit-verify: missing file argument\n\n${renderHelp()}\n`);
|
|
265
|
+
return 2;
|
|
266
|
+
}
|
|
267
|
+
let androidKeystoreExpectedAttestationApplicationId;
|
|
268
|
+
if (args.androidAttestationApplicationIdPath !== undefined) {
|
|
269
|
+
try {
|
|
270
|
+
const bytes = readFileSync(args.androidAttestationApplicationIdPath);
|
|
271
|
+
androidKeystoreExpectedAttestationApplicationId = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
275
|
+
process.stderr.write(`motebit-verify: cannot read --android-attestation-application-id at ${args.androidAttestationApplicationIdPath}: ${msg}\n`);
|
|
276
|
+
return 2;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const hardwareAttestation = buildHardwareVerifiers({
|
|
280
|
+
...(args.bundleId !== undefined && { appAttestBundleId: args.bundleId }),
|
|
281
|
+
...(args.androidPackage !== undefined && { playIntegrityPackageName: args.androidPackage }),
|
|
282
|
+
...(androidKeystoreExpectedAttestationApplicationId !== undefined && {
|
|
283
|
+
androidKeystoreExpectedAttestationApplicationId,
|
|
284
|
+
}),
|
|
285
|
+
...(args.rpId !== undefined && { webauthnRpId: args.rpId }),
|
|
286
|
+
});
|
|
287
|
+
let result;
|
|
288
|
+
try {
|
|
289
|
+
result = await verifyFile(args.file, {
|
|
290
|
+
...(args.expectedType !== undefined && { expectedType: args.expectedType }),
|
|
291
|
+
...(args.clockSkewSeconds !== undefined && { clockSkewSeconds: args.clockSkewSeconds }),
|
|
292
|
+
hardwareAttestation,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
297
|
+
process.stderr.write(`motebit-verify: cannot read ${args.file}: ${msg}\n`);
|
|
298
|
+
return 2;
|
|
299
|
+
}
|
|
300
|
+
if (args.json) {
|
|
301
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
process.stdout.write(`${formatHuman(result)}\n`);
|
|
305
|
+
}
|
|
306
|
+
return result.valid ? 0 : 1;
|
|
307
|
+
}
|
|
308
|
+
main()
|
|
309
|
+
.then((code) => {
|
|
310
|
+
process.exit(code);
|
|
311
|
+
})
|
|
312
|
+
.catch((err) => {
|
|
313
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
314
|
+
process.stderr.write(`motebit-verify: ${msg}\n`);
|
|
315
|
+
process.exit(2);
|
|
316
|
+
});
|
|
317
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|