Skip to content
This repository was archived by the owner on Dec 21, 2021. It is now read-only.

Commit ad2db70

Browse files
authored
Merge pull request #205 from streamr-dev/NET-208-revocation
Read/write streamMessage.newGroupKey & enable key revocation
2 parents 7cb3912 + e229b81 commit ad2db70

File tree

12 files changed

+670
-98
lines changed

12 files changed

+670
-98
lines changed

package-lock.json

Lines changed: 23 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/StreamrClient.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { BigNumber } from '@ethersproject/bignumber'
2323
import { getAddress } from '@ethersproject/address'
2424
import { Contract } from '@ethersproject/contracts'
2525
import { StreamPartDefinition } from './stream'
26+
import type { GroupKey } from './stream/Encryption'
2627

2728
// TODO get metadata type from streamr-protocol-js project (it doesn't export the type definitions yet)
2829
export type OnMessageCallback = MaybeAsync<(message: any, metadata: any) => void>
@@ -160,7 +161,7 @@ function Plugin(targetInstance: any, srcInstance: any) {
160161
}
161162

162163
// these are mixed in via Plugin function above
163-
export interface StreamrClient extends StreamEndpoints, LoginEndpoints {}
164+
export interface StreamrClient extends StreamEndpoints, LoginEndpoints, ReturnType<typeof Publisher>, Subscriber {}
164165

165166
/**
166167
* @category Important
@@ -340,12 +341,16 @@ export class StreamrClient extends EventEmitter { // eslint-disable-line no-rede
340341
return getUserId(this)
341342
}
342343

343-
setNextGroupKey(...args: Todo) {
344-
return this.publisher.setNextGroupKey(...args)
344+
setNextGroupKey(streamId: string, newKey: GroupKey) {
345+
return this.publisher.setNextGroupKey(streamId, newKey)
345346
}
346347

347-
rotateGroupKey(...args: Todo) {
348-
return this.publisher.rotateGroupKey(...args)
348+
rotateGroupKey(streamId: string) {
349+
return this.publisher.rotateGroupKey(streamId)
350+
}
351+
352+
rekey(streamId: string) {
353+
return this.publisher.rekey(streamId)
349354
}
350355

351356
/**

src/publish/Encrypt.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ export default function Encrypt(client: StreamrClient) {
3939
if (streamMessage.messageType !== StreamMessage.MESSAGE_TYPES.MESSAGE) {
4040
return
4141
}
42-
const groupKey = await getPublisherKeyExchange().useGroupKey(stream.id)
43-
await EncryptionUtil.encryptStreamMessage(streamMessage, groupKey)
42+
43+
const [groupKey, nextGroupKey] = await getPublisherKeyExchange().useGroupKey(stream.id)
44+
await EncryptionUtil.encryptStreamMessage(streamMessage, groupKey, nextGroupKey)
4445
}
4546

4647
return Object.assign(encrypt, {
@@ -50,6 +51,9 @@ export default function Encrypt(client: StreamrClient) {
5051
rotateGroupKey(...args: Parameters<PublisherKeyExhangeAPI['rotateGroupKey']>) {
5152
return getPublisherKeyExchange().rotateGroupKey(...args)
5253
},
54+
rekey(...args: Parameters<PublisherKeyExhangeAPI['rekey']>) {
55+
return getPublisherKeyExchange().rekey(...args)
56+
},
5357
start() {
5458
return getPublisherKeyExchange().start()
5559
},

src/publish/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ function getCreateStreamMessage(client) {
189189
rotateGroupKey(maybeStreamId) {
190190
return encrypt.rotateGroupKey(maybeStreamId)
191191
},
192+
rekey(maybeStreamId) {
193+
return encrypt.rekey(maybeStreamId)
194+
},
192195
startKeyExchange() {
193196
return encrypt.start()
194197
},
@@ -315,6 +318,9 @@ export default function Publisher(client) {
315318
},
316319
setNextGroupKey(streamId, newKey) {
317320
return createStreamMessage.setNextGroupKey(streamId, newKey)
321+
},
322+
rekey(streamId) {
323+
return createStreamMessage.rekey(streamId)
318324
}
319325
}
320326
}

src/stream/KeyExchange.js

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function GroupKeyStore({ groupKeys = new Map() }) {
6565
})
6666

6767
let currentGroupKeyId // current key id if any
68-
let nextGroupKey // key to use next, disappears if not actually used.
68+
const nextGroupKeys = [] // the keys to use next, disappears if not actually used. Max queue size 2
6969

7070
store.forEach((groupKey) => {
7171
GroupKey.validate(GroupKey.from(groupKey))
@@ -79,7 +79,8 @@ function GroupKeyStore({ groupKeys = new Map() }) {
7979
const existingKey = GroupKey.from(store.get(groupKey.id))
8080
if (!existingKey.equals(groupKey)) {
8181
throw new GroupKey.InvalidGroupKeyError(
82-
`Trying to add groupKey ${groupKey.id} but key exists & is not equivalent to new GroupKey: ${groupKey}.`
82+
`Trying to add groupKey ${groupKey.id} but key exists & is not equivalent to new GroupKey: ${groupKey}.`,
83+
groupKey
8384
)
8485
}
8586

@@ -96,29 +97,49 @@ function GroupKeyStore({ groupKeys = new Map() }) {
9697
has(groupKeyId) {
9798
if (currentGroupKeyId === groupKeyId) { return true }
9899

99-
if (nextGroupKey && nextGroupKey.id === groupKeyId) { return true }
100+
if (nextGroupKeys.some((nextKey) => nextKey.id === groupKeyId)) { return true }
100101

101102
return store.has(groupKeyId)
102103
},
103104
isEmpty() {
104-
return !nextGroupKey && store.size === 0
105+
return nextGroupKeys.length === 0 && store.size === 0
105106
},
106107
useGroupKey() {
107-
if (nextGroupKey) {
108-
// next key becomes current key
109-
storeKey(nextGroupKey)
110-
111-
currentGroupKeyId = nextGroupKey.id
112-
nextGroupKey = undefined
113-
}
114-
115-
if (!currentGroupKeyId) {
116-
// generate & use key if none already set
117-
this.rotateGroupKey()
118-
return this.useGroupKey()
108+
const nextGroupKey = nextGroupKeys.pop()
109+
switch (true) {
110+
// First use of group key on this stream, no current key. Make next key current.
111+
case !!(!currentGroupKeyId && nextGroupKey): {
112+
storeKey(nextGroupKey)
113+
currentGroupKeyId = nextGroupKey.id
114+
return [
115+
this.get(currentGroupKeyId),
116+
undefined,
117+
]
118+
}
119+
// Keep using current key (empty next)
120+
case !!(currentGroupKeyId && !nextGroupKey): {
121+
return [
122+
this.get(currentGroupKeyId),
123+
undefined
124+
]
125+
}
126+
// Key changed (non-empty next). return current + next. Make next key current.
127+
case !!(currentGroupKeyId && nextGroupKey): {
128+
storeKey(nextGroupKey)
129+
const prevGroupKey = this.get(currentGroupKeyId)
130+
currentGroupKeyId = nextGroupKey.id
131+
// use current key one more time
132+
return [
133+
prevGroupKey,
134+
nextGroupKey,
135+
]
136+
}
137+
// Generate & use new key if none already set.
138+
default: {
139+
this.rotateGroupKey()
140+
return this.useGroupKey()
141+
}
119142
}
120-
121-
return this.get(currentGroupKeyId)
122143
},
123144
get(groupKeyId) {
124145
const groupKey = store.get(groupKeyId)
@@ -127,7 +148,7 @@ function GroupKeyStore({ groupKeys = new Map() }) {
127148
},
128149
clear() {
129150
currentGroupKeyId = undefined
130-
nextGroupKey = undefined
151+
nextGroupKeys.length = 0
131152
return store.clear()
132153
},
133154
rotateGroupKey() {
@@ -138,7 +159,14 @@ function GroupKeyStore({ groupKeys = new Map() }) {
138159
},
139160
setNextGroupKey(newKey) {
140161
GroupKey.validate(newKey)
141-
nextGroupKey = newKey
162+
nextGroupKeys.unshift(newKey)
163+
nextGroupKeys.length = Math.min(nextGroupKeys.length, 2)
164+
},
165+
rekey() {
166+
const newKey = GroupKey.generate()
167+
storeKey(newKey)
168+
currentGroupKeyId = newKey.id
169+
nextGroupKeys.length = 0
142170
}
143171
}
144172
}
@@ -215,7 +243,8 @@ async function PublisherKeyExhangeSubscription(client, getGroupKeyStore) {
215243
const subscriberId = streamMessage.getPublisherId()
216244

217245
const groupKeyStore = getGroupKeyStore(streamId)
218-
const encryptedGroupKeys = groupKeyIds.map((id) => {
246+
const isSubscriber = await client.isStreamSubscriber(streamId, subscriberId)
247+
const encryptedGroupKeys = !isSubscriber ? [] : groupKeyIds.map((id) => {
219248
const groupKey = groupKeyStore.get(id)
220249
if (!groupKey) {
221250
return null // will be filtered out
@@ -316,9 +345,17 @@ export function PublisherKeyExhange(client, { groupKeys = {} } = {}) {
316345
return !groupKeyStore.isEmpty()
317346
}
318347

348+
async function rekey(streamId) {
349+
if (!enabled) { return }
350+
const groupKeyStore = getGroupKeyStore(streamId)
351+
groupKeyStore.rekey()
352+
await next()
353+
}
354+
319355
return {
320356
setNextGroupKey,
321357
useGroupKey,
358+
rekey,
322359
rotateGroupKey,
323360
hasAnyGroupKey,
324361
async start() {
@@ -341,7 +378,7 @@ async function getGroupKeysFromStreamMessage(streamMessage, encryptionUtil) {
341378

342379
async function SubscriberKeyExhangeSubscription(client, getGroupKeyStore, encryptionUtil) {
343380
let sub
344-
async function onKeyExchangeMessage(parsedContent, streamMessage) {
381+
async function onKeyExchangeMessage(_parsedContent, streamMessage) {
345382
try {
346383
const { messageType } = streamMessage
347384
const { MESSAGE_TYPES } = StreamMessage
@@ -537,9 +574,9 @@ export function SubscriberKeyExchange(client, { groupKeys = {} } = {}) {
537574
})
538575

539576
async function getGroupKey(streamMessage) {
540-
if (!streamMessage.groupKeyId) { return undefined }
577+
if (!streamMessage.groupKeyId) { return [] }
541578
await next()
542-
if (!enabled) { return undefined }
579+
if (!enabled) { return [] }
543580

544581
return getKey(streamMessage)
545582
}
@@ -549,6 +586,12 @@ export function SubscriberKeyExchange(client, { groupKeys = {} } = {}) {
549586
enabled = true
550587
return next()
551588
},
589+
addNewKey(streamMessage) {
590+
if (!streamMessage.newGroupKey) { return }
591+
const streamId = streamMessage.getStreamId()
592+
const groupKeyStore = getGroupKeyStore(streamId)
593+
groupKeyStore.add(streamMessage.newGroupKey)
594+
},
552595
async stop() {
553596
enabled = false
554597
return next()

src/subscribe/Decrypt.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default function Decrypt(client, options = {}) {
4646
throw new UnableToDecryptError(`Group key not found: ${streamMessage.groupKeyId}`, streamMessage)
4747
}
4848
await EncryptionUtil.decryptStreamMessage(streamMessage, groupKey)
49+
requestKey.addNewKey(streamMessage)
4950
} catch (err) {
5051
await onError(err, streamMessage)
5152
} finally {

0 commit comments

Comments
 (0)