This repository was archived by the owner on Dec 21, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
Read/write streamMessage.newGroupKey & enable key revocation #205
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
b74b748
feat(encryption): Support reading & writing newGroupKey. Delays actua…
timoxley da847fe
feat(encryption): Add client.rekey(streamId). Test revocation works.
timoxley c4cfb2d
Add failing multiple publishers + late subscriber test.
timoxley 9f849e8
test: Passing integration/Encryption.test.js with revoke tests.
timoxley 6e52f77
Linting.
timoxley 5b3224a
Fixing MultipleClients test. WIP.
timoxley f2c7386
types(subscribe): Fix subscription & stream validateOptions types.
timoxley 7c7c7d1
refactor(test): Tidy MultipleClients late sub test.
timoxley 0f30ab2
types(client): Explicitly type top-level groupkey handling methods.
timoxley 3d7602a
refactor(keyexchange): Tidy up useGroupKey with switch instead of mul…
timoxley 7a8ed9f
test(multipleclients): Fix late sub test.
timoxley b035419
fix(keyexchange): Fix bad switch case boolean.
timoxley 793fb54
test(keyexchange): Add test repeats & reduce test timeout for Encrypt…
timoxley e229b81
test: Make MultipleClients test more reliable.
timoxley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,7 +65,7 @@ function GroupKeyStore({ groupKeys = new Map() }) { | |
| }) | ||
|
|
||
| let currentGroupKeyId // current key id if any | ||
| let nextGroupKey // key to use next, disappears if not actually used. | ||
| const nextGroupKeys = [] // the keys to use next, disappears if not actually used. Max queue size 2 | ||
|
|
||
| store.forEach((groupKey) => { | ||
| GroupKey.validate(GroupKey.from(groupKey)) | ||
|
|
@@ -79,7 +79,8 @@ function GroupKeyStore({ groupKeys = new Map() }) { | |
| const existingKey = GroupKey.from(store.get(groupKey.id)) | ||
| if (!existingKey.equals(groupKey)) { | ||
| throw new GroupKey.InvalidGroupKeyError( | ||
| `Trying to add groupKey ${groupKey.id} but key exists & is not equivalent to new GroupKey: ${groupKey}.` | ||
| `Trying to add groupKey ${groupKey.id} but key exists & is not equivalent to new GroupKey: ${groupKey}.`, | ||
| groupKey | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -96,29 +97,49 @@ function GroupKeyStore({ groupKeys = new Map() }) { | |
| has(groupKeyId) { | ||
| if (currentGroupKeyId === groupKeyId) { return true } | ||
|
|
||
| if (nextGroupKey && nextGroupKey.id === groupKeyId) { return true } | ||
| if (nextGroupKeys.some((nextKey) => nextKey.id === groupKeyId)) { return true } | ||
|
|
||
| return store.has(groupKeyId) | ||
| }, | ||
| isEmpty() { | ||
| return !nextGroupKey && store.size === 0 | ||
| return nextGroupKeys.length === 0 && store.size === 0 | ||
| }, | ||
| useGroupKey() { | ||
| if (nextGroupKey) { | ||
| // next key becomes current key | ||
| storeKey(nextGroupKey) | ||
|
|
||
| currentGroupKeyId = nextGroupKey.id | ||
| nextGroupKey = undefined | ||
| } | ||
|
|
||
| if (!currentGroupKeyId) { | ||
| // generate & use key if none already set | ||
| this.rotateGroupKey() | ||
| return this.useGroupKey() | ||
| const nextGroupKey = nextGroupKeys.pop() | ||
| switch (true) { | ||
| // First use of group key on this stream, no current key. Make next key current. | ||
| case !!(!currentGroupKeyId && nextGroupKey): { | ||
| storeKey(nextGroupKey) | ||
| currentGroupKeyId = nextGroupKey.id | ||
| return [ | ||
| this.get(currentGroupKeyId), | ||
| undefined, | ||
| ] | ||
| } | ||
| // Keep using current key (empty next) | ||
| case !!(currentGroupKeyId && !nextGroupKey): { | ||
| return [ | ||
| this.get(currentGroupKeyId), | ||
| undefined | ||
| ] | ||
| } | ||
| // Key changed (non-empty next). return current + next. Make next key current. | ||
| case !!(currentGroupKeyId && nextGroupKey): { | ||
| storeKey(nextGroupKey) | ||
| const prevGroupKey = this.get(currentGroupKeyId) | ||
| currentGroupKeyId = nextGroupKey.id | ||
| // use current key one more time | ||
| return [ | ||
| prevGroupKey, | ||
| nextGroupKey, | ||
| ] | ||
| } | ||
| // Generate & use new key if none already set. | ||
| default: { | ||
| this.rotateGroupKey() | ||
| return this.useGroupKey() | ||
| } | ||
| } | ||
|
|
||
| return this.get(currentGroupKeyId) | ||
| }, | ||
| get(groupKeyId) { | ||
| const groupKey = store.get(groupKeyId) | ||
|
|
@@ -127,7 +148,7 @@ function GroupKeyStore({ groupKeys = new Map() }) { | |
| }, | ||
| clear() { | ||
| currentGroupKeyId = undefined | ||
| nextGroupKey = undefined | ||
| nextGroupKeys.length = 0 | ||
timoxley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return store.clear() | ||
| }, | ||
| rotateGroupKey() { | ||
|
|
@@ -138,7 +159,14 @@ function GroupKeyStore({ groupKeys = new Map() }) { | |
| }, | ||
| setNextGroupKey(newKey) { | ||
| GroupKey.validate(newKey) | ||
| nextGroupKey = newKey | ||
| nextGroupKeys.unshift(newKey) | ||
| nextGroupKeys.length = Math.min(nextGroupKeys.length, 2) | ||
| }, | ||
| rekey() { | ||
| const newKey = GroupKey.generate() | ||
| storeKey(newKey) | ||
| currentGroupKeyId = newKey.id | ||
| nextGroupKeys.length = 0 | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -215,7 +243,8 @@ async function PublisherKeyExhangeSubscription(client, getGroupKeyStore) { | |
| const subscriberId = streamMessage.getPublisherId() | ||
|
|
||
| const groupKeyStore = getGroupKeyStore(streamId) | ||
| const encryptedGroupKeys = groupKeyIds.map((id) => { | ||
| const isSubscriber = await client.isStreamSubscriber(streamId, subscriberId) | ||
| const encryptedGroupKeys = !isSubscriber ? [] : groupKeyIds.map((id) => { | ||
| const groupKey = groupKeyStore.get(id) | ||
| if (!groupKey) { | ||
| return null // will be filtered out | ||
|
|
@@ -316,9 +345,17 @@ export function PublisherKeyExhange(client, { groupKeys = {} } = {}) { | |
| return !groupKeyStore.isEmpty() | ||
| } | ||
|
|
||
| async function rekey(streamId) { | ||
| if (!enabled) { return } | ||
| const groupKeyStore = getGroupKeyStore(streamId) | ||
| groupKeyStore.rekey() | ||
| await next() | ||
| } | ||
|
|
||
| return { | ||
| setNextGroupKey, | ||
| useGroupKey, | ||
| rekey, | ||
| rotateGroupKey, | ||
| hasAnyGroupKey, | ||
| async start() { | ||
|
|
@@ -341,7 +378,7 @@ async function getGroupKeysFromStreamMessage(streamMessage, encryptionUtil) { | |
|
|
||
| async function SubscriberKeyExhangeSubscription(client, getGroupKeyStore, encryptionUtil) { | ||
| let sub | ||
| async function onKeyExchangeMessage(parsedContent, streamMessage) { | ||
| async function onKeyExchangeMessage(_parsedContent, streamMessage) { | ||
| try { | ||
| const { messageType } = streamMessage | ||
| const { MESSAGE_TYPES } = StreamMessage | ||
|
|
@@ -537,9 +574,9 @@ export function SubscriberKeyExchange(client, { groupKeys = {} } = {}) { | |
| }) | ||
|
|
||
| async function getGroupKey(streamMessage) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (!streamMessage.groupKeyId) { return undefined } | ||
| if (!streamMessage.groupKeyId) { return [] } | ||
| await next() | ||
| if (!enabled) { return undefined } | ||
| if (!enabled) { return [] } | ||
|
|
||
| return getKey(streamMessage) | ||
| } | ||
|
|
@@ -549,6 +586,12 @@ export function SubscriberKeyExchange(client, { groupKeys = {} } = {}) { | |
| enabled = true | ||
| return next() | ||
| }, | ||
| addNewKey(streamMessage) { | ||
| if (!streamMessage.newGroupKey) { return } | ||
| const streamId = streamMessage.getStreamId() | ||
| const groupKeyStore = getGroupKeyStore(streamId) | ||
| groupKeyStore.add(streamMessage.newGroupKey) | ||
| }, | ||
| async stop() { | ||
| enabled = false | ||
| return next() | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method could be plural as it generates an array of
GroupKeys?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, sort of, it could probably do with some docs though.
Basically it's giving you the current + (optional) next group key.
If there's two values, it'll encrypt the message with the first, but embed the second in the stream message (encrypted with the first), then the next time you call
useGroupKeyyou will get that second value in the first position, and an empty value second position, until a new group key is assigned. When a new group key is assigned, it'll use the existing key one more time, embedding the next key, then start using the new key from there on.Something like this:
Where
useGroupKeyis called before encrypting each message.Iff there's a second value, it will be set as the
streamMessage.newGroupKeyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On possibility would be to introduce some helper data type, like
GroupKeyQueue. It would encapsulate this e.g. max length handling.