@dxos/credentials 2.33.1-dev.83d113fe → 2.33.1-dev.eb88db79

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 (62) hide show
  1. package/.rush/temp/package-deps_build.json +14 -14
  2. package/.rush/temp/package-deps_build:test.json +13 -13
  3. package/.rush/temp/package-deps_prerelease.json +13 -13
  4. package/dist/src/auth/auth-message.d.ts +1 -1
  5. package/dist/src/auth/auth-message.d.ts.map +1 -1
  6. package/dist/src/auth/auth-message.js +4 -3
  7. package/dist/src/auth/auth-message.js.map +1 -1
  8. package/dist/src/auth/auth-plugin.test.js +2 -4
  9. package/dist/src/auth/auth-plugin.test.js.map +1 -1
  10. package/dist/src/auth/auth.test.d.ts.map +1 -1
  11. package/dist/src/auth/auth.test.js +255 -248
  12. package/dist/src/auth/auth.test.js.map +1 -1
  13. package/dist/src/auth/authenticator.d.ts +9 -14
  14. package/dist/src/auth/authenticator.d.ts.map +1 -1
  15. package/dist/src/auth/authenticator.js +15 -46
  16. package/dist/src/auth/authenticator.js.map +1 -1
  17. package/dist/src/greet/greeter-plugin.test.js +4 -4
  18. package/dist/src/greet/greeter-plugin.test.js.map +1 -1
  19. package/dist/src/greet/greeter.d.ts +15 -15
  20. package/dist/src/greet/greeter.js +5 -5
  21. package/dist/src/greet/greeter.js.map +1 -1
  22. package/dist/src/greet/greeter.test.js +12 -12
  23. package/dist/src/greet/greeter.test.js.map +1 -1
  24. package/dist/src/greet/greeting-message.d.ts +2 -2
  25. package/dist/src/greet/greeting-message.js +9 -9
  26. package/dist/src/greet/greeting-message.js.map +1 -1
  27. package/dist/src/identity/identity-message.js +4 -4
  28. package/dist/src/identity/identity-message.js.map +1 -1
  29. package/dist/src/keys/keyring-helpers.d.ts.map +1 -1
  30. package/dist/src/keys/keyring-helpers.js +4 -3
  31. package/dist/src/keys/keyring-helpers.js.map +1 -1
  32. package/dist/src/keys/keyring.js +2 -2
  33. package/dist/src/keys/keyring.js.map +1 -1
  34. package/dist/src/party/party-credential.d.ts +1 -1
  35. package/dist/src/party/party-credential.js +10 -10
  36. package/dist/src/party/party-credential.js.map +1 -1
  37. package/dist/src/proto/any.d.ts +3 -3
  38. package/dist/src/proto/any.d.ts.map +1 -1
  39. package/dist/src/proto/gen/dxos/credentials/auth.d.ts +16 -0
  40. package/dist/src/proto/gen/dxos/credentials/auth.d.ts.map +1 -1
  41. package/dist/src/proto/gen/index.d.ts.map +1 -1
  42. package/dist/src/proto/gen/index.js +1 -1
  43. package/dist/src/proto/gen/index.js.map +1 -1
  44. package/dist/src/proto/substitutions.d.ts +1 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +11 -11
  47. package/src/auth/auth-message.ts +5 -3
  48. package/src/auth/auth-plugin.test.ts +7 -9
  49. package/src/auth/auth.test.ts +551 -0
  50. package/src/auth/authenticator.ts +21 -42
  51. package/src/greet/greeter-plugin.test.ts +4 -4
  52. package/src/greet/greeter.test.js +12 -12
  53. package/src/greet/greeter.ts +5 -5
  54. package/src/greet/greeting-message.ts +9 -9
  55. package/src/identity/identity-message.ts +4 -4
  56. package/src/keys/keyring-helpers.ts +4 -3
  57. package/src/keys/keyring.ts +2 -2
  58. package/src/party/party-credential.ts +10 -10
  59. package/src/proto/any.ts +3 -3
  60. package/src/proto/gen/dxos/credentials/auth.ts +16 -0
  61. package/src/proto/gen/index.ts +1 -1
  62. package/src/auth/auth.test.js +0 -515
@@ -0,0 +1,551 @@
1
+ //
2
+ // Copyright 2019 DXOS.org
3
+ //
4
+
5
+ // DXOS testing browser.
6
+
7
+ import expect from 'expect';
8
+ import moment from 'moment';
9
+
10
+ import { PublicKey, randomBytes } from '@dxos/crypto';
11
+
12
+ import { Filter, Keyring } from '../keys';
13
+ import {
14
+ admitsKeys,
15
+ createEnvelopeMessage,
16
+ createFeedAdmitMessage,
17
+ createKeyAdmitMessage,
18
+ createPartyGenesisMessage,
19
+ PartyState
20
+ } from '../party';
21
+ import { codecLoop, KeyType, Message, SignedMessage } from '../proto';
22
+ import { createAuthMessage } from './auth-message';
23
+ import { PartyAuthenticator } from './authenticator';
24
+
25
+ const createPartyKeyrings = async () => {
26
+ // This Keyring has the full key pairs, which is the case when creating a new Party.
27
+ const keyring = new Keyring();
28
+ for (const type of Object.values(KeyType)) {
29
+ if (typeof type === 'string') {
30
+ await keyring.createKeyRecord({ type: KeyType[type as any] });
31
+ }
32
+ }
33
+
34
+ const partyKey = keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!.publicKey;
35
+
36
+ /* This Keyring will have nothing but the public key of the Party. This mimics the initial state
37
+ * when joining a Party, since all that is known at that time know at that time is the public key.
38
+ */
39
+ const bareKeyring = new Keyring();
40
+ await bareKeyring.addPublicKey({
41
+ publicKey: partyKey,
42
+ type: KeyType.PARTY,
43
+ trusted: true,
44
+ own: false
45
+ });
46
+
47
+ return {
48
+ partyKey,
49
+ keyring,
50
+ bareKeyring
51
+ };
52
+ };
53
+
54
+ /* eslint-enable @typescript-eslint/no-unused-vars */
55
+ const messageMap = (messages: (Message | SignedMessage)[]): Map<string, Message | SignedMessage> => {
56
+ const map = new Map();
57
+ for (const message of messages) {
58
+ const admits = admitsKeys(message);
59
+ for (const key of admits) {
60
+ map.set(key.toHex(), message);
61
+ }
62
+ }
63
+ return map;
64
+ };
65
+
66
+ const getIdentityKeyChainForDevice = (keyring: Keyring, devicePublicKey: PublicKey, messages: Map<string, Message | SignedMessage>) => {
67
+ // Excludes all the FEED keys.
68
+ return Keyring.buildKeyChain(devicePublicKey, messages,
69
+ keyring.findKeys(Filter.matches({ type: KeyType.FEED })).map(key => key.publicKey));
70
+ };
71
+
72
+ describe('PartyAuthenticator', () => {
73
+
74
+ it('Chain of Keys', async () => {
75
+ const haloKeyring = new Keyring();
76
+ const identityKey = await haloKeyring.createKeyRecord({ type: KeyType.PARTY });
77
+ const deviceKeyA = await haloKeyring.createKeyRecord({ type: KeyType.DEVICE });
78
+ const deviceKeyB = await haloKeyring.createKeyRecord({ type: KeyType.DEVICE });
79
+ const deviceKeyC = await haloKeyring.createKeyRecord({ type: KeyType.DEVICE });
80
+ const feedKeyA = await haloKeyring.createKeyRecord({ type: KeyType.FEED });
81
+
82
+ const messages = new Map();
83
+
84
+ // The first message in the chain in always a PartyGenesis for the Halo.
85
+ messages.set(identityKey.publicKey.toHex(), createPartyGenesisMessage(haloKeyring, identityKey, feedKeyA.publicKey, deviceKeyA));
86
+ messages.set(deviceKeyA.publicKey.toHex(), messages.get(identityKey.publicKey.toHex()));
87
+ messages.set(feedKeyA.publicKey.toHex(), messages.get(identityKey.publicKey.toHex()));
88
+
89
+ // Next is DeviceB greeted by DeviceA.
90
+ messages.set(deviceKeyB.publicKey.toHex(),
91
+ createEnvelopeMessage(haloKeyring, identityKey.publicKey,
92
+ createKeyAdmitMessage(haloKeyring, identityKey.publicKey, deviceKeyB, [deviceKeyB]),
93
+ [deviceKeyA]
94
+ ));
95
+
96
+ // Next is DeviceC greeted by DeviceB.
97
+ messages.set(deviceKeyC.publicKey.toHex(),
98
+ createEnvelopeMessage(haloKeyring, identityKey.publicKey,
99
+ createKeyAdmitMessage(haloKeyring, identityKey.publicKey, deviceKeyC, [deviceKeyC]),
100
+ [deviceKeyB]
101
+ ));
102
+
103
+ const targetKeyring = new Keyring();
104
+ await targetKeyring.addPublicKey({
105
+ publicKey: identityKey.publicKey,
106
+ type: KeyType.IDENTITY,
107
+ trusted: true,
108
+ own: false
109
+ });
110
+
111
+ for (const deviceKey of [deviceKeyA, deviceKeyB, deviceKeyC]) {
112
+ const emptyKeyring = new Keyring();
113
+ const chain = Keyring.buildKeyChain(deviceKey.publicKey, messages);
114
+ // In the target keyring, which only has the Identity, it should chase all the way back to the Identity.
115
+ expect(identityKey.publicKey).toEqual((await targetKeyring.findTrusted(chain))!.publicKey);
116
+ // And in the halo, which has all the keys, it should chase straight back to this key.
117
+ expect(deviceKey.publicKey).toEqual((await haloKeyring.findTrusted(chain))!.publicKey);
118
+ // And in an empty Keyring, we should not get anything.
119
+ expect(await emptyKeyring.findTrusted(chain)).toBeUndefined();
120
+ }
121
+ });
122
+
123
+ it('good direct', async () => {
124
+ const { keyring, partyKey } = await createPartyKeyrings();
125
+ const party = new PartyState(partyKey);
126
+ const auth = new PartyAuthenticator(party, async () => {});
127
+
128
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
129
+
130
+ const messages = [
131
+ createPartyGenesisMessage(keyring,
132
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
133
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
134
+ identityKeyRecord!
135
+ )
136
+ ];
137
+
138
+ // Only add the Identity to the party keyring.
139
+ await party.processMessages(messages);
140
+
141
+ const wrappedCredentials = codecLoop(
142
+ createAuthMessage(
143
+ keyring,
144
+ partyKey,
145
+ identityKeyRecord!,
146
+ identityKeyRecord!
147
+ )
148
+ );
149
+
150
+ const ok = await auth.authenticate(wrappedCredentials.payload);
151
+ expect(ok).toBe(true);
152
+ });
153
+
154
+ it('auth callback is called', async () => {
155
+ const { keyring, partyKey } = await createPartyKeyrings();
156
+ const party = new PartyState(partyKey);
157
+
158
+ let called = false;
159
+ const auth = new PartyAuthenticator(party, async (msg) => {
160
+ called = true;
161
+ });
162
+
163
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }))!;
164
+
165
+ const messages = [
166
+ createPartyGenesisMessage(keyring,
167
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
168
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
169
+ identityKeyRecord!
170
+ )
171
+ ];
172
+
173
+ // Only add the Identity to the party keyring.
174
+ await party.processMessages(messages);
175
+
176
+ const newFeedKeyRecord = await keyring.createKeyRecord({ type: KeyType.FEED })!;
177
+ const feedAdmit = createFeedAdmitMessage(
178
+ keyring,
179
+ partyKey,
180
+ newFeedKeyRecord.publicKey
181
+ );
182
+
183
+ const wrappedCredentials = codecLoop(
184
+ createAuthMessage(
185
+ keyring,
186
+ partyKey,
187
+ identityKeyRecord,
188
+ identityKeyRecord,
189
+ newFeedKeyRecord,
190
+ undefined,
191
+ feedAdmit
192
+ )
193
+ );
194
+
195
+ const ok = await auth.authenticate(wrappedCredentials.payload);
196
+ expect(ok).toBe(true);
197
+
198
+ expect(called).toEqual(true);
199
+ });
200
+
201
+ it('good chain', async () => {
202
+ const { keyring, partyKey } = await createPartyKeyrings();
203
+ const party = new PartyState(partyKey);
204
+ const auth = new PartyAuthenticator(party, async () => {});
205
+
206
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
207
+ const deviceKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.DEVICE }));
208
+ const secondDeviceKeyRecord = await keyring.createKeyRecord({ type: KeyType.DEVICE });
209
+
210
+ const messages = [
211
+ createPartyGenesisMessage(keyring,
212
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
213
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
214
+ identityKeyRecord!
215
+ )
216
+ ];
217
+
218
+ // Only add the Identity to the party keyring.
219
+ await party.processMessages(messages);
220
+
221
+ const wrappedCredentials = codecLoop(
222
+ createAuthMessage(
223
+ keyring,
224
+ partyKey,
225
+ identityKeyRecord!,
226
+ getIdentityKeyChainForDevice(
227
+ keyring,
228
+ secondDeviceKeyRecord.publicKey,
229
+ messageMap([
230
+ ...messages,
231
+ createKeyAdmitMessage(keyring, partyKey, deviceKeyRecord!, [identityKeyRecord!]),
232
+ createKeyAdmitMessage(keyring, partyKey, secondDeviceKeyRecord, [deviceKeyRecord!])
233
+ ]
234
+ )
235
+ )
236
+ )
237
+ );
238
+
239
+ const ok = await auth.authenticate(wrappedCredentials.payload);
240
+ expect(ok).toBe(true);
241
+ });
242
+
243
+ it('bad chain', async () => {
244
+ const { keyring, partyKey } = await createPartyKeyrings();
245
+ const party = new PartyState(partyKey);
246
+ const auth = new PartyAuthenticator(party, async () => {});
247
+
248
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
249
+ const deviceKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.DEVICE }));
250
+ const secondDeviceKeyRecord = await keyring.createKeyRecord({ type: KeyType.DEVICE });
251
+
252
+ const messages = [
253
+ createPartyGenesisMessage(keyring,
254
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
255
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
256
+ identityKeyRecord!
257
+ )
258
+ ];
259
+
260
+ // Only add the Identity to the party keyring.
261
+ await party.processMessages(messages);
262
+
263
+ // A bad chain, it doesn't track back to the Identity.
264
+ const chain = getIdentityKeyChainForDevice(
265
+ keyring,
266
+ secondDeviceKeyRecord.publicKey,
267
+ messageMap([
268
+ ...messages,
269
+ createKeyAdmitMessage(keyring, partyKey, deviceKeyRecord!, [deviceKeyRecord!]),
270
+ createKeyAdmitMessage(keyring, partyKey, secondDeviceKeyRecord, [deviceKeyRecord!])
271
+ ]
272
+ )
273
+ );
274
+
275
+ const wrappedCredentials = codecLoop(
276
+ createAuthMessage(
277
+ keyring,
278
+ partyKey,
279
+ identityKeyRecord!,
280
+ chain
281
+ )
282
+ );
283
+
284
+ const ok = await auth.authenticate(wrappedCredentials.payload);
285
+ expect(ok).toBe(false);
286
+ });
287
+
288
+ // TODO(dboreham): This test isn't discriminating errors because when I broke the code entirely it still passed.
289
+ it('wrong key', async () => {
290
+ const { keyring, partyKey } = await createPartyKeyrings();
291
+ const party = new PartyState(partyKey);
292
+ const auth = new PartyAuthenticator(party, async () => {});
293
+ const wrongKey = await keyring.createKeyRecord();
294
+
295
+ const messages = [
296
+ createPartyGenesisMessage(keyring,
297
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
298
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
299
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }))!
300
+ )
301
+ ];
302
+ await party.processMessages(messages);
303
+
304
+ const wrappedCredentials = codecLoop(
305
+ createAuthMessage(
306
+ keyring,
307
+ partyKey,
308
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }))!,
309
+ wrongKey
310
+ )
311
+ );
312
+
313
+ const ok = await auth.authenticate(wrappedCredentials.payload);
314
+ expect(ok).toBe(false);
315
+ });
316
+
317
+ it('wrong party', async () => {
318
+ const { keyring, partyKey } = await createPartyKeyrings();
319
+ const party = new PartyState(partyKey);
320
+ const auth = new PartyAuthenticator(party, async () => {});
321
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
322
+
323
+ const messages = [
324
+ createPartyGenesisMessage(keyring,
325
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
326
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
327
+ identityKeyRecord!
328
+ )
329
+ ];
330
+ await party.processMessages(messages);
331
+
332
+ const wrappedCredentials = codecLoop(
333
+ createAuthMessage(
334
+ keyring,
335
+ randomBytes(32),
336
+ identityKeyRecord!,
337
+ identityKeyRecord!
338
+ )
339
+ );
340
+
341
+ const ok = await auth.authenticate(wrappedCredentials.payload);
342
+ expect(ok).toBe(false);
343
+ });
344
+
345
+ it('missing deviceKey', async () => {
346
+ const { keyring, partyKey } = await createPartyKeyrings();
347
+ const party = new PartyState(partyKey);
348
+ const auth = new PartyAuthenticator(party, async () => {});
349
+
350
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
351
+
352
+ const messages = [
353
+ createPartyGenesisMessage(keyring,
354
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
355
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
356
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }))!
357
+ )
358
+ ];
359
+ await party.processMessages(messages);
360
+
361
+ const wrappedCredentials = {
362
+ '@type': 'dxos.credentials.Message',
363
+ payload:
364
+ keyring.sign({
365
+ '@type': 'dxos.credentials.auth.Auth',
366
+ partyKey,
367
+ identityKey: identityKeyRecord!.publicKey
368
+ }, [identityKeyRecord!])
369
+ };
370
+
371
+ const ok = await auth.authenticate(wrappedCredentials.payload);
372
+ expect(ok).toBe(false);
373
+ });
374
+
375
+ it('tampered message', async () => {
376
+ const { keyring, partyKey } = await createPartyKeyrings();
377
+ const party = new PartyState(partyKey);
378
+ const auth = new PartyAuthenticator(party, async () => {});
379
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
380
+
381
+ const messages = [
382
+ createPartyGenesisMessage(keyring,
383
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
384
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
385
+ identityKeyRecord!
386
+ )
387
+ ];
388
+
389
+ await party.processMessages(messages);
390
+
391
+ const wrappedCredentials = codecLoop(
392
+ createAuthMessage(
393
+ keyring,
394
+ partyKey,
395
+ identityKeyRecord!,
396
+ identityKeyRecord!
397
+ )
398
+ );
399
+
400
+ const before = await auth.authenticate(wrappedCredentials.payload);
401
+ expect(before).toBe(true);
402
+
403
+ wrappedCredentials.payload.signed.payload.deviceKey = randomBytes(32);
404
+
405
+ const after = await auth.authenticate(wrappedCredentials.payload);
406
+ expect(after).toBe(false);
407
+ });
408
+
409
+ it('tampered signature', async () => {
410
+ const { keyring, partyKey } = await createPartyKeyrings();
411
+ const party = new PartyState(partyKey);
412
+ const auth = new PartyAuthenticator(party, async () => {});
413
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
414
+
415
+ const messages = [
416
+ createPartyGenesisMessage(keyring,
417
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
418
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
419
+ identityKeyRecord!
420
+ )
421
+ ];
422
+
423
+ await party.processMessages(messages);
424
+
425
+ const wrappedCredentials = codecLoop(
426
+ createAuthMessage(
427
+ keyring,
428
+ partyKey,
429
+ identityKeyRecord!,
430
+ identityKeyRecord!
431
+ )
432
+ );
433
+
434
+ const before = await auth.authenticate(wrappedCredentials.payload);
435
+ expect(before).toBe(true);
436
+
437
+ wrappedCredentials.payload.signatures[0].signature = randomBytes(64);
438
+
439
+ const after = await auth.authenticate(wrappedCredentials.payload);
440
+ expect(after).toBe(false);
441
+ });
442
+
443
+ it('signature too old', async () => {
444
+ const { keyring, partyKey } = await createPartyKeyrings();
445
+ const party = new PartyState(partyKey);
446
+ const auth = new PartyAuthenticator(party, async () => {});
447
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
448
+ const deviceKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.DEVICE }));
449
+
450
+ const messages = [
451
+ createPartyGenesisMessage(keyring,
452
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
453
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
454
+ identityKeyRecord!
455
+ )
456
+ ];
457
+
458
+ await party.processMessages(messages);
459
+
460
+ const wrappedCredentials = {
461
+ '@type': 'dxos.credentials.Message',
462
+ payload:
463
+ keyring.sign({
464
+ '@type': 'dxos.credentials.auth.Auth',
465
+ partyKey,
466
+ identityKey: identityKeyRecord!.publicKey,
467
+ deviceKey: deviceKeyRecord!.publicKey
468
+ },
469
+ [identityKeyRecord!],
470
+ undefined,
471
+ moment().subtract(2, 'days').format('YYYY-MM-DDTHH:mm:ssZ')
472
+ )
473
+ };
474
+
475
+ const ok = await auth.authenticate(wrappedCredentials.payload);
476
+ expect(ok).toBe(false);
477
+ });
478
+
479
+ it('signature too far in future', async () => {
480
+ const { keyring, partyKey } = await createPartyKeyrings();
481
+ const party = new PartyState(partyKey);
482
+ const auth = new PartyAuthenticator(party, async () => {});
483
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
484
+ const deviceKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.DEVICE }));
485
+
486
+ const messages = [
487
+ createPartyGenesisMessage(keyring,
488
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
489
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
490
+ identityKeyRecord!
491
+ )
492
+ ];
493
+
494
+ await party.processMessages(messages);
495
+
496
+ const wrappedCredentials = {
497
+ '@type': 'dxos.credentials.Message',
498
+ payload:
499
+ keyring.sign({
500
+ '@type': 'dxos.credentials.auth.Auth',
501
+ partyKey,
502
+ identityKey: identityKeyRecord!.publicKey,
503
+ deviceKey: deviceKeyRecord!.publicKey
504
+ },
505
+ [identityKeyRecord!],
506
+ undefined,
507
+ moment().add(2, 'days').format('YYYY-MM-DDTHH:mm:ssZ')
508
+ )
509
+ };
510
+
511
+ const ok = await auth.authenticate(wrappedCredentials.payload);
512
+ expect(ok).toBe(false);
513
+ });
514
+
515
+ it('signature date invalid', async () => {
516
+ const { keyring, partyKey } = await createPartyKeyrings();
517
+ const party = new PartyState(partyKey);
518
+ const auth = new PartyAuthenticator(party, async () => {});
519
+ const identityKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.IDENTITY }));
520
+ const deviceKeyRecord = keyring.findKey(Keyring.signingFilter({ type: KeyType.DEVICE }));
521
+
522
+ const messages = [
523
+ createPartyGenesisMessage(keyring,
524
+ keyring.findKey(Filter.matches({ type: KeyType.PARTY }))!,
525
+ keyring.findKey(Keyring.signingFilter({ type: KeyType.FEED }))!.publicKey,
526
+ identityKeyRecord!
527
+ )
528
+ ];
529
+
530
+ await party.processMessages(messages);
531
+
532
+ const wrappedCredentials = {
533
+ '@type': 'dxos.credentials.Message',
534
+ payload:
535
+ keyring.sign({
536
+ '@type': 'dxos.credentials.auth.Auth',
537
+ partyKey,
538
+ identityKey: identityKeyRecord!.publicKey,
539
+ deviceKey: deviceKeyRecord!.publicKey
540
+ },
541
+ [identityKeyRecord!],
542
+ undefined,
543
+ 'INVALID'
544
+ )
545
+ };
546
+
547
+ const ok = await auth.authenticate(wrappedCredentials.payload);
548
+ expect(ok).toBe(false);
549
+ });
550
+
551
+ });
@@ -2,65 +2,44 @@
2
2
  // Copyright 2019 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'assert';
6
5
  import debug from 'debug';
7
6
  import moment from 'moment';
8
7
 
9
8
  import { PublicKey } from '@dxos/crypto';
10
9
 
11
- import { Keyring } from '../keys';
12
10
  import { isSignedMessage, PartyState } from '../party';
13
- import { codec, KeyType, Message } from '../proto';
11
+ import { Auth, SignedMessage } from '../proto';
14
12
 
15
13
  const log = debug('dxos:halo:auth');
16
14
 
17
15
  const MAX_AGE = 24 * 60 * 60; // One day.
18
16
 
19
17
  /**
20
- * Abstract base class for Authenticators.
18
+ * Interface for Authenticators.
21
19
  * Used by AuthPlugin for authenticating nodes during handshake.
22
20
  */
23
- // TODO(telackey): Explain here the intention behind the abstract base class:
21
+ // TODO(telackey): Explain here the intention behind the interface:
24
22
  // E.g., to have different authentication methods (besides the current PartyAuthenticator) for replication auth,
25
23
  // or to use this base class everywhere auth is done in the project (not used in greeting at present, for example)?
26
- export abstract class Authenticator {
27
- // TODO(dboreham): The following static methods:
28
- // temporary work around move encapsualtion breaking code from `data-client/partitions.js`.
29
-
30
- static encodePayload (credentials: Message) {
31
- return codec.encode(credentials);
32
- }
33
-
34
- static decodePayload (credentials: Buffer) {
35
- return codec.decode(credentials);
36
- }
37
-
24
+ export interface Authenticator {
38
25
  /**
39
26
  * Return true if the credentials checkout, else false.
40
- * @param credentials
41
- * @returns {Promise<boolean>}
42
27
  */
43
- async authenticate(credentials: any): Promise<boolean> { // eslint-disable-line
44
- throw new Error('Not Implemented');
45
- }
28
+ authenticate(credentials: SignedMessage): Promise<boolean>
46
29
  }
47
30
 
48
31
  /**
49
32
  * A Party-based Authenticator, which checks that the supplied credentials belong to a Party member.
50
33
  */
51
- export class PartyAuthenticator extends Authenticator {
52
- _party: PartyState;
34
+ export class PartyAuthenticator implements Authenticator {
53
35
 
54
36
  /**
55
37
  * Takes the target Party for checking admitted keys and verifying signatures.
56
- * @param party
57
38
  */
58
- constructor (party: PartyState) {
59
- assert(party);
60
- super();
61
-
62
- this._party = party;
63
- }
39
+ constructor (
40
+ private readonly _party: PartyState,
41
+ private readonly _onAuthenticated?: (auth: Auth) => Promise<void>
42
+ ) {}
64
43
 
65
44
  /**
66
45
  * Authenticate the credentials presented during handshake. The signature on the credentials must be valid and belong
@@ -70,14 +49,14 @@ export class PartyAuthenticator extends Authenticator {
70
49
  */
71
50
  // TODO(dboreham): Verify that credentials is a message of type `dxos.credentials.SignedMessage`
72
51
  // signing a message of type `dxos.credentials.auth.Auth`.
73
- override async authenticate (credentials: any) {
52
+ async authenticate (credentials: SignedMessage): Promise<boolean> {
74
53
  if (!credentials || !isSignedMessage(credentials)) {
75
54
  log('Bad credentials:', credentials);
76
55
  return false;
77
56
  }
78
57
 
79
- const { created, payload } = credentials.signed || {};
80
- const { deviceKey, identityKey, partyKey, feedKey } = payload || {};
58
+ const { created, payload: auth = {} } = credentials.signed || {};
59
+ const { deviceKey, identityKey, partyKey } = auth;
81
60
  if (!created || !PublicKey.isPublicKey(deviceKey) || !PublicKey.isPublicKey(identityKey) || !PublicKey.isPublicKey(partyKey)) {
82
61
  log('Bad credentials:', credentials);
83
62
  return false;
@@ -109,14 +88,14 @@ export class PartyAuthenticator extends Authenticator {
109
88
 
110
89
  const verified = this._party.verifySignatures(credentials);
111
90
 
112
- // TODO(telackey): Find a better place to do this, since doing it here could be considered a side-effect.
113
- if (verified &&
114
- PublicKey.isPublicKey(feedKey) &&
115
- Keyring.signingKeys(credentials, { deep: false, validate: false }).find(key => key.equals(feedKey)) &&
116
- !this._party.memberFeeds.find(partyFeed => partyFeed.equals(feedKey))) {
117
- log(`Auto-hinting feedKey: ${feedKey.toHex()} for Device ` +
118
- `${deviceKey.toHex()} on Identity ${identityKey.toHex()}`);
119
- await this._party.takeHints([{ publicKey: feedKey, type: KeyType.FEED }]);
91
+ if (
92
+ verified
93
+ // feedAdmit &&
94
+ // PublicKey.isPublicKey(feedKey) &&
95
+ // !this._party.memberFeeds.find(partyFeed => partyFeed.equals(feedKey))
96
+ ) {
97
+ log(`Member authenticated: deviceKey=${deviceKey}, identityKey=${identityKey}`);
98
+ await this._onAuthenticated?.(auth);
120
99
  }
121
100
 
122
101
  return verified;
@@ -123,7 +123,7 @@ it('Greeting Flow using GreetingCommandPlugin', async () => {
123
123
  // Present the invitation (by showing up).
124
124
  {
125
125
  const command = {
126
- __type_url: 'dxos.credentials.greet.Command',
126
+ '@type': 'dxos.credentials.greet.Command',
127
127
  command: Command.Type.BEGIN
128
128
  };
129
129
 
@@ -133,7 +133,7 @@ it('Greeting Flow using GreetingCommandPlugin', async () => {
133
133
  // Obtain the nonce and partyKey from the HANDSHAKE response.
134
134
  const { nonce, partyKey } = await (async () => {
135
135
  const command = {
136
- __type_url: 'dxos.credentials.greet.Command',
136
+ '@type': 'dxos.credentials.greet.Command',
137
137
  command: Command.Type.HANDSHAKE,
138
138
  secret: await secretProvider(),
139
139
  params: []
@@ -149,7 +149,7 @@ it('Greeting Flow using GreetingCommandPlugin', async () => {
149
149
  const identityKey = await keyring.createKeyRecord({ type: KeyType.IDENTITY });
150
150
 
151
151
  const command = {
152
- __type_url: 'dxos.credentials.greet.Command',
152
+ '@type': 'dxos.credentials.greet.Command',
153
153
  command: Command.Type.NOTARIZE,
154
154
  secret: await secretProvider(),
155
155
  params: [
@@ -173,7 +173,7 @@ it('Greeting Flow using GreetingCommandPlugin', async () => {
173
173
 
174
174
  const oneway = true;
175
175
  await plugin.send(rendezvousKey, {
176
- __type_url: 'dxos.credentials.greet.Command',
176
+ '@type': 'dxos.credentials.greet.Command',
177
177
  command: Command.Type.FINISH,
178
178
  secret: await secretProvider(),
179
179
  params: []