@necessarysdk/react-native 0.0.0-stage → 0.1.5

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +2 -0
  4. package/NecessaryReactNative.podspec +23 -0
  5. package/README.md +429 -2
  6. package/android/build.gradle +83 -0
  7. package/android/src/main/AndroidManifest.xml +41 -0
  8. package/android/src/main/cpp/CMakeLists.txt +7 -0
  9. package/android/src/main/cpp/code_integrity.cpp +484 -0
  10. package/android/src/main/java/com/necessarysdk/reactnative/Causes.kt +49 -0
  11. package/android/src/main/java/com/necessarysdk/reactnative/InstallationKeyManager.kt +388 -0
  12. package/android/src/main/java/com/necessarysdk/reactnative/IntegrityProbeManager.kt +1152 -0
  13. package/android/src/main/java/com/necessarysdk/reactnative/KeyPresence.kt +62 -0
  14. package/android/src/main/java/com/necessarysdk/reactnative/NecessaryModules.kt +94 -0
  15. package/android/src/main/java/com/necessarysdk/reactnative/NecessaryNative.kt +323 -0
  16. package/android/src/main/java/com/necessarysdk/reactnative/NecessaryPackage.kt +38 -0
  17. package/android/src/main/java/com/necessarysdk/reactnative/ProbeReads.kt +314 -0
  18. package/android/src/main/java/com/necessarysdk/reactnative/ScreenGuard.kt +56 -0
  19. package/android/src/main/java/com/necessarysdk/reactnative/SecureStore.kt +127 -0
  20. package/android/src/main/java/com/necessarysdk/reactnative/StepUpKeyEntry.kt +57 -0
  21. package/android/src/main/java/com/necessarysdk/reactnative/StepUpKeyManager.kt +762 -0
  22. package/contract/report-1/android-8-ios-5/RULES.md +149 -0
  23. package/contract/report-1/android-8-ios-5/report.schema.json +2753 -0
  24. package/contract/report-1/android-8-ios-5/samples/android-8-complete.json +205 -0
  25. package/contract/report-1/android-8-ios-5/samples/android-8-incomplete.json +205 -0
  26. package/contract/report-1/android-8-ios-5/samples/android-8-invalid.json +205 -0
  27. package/contract/report-1/android-8-ios-5/samples/android-8-location-one-fix.json +228 -0
  28. package/contract/report-1/android-8-ios-5/samples/android-8-location-two-fixes.json +228 -0
  29. package/contract/report-1/android-8-ios-5/samples/android-8-refused.json +208 -0
  30. package/contract/report-1/android-8-ios-5/samples/ios-5-complete.json +106 -0
  31. package/contract/report-1/android-8-ios-5/samples/ios-5-incomplete.json +105 -0
  32. package/contract/report-1/android-8-ios-5/samples/ios-5-invalid.json +107 -0
  33. package/contract/report-1/android-8-ios-5/samples/ios-5-partial.json +110 -0
  34. package/contract/report-1/android-8-ios-5/samples/ios-5-refused.json +109 -0
  35. package/doc/integration_guide.md +771 -0
  36. package/doc/measurements.md +774 -0
  37. package/ios/InstallationKeyManager.swift +694 -0
  38. package/ios/IntegrityProbeManager.swift +960 -0
  39. package/ios/NecessaryModules.h +34 -0
  40. package/ios/NecessaryModules.mm +208 -0
  41. package/ios/NecessaryNative.swift +327 -0
  42. package/ios/PrivacyInfo.xcprivacy +14 -0
  43. package/ios/ProbeReads.swift +353 -0
  44. package/lib/client.d.ts +105 -0
  45. package/lib/client.js +176 -0
  46. package/lib/collector.d.ts +18 -0
  47. package/lib/collector.js +49 -0
  48. package/lib/deviceTrust.d.ts +253 -0
  49. package/lib/deviceTrust.js +690 -0
  50. package/lib/errors.d.ts +43 -0
  51. package/lib/errors.js +56 -0
  52. package/lib/index.d.ts +18 -0
  53. package/lib/index.js +34 -0
  54. package/lib/installationKey.d.ts +62 -0
  55. package/lib/installationKey.js +185 -0
  56. package/lib/installationStore.d.ts +49 -0
  57. package/lib/installationStore.js +127 -0
  58. package/lib/native/NativeNecessaryInstallationKey.d.ts +10 -0
  59. package/lib/native/NativeNecessaryInstallationKey.js +4 -0
  60. package/lib/native/NativeNecessaryIntegrity.d.ts +8 -0
  61. package/lib/native/NativeNecessaryIntegrity.js +4 -0
  62. package/lib/native/NativeNecessarySecureScreen.d.ts +7 -0
  63. package/lib/native/NativeNecessarySecureScreen.js +4 -0
  64. package/lib/native/NativeNecessaryStepUpKey.d.ts +9 -0
  65. package/lib/native/NativeNecessaryStepUpKey.js +4 -0
  66. package/lib/native/NativeNecessaryStorage.d.ts +14 -0
  67. package/lib/native/NativeNecessaryStorage.js +4 -0
  68. package/lib/protocol.d.ts +24 -0
  69. package/lib/protocol.js +50 -0
  70. package/lib/reinstallHint.d.ts +23 -0
  71. package/lib/reinstallHint.js +65 -0
  72. package/lib/report.d.ts +62 -0
  73. package/lib/report.js +140 -0
  74. package/lib/requestProof.d.ts +116 -0
  75. package/lib/requestProof.js +220 -0
  76. package/lib/secureScreen.d.ts +15 -0
  77. package/lib/secureScreen.js +32 -0
  78. package/lib/stepUpKey.d.ts +105 -0
  79. package/lib/stepUpKey.js +187 -0
  80. package/lib/transport.d.ts +39 -0
  81. package/lib/transport.js +117 -0
  82. package/lib/util/bytes.d.ts +18 -0
  83. package/lib/util/bytes.js +207 -0
  84. package/lib/util/native.d.ts +20 -0
  85. package/lib/util/native.js +49 -0
  86. package/lib/util/release.d.ts +2 -0
  87. package/lib/util/release.js +7 -0
  88. package/lib/util/target.d.ts +18 -0
  89. package/lib/util/target.js +128 -0
  90. package/package.json +81 -4
  91. package/src/client.ts +221 -0
  92. package/src/collector.ts +64 -0
  93. package/src/deviceTrust.ts +842 -0
  94. package/src/errors.ts +71 -0
  95. package/src/index.ts +19 -0
  96. package/src/installationKey.ts +223 -0
  97. package/src/installationStore.ts +143 -0
  98. package/src/native/NativeNecessaryInstallationKey.ts +12 -0
  99. package/src/native/NativeNecessaryIntegrity.ts +15 -0
  100. package/src/native/NativeNecessarySecureScreen.ts +9 -0
  101. package/src/native/NativeNecessaryStepUpKey.ts +11 -0
  102. package/src/native/NativeNecessaryStorage.ts +16 -0
  103. package/src/protocol.ts +52 -0
  104. package/src/reinstallHint.ts +69 -0
  105. package/src/report.ts +174 -0
  106. package/src/requestProof.ts +295 -0
  107. package/src/secureScreen.ts +29 -0
  108. package/src/stepUpKey.ts +238 -0
  109. package/src/transport.ts +145 -0
  110. package/src/util/bytes.ts +194 -0
  111. package/src/util/native.ts +56 -0
  112. package/src/util/release.ts +6 -0
  113. package/src/util/target.ts +132 -0
  114. package/vectors/client_server.json +264 -0
@@ -0,0 +1,24 @@
1
+ /** What the client does about the protocol errors of the public guide's section 4. */
2
+ import { ApiException } from './errors';
3
+ /**
4
+ * What the client does about a refusal from your backend's API: the protocol errors of the
5
+ * integration guide's section 4, each acted on once. Any other refusal goes to the app as it came.
6
+ */
7
+ export declare enum ProtocolStep {
8
+ askAgain = "askAgain",
9
+ proveAgain = "proveAgain",
10
+ registerAgain = "registerAgain",
11
+ waitThenRetry = "waitThenRetry",
12
+ stop = "stop"
13
+ }
14
+ /** The longest wait the client sits through before it retries a 429 itself; a longer one goes to the app. */
15
+ export declare const protocolMaxWaitSeconds = 10;
16
+ /**
17
+ * [deviceSession]: the request ran under the session from /v1/installations/verify, which the
18
+ * client can renew by proving its key again; an account session is the app's to renew.
19
+ */
20
+ export declare function protocolStepFor(error: ApiException, options: {
21
+ deviceSession: boolean;
22
+ }): ProtocolStep;
23
+ /** How long a 429 asks the client to wait, in milliseconds, when that is short enough to wait. */
24
+ export declare function protocolWait(error: ApiException): number | null;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /** What the client does about the protocol errors of the public guide's section 4. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.protocolMaxWaitSeconds = exports.ProtocolStep = void 0;
5
+ exports.protocolStepFor = protocolStepFor;
6
+ exports.protocolWait = protocolWait;
7
+ /**
8
+ * What the client does about a refusal from your backend's API: the protocol errors of the
9
+ * integration guide's section 4, each acted on once. Any other refusal goes to the app as it came.
10
+ */
11
+ var ProtocolStep;
12
+ (function (ProtocolStep) {
13
+ ProtocolStep["askAgain"] = "askAgain";
14
+ ProtocolStep["proveAgain"] = "proveAgain";
15
+ ProtocolStep["registerAgain"] = "registerAgain";
16
+ ProtocolStep["waitThenRetry"] = "waitThenRetry";
17
+ ProtocolStep["stop"] = "stop";
18
+ })(ProtocolStep || (exports.ProtocolStep = ProtocolStep = {}));
19
+ /** The longest wait the client sits through before it retries a 429 itself; a longer one goes to the app. */
20
+ exports.protocolMaxWaitSeconds = 10;
21
+ /**
22
+ * [deviceSession]: the request ran under the session from /v1/installations/verify, which the
23
+ * client can renew by proving its key again; an account session is the app's to renew.
24
+ */
25
+ function protocolStepFor(error, options) {
26
+ switch (error.code) {
27
+ case 'challenge_expired':
28
+ case 'challenge_used':
29
+ case 'integrity_challenge_expired':
30
+ case 'integrity_challenge_replay':
31
+ return ProtocolStep.askAgain;
32
+ case 'expired_token':
33
+ return options.deviceSession ? ProtocolStep.proveAgain : ProtocolStep.stop;
34
+ case 'installation_not_found':
35
+ return ProtocolStep.registerAgain;
36
+ }
37
+ return protocolWait(error) === null ? ProtocolStep.stop : ProtocolStep.waitThenRetry;
38
+ }
39
+ /** How long a 429 asks the client to wait, in milliseconds, when that is short enough to wait. */
40
+ function protocolWait(error) {
41
+ const seconds = error.details?.['retry_after_seconds'];
42
+ if (error.statusCode !== 429 ||
43
+ typeof seconds !== 'number' ||
44
+ !Number.isFinite(seconds) ||
45
+ seconds < 0 ||
46
+ seconds > exports.protocolMaxWaitSeconds) {
47
+ return null;
48
+ }
49
+ return Math.round(seconds * 1000);
50
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The reinstall hint: a SHA-256 of the platform's per-app device identifier, which the backend
3
+ * keys before storing. Client input, never proof.
4
+ */
5
+ export declare class ReinstallHint {
6
+ readonly kind: string;
7
+ readonly value: string;
8
+ constructor(value: {
9
+ kind: string;
10
+ value: string;
11
+ });
12
+ toJSON(): Record<string, unknown>;
13
+ }
14
+ export declare class ReinstallHintReader {
15
+ private readonly platform;
16
+ private readonly identifier;
17
+ /** [platform] and [identifier] replace the phone's in tests. */
18
+ constructor(options?: {
19
+ platform?: () => string;
20
+ identifier?: () => Promise<string>;
21
+ });
22
+ read(): Promise<ReinstallHint | null>;
23
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * The reinstall hint: a SHA-256 of the platform's per-app device identifier, which the backend
4
+ * keys before storing. Client input, never proof.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ReinstallHintReader = exports.ReinstallHint = void 0;
8
+ const bytes_1 = require("./util/bytes");
9
+ const native_1 = require("./util/native");
10
+ class ReinstallHint {
11
+ constructor(value) {
12
+ this.kind = value.kind;
13
+ this.value = value.value;
14
+ }
15
+ toJSON() {
16
+ return { kind: this.kind, value: this.value };
17
+ }
18
+ }
19
+ exports.ReinstallHint = ReinstallHint;
20
+ class ReinstallHintReader {
21
+ /** [platform] and [identifier] replace the phone's in tests. */
22
+ constructor(options = {}) {
23
+ this.platform = options.platform ?? native_1.currentPlatform;
24
+ this.identifier =
25
+ options.identifier ??
26
+ (async () => {
27
+ const native = (0, native_1.installationKeyModule)();
28
+ return native === null ? '' : await native.reinstallHintIdentifier();
29
+ });
30
+ }
31
+ async read() {
32
+ // The phone's per-app identifier: ANDROID_ID, or the identifier for vendor on iOS.
33
+ let kind;
34
+ let platform;
35
+ try {
36
+ platform = this.platform();
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ switch (platform) {
42
+ case 'android':
43
+ kind = 'android_id';
44
+ break;
45
+ case 'ios':
46
+ kind = 'idfv';
47
+ break;
48
+ default:
49
+ return null;
50
+ }
51
+ try {
52
+ const value = ((await this.identifier()) ?? '').trim().toLowerCase();
53
+ if (value.length === 0) {
54
+ return null;
55
+ }
56
+ const digest = (0, bytes_1.sha256Hex)(`device-recognition-hint-v1|${kind}|${value}`);
57
+ return new ReinstallHint({ kind: `${kind}_sha256`, value: digest });
58
+ }
59
+ catch {
60
+ // A missing hint must never stop cryptographic installation identity.
61
+ }
62
+ return null;
63
+ }
64
+ }
65
+ exports.ReinstallHintReader = ReinstallHintReader;
@@ -0,0 +1,62 @@
1
+ /** The scan (the public guide, section 2): your backend's challenge, the measurements it asks for, and the report the installation key signs. */
2
+ import type { NativeIntegrityCollector } from './collector';
3
+ import type { InstallationIdentity } from './installationKey';
4
+ /** The most a backend takes: a report of 65,536 bytes of JSON (the measurement contract, section 1). */
5
+ export declare const reportByteLimit = 65536;
6
+ /**
7
+ * The lists a report may shorten, by measurement -- each beside the `truncated` flag that says the
8
+ * phone had more (the measurement contract, section 6).
9
+ */
10
+ export declare const shortenableLists: Readonly<Record<string, readonly string[]>>;
11
+ /**
12
+ * [report] made to fit [reportByteLimit]: the longest list it may shorten is halved, and marked
13
+ * `truncated`, until it fits. A report that cannot fit is never cut anywhere else: the answer is an
14
+ * error naming the limit.
15
+ */
16
+ export declare function fitReport(report: Record<string, unknown>): Record<string, unknown>;
17
+ /** A challenge for a scan, as your backend sent it. */
18
+ export declare class ScanChallenge {
19
+ readonly challengeId: string;
20
+ readonly nonce: string;
21
+ readonly platform: string;
22
+ /** The measurements the report must answer. */
23
+ readonly requiredProbes: string[];
24
+ /** The personal measurements asked for, answered only where the app enabled them; none from a backend that names none. */
25
+ readonly optionalProbes: string[];
26
+ /** The challenge whole. */
27
+ readonly answer: Record<string, unknown>;
28
+ constructor(value: {
29
+ challengeId: string;
30
+ nonce: string;
31
+ platform: string;
32
+ requiredProbes: string[];
33
+ optionalProbes: string[];
34
+ answer: Record<string, unknown>;
35
+ });
36
+ static fromJson(value: Record<string, unknown>): ScanChallenge;
37
+ }
38
+ /** A report as the installation key signed it. */
39
+ export declare class SignedReport {
40
+ /** The report, as signed. */
41
+ readonly report: Record<string, unknown>;
42
+ /** `report_payload`: the base64url of the report's JSON bytes. */
43
+ readonly payload: string;
44
+ /** `report_signature`. */
45
+ readonly signature: string;
46
+ constructor(value: {
47
+ report: Record<string, unknown>;
48
+ payload: string;
49
+ signature: string;
50
+ });
51
+ toJSON(): Record<string, unknown>;
52
+ }
53
+ /**
54
+ * Measures what [challenge] asks for -- and, of the personal measurements it asks for, those in
55
+ * [enabledMeasurements] -- and signs the report with [identity]'s key.
56
+ */
57
+ export declare function collectReport(args: {
58
+ challenge: ScanChallenge;
59
+ identity: InstallationIdentity;
60
+ collector: NativeIntegrityCollector;
61
+ enabledMeasurements: string[];
62
+ }): Promise<SignedReport>;
package/lib/report.js ADDED
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ /** The scan (the public guide, section 2): your backend's challenge, the measurements it asks for, and the report the installation key signs. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.SignedReport = exports.ScanChallenge = exports.shortenableLists = exports.reportByteLimit = void 0;
5
+ exports.fitReport = fitReport;
6
+ exports.collectReport = collectReport;
7
+ const errors_1 = require("./errors");
8
+ const bytes_1 = require("./util/bytes");
9
+ const native_1 = require("./util/native");
10
+ /** The most a backend takes: a report of 65,536 bytes of JSON (the measurement contract, section 1). */
11
+ exports.reportByteLimit = 65536;
12
+ /**
13
+ * The lists a report may shorten, by measurement -- each beside the `truncated` flag that says the
14
+ * phone had more (the measurement contract, section 6).
15
+ */
16
+ exports.shortenableLists = {
17
+ accessibility: ['services', 'settings_only'],
18
+ notification_access: ['listeners'],
19
+ other_access: ['device_admins', 'remote_access_apps'],
20
+ };
21
+ /**
22
+ * [report] made to fit [reportByteLimit]: the longest list it may shorten is halved, and marked
23
+ * `truncated`, until it fits. A report that cannot fit is never cut anywhere else: the answer is an
24
+ * error naming the limit.
25
+ */
26
+ function fitReport(report) {
27
+ const probes = { ...report['probe_results'] };
28
+ const fitted = { ...report, probe_results: probes };
29
+ const size = () => (0, bytes_1.utf8Encode)(JSON.stringify(fitted)).length;
30
+ for (let bytes = size(); bytes > exports.reportByteLimit; bytes = size()) {
31
+ let probe = null;
32
+ let field = null;
33
+ let longest = 0;
34
+ for (const [name, fields] of Object.entries(exports.shortenableLists)) {
35
+ const result = probes[name];
36
+ for (const list of fields) {
37
+ const items = (0, native_1.isObject)(result) ? result[list] : undefined;
38
+ if (Array.isArray(items) && items.length > longest) {
39
+ probe = name;
40
+ field = list;
41
+ longest = items.length;
42
+ }
43
+ }
44
+ }
45
+ if (probe === null || field === null) {
46
+ throw new errors_1.ApiException(`The report would be ${bytes} bytes; a backend takes at most ${exports.reportByteLimit}.`, {
47
+ code: 'report_too_large',
48
+ details: { bytes, limit: exports.reportByteLimit },
49
+ });
50
+ }
51
+ const shortened = { ...probes[probe] };
52
+ shortened[field] = shortened[field].slice(0, Math.floor(longest / 2));
53
+ shortened['truncated'] = true;
54
+ probes[probe] = shortened;
55
+ }
56
+ return fitted;
57
+ }
58
+ /** A challenge for a scan, as your backend sent it. */
59
+ class ScanChallenge {
60
+ constructor(value) {
61
+ this.challengeId = value.challengeId;
62
+ this.nonce = value.nonce;
63
+ this.platform = value.platform;
64
+ this.requiredProbes = value.requiredProbes;
65
+ this.optionalProbes = value.optionalProbes;
66
+ this.answer = value.answer;
67
+ }
68
+ static fromJson(value) {
69
+ const names = (list) => (Array.isArray(list) ? list.map((item) => String(item)) : []);
70
+ const text = (field) => {
71
+ const item = value[field];
72
+ if (typeof item !== 'string') {
73
+ throw new TypeError(`The scan challenge has no ${field}.`);
74
+ }
75
+ return item;
76
+ };
77
+ return new ScanChallenge({
78
+ challengeId: text('challenge_id'),
79
+ nonce: text('nonce'),
80
+ platform: text('platform'),
81
+ requiredProbes: names(value['required_probes']),
82
+ optionalProbes: names(value['optional_probes']),
83
+ answer: value,
84
+ });
85
+ }
86
+ }
87
+ exports.ScanChallenge = ScanChallenge;
88
+ /** A report as the installation key signed it. */
89
+ class SignedReport {
90
+ constructor(value) {
91
+ this.report = value.report;
92
+ this.payload = value.payload;
93
+ this.signature = value.signature;
94
+ }
95
+ toJSON() {
96
+ return { report_payload: this.payload, report_signature: this.signature };
97
+ }
98
+ }
99
+ exports.SignedReport = SignedReport;
100
+ /**
101
+ * Measures what [challenge] asks for -- and, of the personal measurements it asks for, those in
102
+ * [enabledMeasurements] -- and signs the report with [identity]'s key.
103
+ */
104
+ async function collectReport(args) {
105
+ const { challenge, identity, collector, enabledMeasurements } = args;
106
+ const native = await collector.collect({
107
+ requiredProbes: challenge.requiredProbes,
108
+ challengeNonce: challenge.nonce,
109
+ enabledMeasurements,
110
+ optionalProbes: challenge.optionalProbes,
111
+ });
112
+ if (!(0, native_1.isObject)(native['probes'])) {
113
+ throw new errors_1.ApiException('The native collector returned no probe map.', {
114
+ code: 'invalid_native_integrity_result',
115
+ });
116
+ }
117
+ if (native['platform'] !== challenge.platform || native['challenge_nonce_echo'] !== challenge.nonce) {
118
+ throw new errors_1.ApiException('The native integrity response did not match the server challenge.', {
119
+ code: 'integrity_challenge_binding_failed',
120
+ });
121
+ }
122
+ const version = native['collector_version'];
123
+ if (version != null && typeof version !== 'number') {
124
+ throw new TypeError('The native collector returned an invalid collector_version.');
125
+ }
126
+ const report = fitReport({
127
+ challenge_id: challenge.challengeId,
128
+ challenge_nonce: challenge.nonce,
129
+ installation_id: identity.installationId,
130
+ platform: challenge.platform,
131
+ collector_version: typeof version === 'number' ? Math.trunc(version) : 1,
132
+ collected_at: Math.floor(Date.now() / 1000),
133
+ probe_results: { ...native['probes'] },
134
+ // The personal measurements this app enabled, whether or not this scan asked for them.
135
+ enabled_measurements: enabledMeasurements,
136
+ version: 1,
137
+ });
138
+ const payload = (0, bytes_1.base64UrlNoPadding)((0, bytes_1.utf8Encode)(JSON.stringify(report)));
139
+ return new SignedReport({ report, payload, signature: await identity.signPayload(payload) });
140
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Request proofs (the public guide, section 3): the installation key signs each request, so a
3
+ * copied session token is useless without the key; with step-up on the person approves one
4
+ * request with the step-up key, and with the app PIN on a request carries the backend's proof of
5
+ * the PIN. Also the description of the one request a scan is to approve (section 2).
6
+ */
7
+ import type { InstallationIdentity } from './installationKey';
8
+ export { base64UrlNoPadding } from './util/bytes';
9
+ /** Where [freshNonce] takes its bytes: the platform's secure random source, replaced in tests. */
10
+ export declare let secureRandomBytes: (count: number) => Promise<Uint8Array>;
11
+ /** Replaces the secure random source -- for tests, which run where no phone is. */
12
+ export declare function setSecureRandomSource(source: (count: number) => Promise<Uint8Array>): void;
13
+ /** 32 random bytes from the platform's secure source. */
14
+ export declare function freshNonce(): Promise<Uint8Array>;
15
+ /**
16
+ * The person's approval of one request: the step-up key's [factor], and [sign], which has the
17
+ * step-up key sign a payload -- the phone asks the person (NativeStepUpKey.sign).
18
+ */
19
+ export interface StepUpApproval {
20
+ factor: string;
21
+ sign: (payload: string) => Promise<string>;
22
+ }
23
+ export interface RequestProofFields {
24
+ bearerToken: string;
25
+ method: string;
26
+ path: string;
27
+ encodedBody: string;
28
+ payload: string;
29
+ signature: string;
30
+ timestamp: number;
31
+ nonce: string;
32
+ stepUpPayload?: string | null;
33
+ stepUpSignature?: string | null;
34
+ appPinProof?: string | null;
35
+ integrityReport?: string | null;
36
+ }
37
+ /**
38
+ * What the installation key signed for one request -- with step-up, what the step-up key signed;
39
+ * with the app PIN, the backend's proof of it; for a request a scan approves, that scan's report --
40
+ * and the headers that carry them beside `Authorization: Bearer`.
41
+ */
42
+ export declare class RequestProof {
43
+ readonly bearerToken: string;
44
+ /** The request the proof was signed for: its method, its path with the query, and the exact body. */
45
+ readonly method: string;
46
+ readonly path: string;
47
+ readonly encodedBody: string;
48
+ /** `X-Access-Proof`: the base64url of the proof's JSON bytes. */
49
+ readonly payload: string;
50
+ /** `X-Access-Signature`. */
51
+ readonly signature: string;
52
+ readonly timestamp: number;
53
+ readonly nonce: string;
54
+ /** `X-Step-Up-Proof` and `X-Step-Up-Signature`, for a request the person approved. */
55
+ readonly stepUpPayload: string | null;
56
+ readonly stepUpSignature: string | null;
57
+ /** `X-App-PIN-Proof`: the backend's proof that the person entered the app PIN for this request. */
58
+ readonly appPinProof: string | null;
59
+ /** `X-Integrity-Report`: the report of the scan made for this request alone (the public guide, section 2). */
60
+ readonly integrityReport: string | null;
61
+ constructor(fields: RequestProofFields);
62
+ get headers(): Record<string, string>;
63
+ /** This request, unlocked by the backend's [proof] of the app PIN. */
64
+ withAppPinProof(proof: string): RequestProof;
65
+ /** This request, naming [reportId], the scan made for it alone. */
66
+ withIntegrityReport(reportId: string): RequestProof;
67
+ private with;
68
+ }
69
+ /**
70
+ * What the app sends to have its backend check the app PIN for one request: the PIN, and that
71
+ * request as its request proof signs it -- method, path, query, the hash of the body sent and the
72
+ * request proof's nonce.
73
+ */
74
+ export declare function appPinProofRequest(args: {
75
+ pin: string;
76
+ method: string;
77
+ path: string;
78
+ encodedBody: string;
79
+ nonce: string;
80
+ }): Record<string, unknown>;
81
+ /**
82
+ * The step-up proof, version 2, that the step-up key signs for one request: the base64url of its
83
+ * JSON, the members in this order. [nonce] is the request proof's nonce for the same request, so
84
+ * the approval is used up with it.
85
+ */
86
+ export declare function stepUpProof(args: {
87
+ installationId: string;
88
+ accessToken: string;
89
+ method: string;
90
+ path: string;
91
+ encodedBody: string;
92
+ factor: string;
93
+ nonce: string;
94
+ timestamp: number;
95
+ }): string;
96
+ /**
97
+ * Signs [method] [path] with [encodedBody] under [bearerToken]: proof version 2, which signs the
98
+ * query string too, exactly as sent. A GET signs an empty body. With [stepUp], the step-up key signs
99
+ * first -- the phone asks the person -- and the request proof after, with the same nonce, so the
100
+ * time the person takes does not count against the request proof's clock window.
101
+ * [installationId], [timestamp] and [nonce] replace what the request proof would otherwise carry,
102
+ * for the shared test vectors and for testing a backend's refusals.
103
+ */
104
+ export declare function signRequest(args: {
105
+ identity: InstallationIdentity;
106
+ method: string;
107
+ path: string;
108
+ bearerToken: string;
109
+ encodedBody?: string;
110
+ stepUp?: StepUpApproval | null;
111
+ installationId?: string | null;
112
+ timestamp?: number | null;
113
+ nonce?: Uint8Array | readonly number[] | null;
114
+ }): Promise<RequestProof>;
115
+ /** The one request a scan is to approve, described exactly as it will be sent: what the challenge names as its `operation`. */
116
+ export declare function describeOperation(method: string, path: string, encodedBody: string): Record<string, unknown>;