trustplane-sdk 0.1.1 → 0.3.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/README.md +50 -1
- package/index.d.ts +38 -0
- package/index.js +103 -1
- package/integration_test.js +59 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Trustplane JS SDK (v0.
|
|
1
|
+
# Trustplane JS SDK (v0.2)
|
|
2
2
|
|
|
3
3
|
Minimal SDK to generate Trustplane proof headers.
|
|
4
4
|
|
|
@@ -55,3 +55,52 @@ const out = client.sign({
|
|
|
55
55
|
privateKey: '<private_key_b64url>'
|
|
56
56
|
});
|
|
57
57
|
```
|
|
58
|
+
|
|
59
|
+
## Blindfold verify (one call)
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const { blindfoldVerify, fromFile } = require('trustplane-sdk');
|
|
63
|
+
|
|
64
|
+
const res = await blindfoldVerify({
|
|
65
|
+
authBaseUrl: 'https://auth.trustplane.mergematter.io',
|
|
66
|
+
tenantId: 'new_tenant',
|
|
67
|
+
apiId: 'api_demo_2',
|
|
68
|
+
clientId: 'client_demo',
|
|
69
|
+
privateKey: '<private_key_b64url>',
|
|
70
|
+
method: 'GET',
|
|
71
|
+
path: '/orders',
|
|
72
|
+
body: '',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(res.status, res.data);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Blindfold uses a blind OPRF exchange under the hood and only sends a blinded input to the Auth Plane.
|
|
79
|
+
|
|
80
|
+
You can also load `auth_base_url` from `trustplane.json`:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
const { fromFile } = require('trustplane-sdk');
|
|
84
|
+
|
|
85
|
+
const client = fromFile('./trustplane.json');
|
|
86
|
+
const res = await client.blindfoldVerify({
|
|
87
|
+
method: 'GET',
|
|
88
|
+
path: '/orders',
|
|
89
|
+
body: '',
|
|
90
|
+
privateKey: '<private_key_b64url>'
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Integration test (against auth plane)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
TP_AUTH_BASE_URL=https://auth.trustplane.mergematter.io \
|
|
98
|
+
TP_TENANT_ID=<tenant_id> \
|
|
99
|
+
TP_API_ID=<api_id> \
|
|
100
|
+
TP_CLIENT_ID=<client_id> \
|
|
101
|
+
TP_PRIVATE_KEY=<private_key_b64url> \
|
|
102
|
+
TP_MODE=core \
|
|
103
|
+
npm run test:integration
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For blindfold APIs, use `TP_MODE=blindfold`.
|
package/index.d.ts
CHANGED
|
@@ -30,15 +30,53 @@ export type SignOutput = {
|
|
|
30
30
|
|
|
31
31
|
export function sign(input: SignInput): SignOutput;
|
|
32
32
|
export function signAsync(input: SignInput): Promise<SignOutput>;
|
|
33
|
+
export function blindfoldVerify(input: SignInput & {
|
|
34
|
+
authBaseUrl: string;
|
|
35
|
+
fetchFn?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
step: string;
|
|
38
|
+
status: number;
|
|
39
|
+
ok: boolean;
|
|
40
|
+
data: any;
|
|
41
|
+
verifyPayload?: any;
|
|
42
|
+
transcript?: string;
|
|
43
|
+
digest?: string;
|
|
44
|
+
}>;
|
|
33
45
|
|
|
34
46
|
export function createClient(input: {
|
|
35
47
|
tenantId: string;
|
|
36
48
|
apiId: string;
|
|
37
49
|
clientId: string;
|
|
38
50
|
bucketSeconds?: number;
|
|
51
|
+
authBaseUrl?: string;
|
|
52
|
+
gatewayUrl?: string;
|
|
53
|
+
requestPath?: string;
|
|
54
|
+
proofType?: string;
|
|
39
55
|
}): {
|
|
56
|
+
config: {
|
|
57
|
+
tenantId: string;
|
|
58
|
+
apiId: string;
|
|
59
|
+
clientId: string;
|
|
60
|
+
bucketSeconds?: number;
|
|
61
|
+
authBaseUrl?: string;
|
|
62
|
+
gatewayUrl?: string;
|
|
63
|
+
requestPath?: string;
|
|
64
|
+
proofType?: string;
|
|
65
|
+
};
|
|
40
66
|
sign(input: Omit<SignInput, "tenantId" | "apiId" | "clientId" | "bucketSeconds">): SignOutput;
|
|
41
67
|
signAsync(input: Omit<SignInput, "tenantId" | "apiId" | "clientId" | "bucketSeconds">): Promise<SignOutput>;
|
|
68
|
+
blindfoldVerify(input: Omit<SignInput, "tenantId" | "apiId" | "clientId" | "bucketSeconds"> & {
|
|
69
|
+
authBaseUrl?: string;
|
|
70
|
+
fetchFn?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
step: string;
|
|
73
|
+
status: number;
|
|
74
|
+
ok: boolean;
|
|
75
|
+
data: any;
|
|
76
|
+
verifyPayload?: any;
|
|
77
|
+
transcript?: string;
|
|
78
|
+
digest?: string;
|
|
79
|
+
}>;
|
|
42
80
|
};
|
|
43
81
|
|
|
44
82
|
export function fromFile(path: string): ReturnType<typeof createClient>;
|
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const nacl = require('tweetnacl');
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const OPRF = require('oprf');
|
|
4
5
|
|
|
5
6
|
function base64urlEncode(buf) {
|
|
6
7
|
return Buffer.from(buf)
|
|
@@ -189,21 +190,122 @@ function fromFile(path) {
|
|
|
189
190
|
apiId: cfg.api_id,
|
|
190
191
|
clientId: cfg.client_id,
|
|
191
192
|
bucketSeconds: cfg.bucket_seconds,
|
|
193
|
+
authBaseUrl: cfg.auth_base_url,
|
|
194
|
+
gatewayUrl: cfg.gateway_url,
|
|
195
|
+
requestPath: cfg.request_path,
|
|
196
|
+
proofType: cfg.proof_type,
|
|
192
197
|
});
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
function createClient({ tenantId, apiId, clientId, bucketSeconds }) {
|
|
200
|
+
function createClient({ tenantId, apiId, clientId, bucketSeconds, authBaseUrl, gatewayUrl, requestPath, proofType }) {
|
|
196
201
|
return {
|
|
202
|
+
config: {
|
|
203
|
+
tenantId,
|
|
204
|
+
apiId,
|
|
205
|
+
clientId,
|
|
206
|
+
bucketSeconds,
|
|
207
|
+
authBaseUrl,
|
|
208
|
+
gatewayUrl,
|
|
209
|
+
requestPath,
|
|
210
|
+
proofType,
|
|
211
|
+
},
|
|
197
212
|
sign: ({ method, path, body, privateKey }) =>
|
|
198
213
|
signProof({ tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds }),
|
|
199
214
|
signAsync: ({ method, path, body, privateKey }) =>
|
|
200
215
|
signProofAsync({ tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds }),
|
|
216
|
+
blindfoldVerify: async ({ authBaseUrl: authOverride, method, path, body, privateKey, fetchFn }) =>
|
|
217
|
+
blindfoldVerify({
|
|
218
|
+
authBaseUrl: authOverride || authBaseUrl,
|
|
219
|
+
tenantId,
|
|
220
|
+
apiId,
|
|
221
|
+
clientId,
|
|
222
|
+
privateKey,
|
|
223
|
+
method,
|
|
224
|
+
path,
|
|
225
|
+
body,
|
|
226
|
+
bucketSeconds,
|
|
227
|
+
fetchFn,
|
|
228
|
+
}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function httpJSON(url, payload, fetchFn) {
|
|
233
|
+
const impl = fetchFn || (typeof fetch !== 'undefined' ? fetch : null);
|
|
234
|
+
if (!impl) {
|
|
235
|
+
throw new Error('fetch is required; provide fetchFn or run in an environment with global fetch');
|
|
236
|
+
}
|
|
237
|
+
const res = await impl(url, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: { 'content-type': 'application/json' },
|
|
240
|
+
body: JSON.stringify(payload),
|
|
241
|
+
});
|
|
242
|
+
const text = await res.text();
|
|
243
|
+
let data;
|
|
244
|
+
try {
|
|
245
|
+
data = JSON.parse(text);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
data = { raw: text };
|
|
248
|
+
}
|
|
249
|
+
return { status: res.status, ok: res.ok, data };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function blindfoldVerify({ authBaseUrl, tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds, fetchFn }) {
|
|
253
|
+
if (!authBaseUrl) throw new Error('authBaseUrl is required');
|
|
254
|
+
const signed = signProof({ tenantId, apiId, clientId, privateKey, method, path, body, bucketSeconds });
|
|
255
|
+
const base = String(authBaseUrl).replace(/\/+$/, '');
|
|
256
|
+
const oprf = new OPRF();
|
|
257
|
+
if (oprf.ready) {
|
|
258
|
+
await oprf.ready;
|
|
259
|
+
}
|
|
260
|
+
const start = await httpJSON(base + '/auth/blindfold/start', {
|
|
261
|
+
tenant_id: signed.verifyPayload.tenant_id,
|
|
262
|
+
api_id: signed.verifyPayload.api_id,
|
|
263
|
+
client_id: signed.verifyPayload.client_id,
|
|
264
|
+
method: signed.verifyPayload.method,
|
|
265
|
+
path: signed.verifyPayload.path,
|
|
266
|
+
body_hash: signed.verifyPayload.body_hash,
|
|
267
|
+
time_bucket: signed.verifyPayload.time_bucket,
|
|
268
|
+
nonce: signed.verifyPayload.nonce,
|
|
269
|
+
}, fetchFn);
|
|
270
|
+
if (!start.ok) {
|
|
271
|
+
return { step: 'start', ...start };
|
|
272
|
+
}
|
|
273
|
+
const oprfInput = signed.digest;
|
|
274
|
+
const masked = oprf.maskInput(oprfInput);
|
|
275
|
+
const evalRes = await httpJSON(base + '/oprf/full-evaluate', {
|
|
276
|
+
blinded_input_b64url: base64urlEncode(masked.point),
|
|
277
|
+
}, fetchFn);
|
|
278
|
+
if (!evalRes.ok) {
|
|
279
|
+
return { step: 'evaluate', ...evalRes };
|
|
280
|
+
}
|
|
281
|
+
const evaluated = base64urlDecode((evalRes.data || {}).evaluated_b64url || '');
|
|
282
|
+
const unmasked = oprf.unmaskPoint(new Uint8Array(evaluated), masked.mask);
|
|
283
|
+
const proofPayload = base64urlEncode(Buffer.from(unmasked));
|
|
284
|
+
const finalize = await httpJSON(base + '/auth/blindfold/finalize', {
|
|
285
|
+
session_id: (start.data || {}).session_id,
|
|
286
|
+
proof_payload: proofPayload,
|
|
287
|
+
}, fetchFn);
|
|
288
|
+
if (!finalize.ok) {
|
|
289
|
+
return { step: 'finalize', ...finalize };
|
|
290
|
+
}
|
|
291
|
+
const verifyPayload = Object.assign({}, signed.verifyPayload, {
|
|
292
|
+
proof_type: 'blindfold',
|
|
293
|
+
proof_payload: ((finalize.data || {}).verify_payload || {}).proof_payload || proofPayload || '',
|
|
294
|
+
});
|
|
295
|
+
const verify = await httpJSON(base + '/auth/verify', verifyPayload, fetchFn);
|
|
296
|
+
return {
|
|
297
|
+
step: 'verify',
|
|
298
|
+
...verify,
|
|
299
|
+
verifyPayload,
|
|
300
|
+
transcript: signed.transcript,
|
|
301
|
+
digest: signed.digest,
|
|
201
302
|
};
|
|
202
303
|
}
|
|
203
304
|
|
|
204
305
|
module.exports = {
|
|
205
306
|
sign: signProof,
|
|
206
307
|
signAsync: signProofAsync,
|
|
308
|
+
blindfoldVerify,
|
|
207
309
|
createClient,
|
|
208
310
|
fromFile,
|
|
209
311
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { sign, blindfoldVerify } = require('./index');
|
|
2
|
+
|
|
3
|
+
async function postJSON(url, payload) {
|
|
4
|
+
const res = await fetch(url, {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
headers: { 'content-type': 'application/json' },
|
|
7
|
+
body: JSON.stringify(payload),
|
|
8
|
+
});
|
|
9
|
+
const data = await res.json().catch(() => ({}));
|
|
10
|
+
return { status: res.status, ok: res.ok, data };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const base = process.env.TP_AUTH_BASE_URL || '';
|
|
15
|
+
const tenantId = process.env.TP_TENANT_ID || '';
|
|
16
|
+
const apiId = process.env.TP_API_ID || '';
|
|
17
|
+
const clientId = process.env.TP_CLIENT_ID || '';
|
|
18
|
+
const privateKey = process.env.TP_PRIVATE_KEY || '';
|
|
19
|
+
const path = process.env.TP_PATH || '/orders';
|
|
20
|
+
const mode = (process.env.TP_MODE || 'core').toLowerCase();
|
|
21
|
+
|
|
22
|
+
if (!base || !tenantId || !apiId || !clientId || !privateKey) {
|
|
23
|
+
console.log('SKIP: set TP_AUTH_BASE_URL TP_TENANT_ID TP_API_ID TP_CLIENT_ID TP_PRIVATE_KEY');
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (mode === 'blindfold') {
|
|
28
|
+
const out = await blindfoldVerify({
|
|
29
|
+
authBaseUrl: base,
|
|
30
|
+
tenantId,
|
|
31
|
+
apiId,
|
|
32
|
+
clientId,
|
|
33
|
+
privateKey,
|
|
34
|
+
method: 'GET',
|
|
35
|
+
path,
|
|
36
|
+
body: '',
|
|
37
|
+
});
|
|
38
|
+
console.log('blindfold', out.status, out.data && out.data.decision);
|
|
39
|
+
process.exit(out.ok ? 0 : 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const out = sign({
|
|
43
|
+
tenantId,
|
|
44
|
+
apiId,
|
|
45
|
+
clientId,
|
|
46
|
+
privateKey,
|
|
47
|
+
method: 'GET',
|
|
48
|
+
path,
|
|
49
|
+
body: '',
|
|
50
|
+
});
|
|
51
|
+
const res = await postJSON(String(base).replace(/\/+$/, '') + '/auth/verify', out.verifyPayload);
|
|
52
|
+
console.log('core', res.status, res.data && res.data.decision);
|
|
53
|
+
process.exit(res.ok ? 0 : 1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main().catch((err) => {
|
|
57
|
+
console.error(err);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trustplane-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Trustplane SDK (JS) for generating request proof headers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -8,16 +8,19 @@
|
|
|
8
8
|
"index.js",
|
|
9
9
|
"index.d.ts",
|
|
10
10
|
"README.md",
|
|
11
|
-
"test_vector.js"
|
|
11
|
+
"test_vector.js",
|
|
12
|
+
"integration_test.js"
|
|
12
13
|
],
|
|
13
14
|
"publishConfig": {
|
|
14
15
|
"access": "public"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
17
|
-
"test": "node test_vector.js"
|
|
18
|
+
"test": "node test_vector.js",
|
|
19
|
+
"test:integration": "node integration_test.js"
|
|
18
20
|
},
|
|
19
21
|
"license": "MIT",
|
|
20
22
|
"dependencies": {
|
|
23
|
+
"oprf": "^2.0.0",
|
|
21
24
|
"tweetnacl": "^1.0.3"
|
|
22
25
|
}
|
|
23
26
|
}
|