Skip to content

Commit a55758a

Browse files
committed
wait for encryption formats to load before encrypting
1 parent 83a126d commit a55758a

File tree

4 files changed

+114
-100
lines changed

4 files changed

+114
-100
lines changed

core.js

Lines changed: 69 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const {
2626
resetLevelPath,
2727
resetPrivatePath,
2828
} = require('./defaults')
29-
const { onceWhen, ReadyGate } = require('./utils')
29+
const { onceWhen, ReadyGate, onceWhenPromise } = require('./utils')
3030
const DebouncingBatchAdd = require('./debounce-batch')
3131
const Log = require('./log')
3232
const Status = require('./status')
@@ -620,7 +620,7 @@ exports.init = function (sbot, config) {
620620
)
621621
}
622622

623-
function create(opts, cb) {
623+
async function create(opts, cb) {
624624
const guard = guardAgainstDuplicateLogs('create()')
625625
if (guard) return cb(guard)
626626

@@ -637,83 +637,78 @@ exports.init = function (sbot, config) {
637637

638638
if (!opts.content) return cb(new Error('create() requires a `content`'))
639639

640-
onceWhen(
641-
stateFeedsReady,
642-
(ready) => ready === true,
643-
() => {
644-
const provisionalNativeMsg = feedFormat.newNativeMsg({
645-
timestamp: Date.now(),
646-
...opts,
647-
previous: null,
640+
await privateIndex.stateLoaded
641+
await onceWhenPromise(stateFeedsReady, (ready) => ready === true)
642+
643+
// Create full opts:
644+
const provisionalNativeMsg = feedFormat.newNativeMsg({
645+
timestamp: Date.now(),
646+
...opts,
647+
previous: null,
648+
keys,
649+
})
650+
const feedId = feedFormat.getFeedId(provisionalNativeMsg)
651+
const previous = state.getAsKV(feedId, feedFormat)
652+
const fullOpts = { timestamp: Date.now(), ...opts, previous, keys, hmacKey }
653+
654+
// If opts ask for encryption, try encryption formats and pick the best:
655+
const recps = fullOpts.recps || fullOpts.content.recps
656+
if (Array.isArray(recps) && recps.length > 0) {
657+
const plaintext = feedFormat.toPlaintextBuffer(fullOpts)
658+
function encryptWith(encryptionFormat) {
659+
const encryptOpts = {
660+
...fullOpts,
648661
keys,
649-
})
650-
const feedId = feedFormat.getFeedId(provisionalNativeMsg)
651-
const previous = state.getAsKV(feedId, feedFormat)
652-
const fullOpts = { timestamp: Date.now(), ...opts, previous, keys }
653-
654-
// If the inputs require encryption, try some encryption formats,
655-
// and pick the best output.
656-
const recps = fullOpts.recps || fullOpts.content.recps
657-
if (Array.isArray(recps) && recps.length > 0) {
658-
const plaintext = feedFormat.toPlaintextBuffer(fullOpts)
659-
function encryptWith(encryptionFormat) {
660-
const eOpts = {
661-
...fullOpts,
662-
keys,
663-
recps,
664-
previous: previous ? previous.key : null,
665-
}
666-
const ciphertextBuf = encryptionFormat.encrypt(plaintext, eOpts)
667-
return (
668-
ciphertextBuf.toString('base64') + '.' + encryptionFormat.name
669-
)
670-
}
671-
if (fullOpts.encryptionFormat) {
672-
const format = findEncryptionFormatByName(fullOpts.encryptionFormat)
662+
recps,
663+
previous: previous ? previous.key : null,
664+
}
665+
const ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
666+
return ciphertextBuf.toString('base64') + '.' + encryptionFormat.name
667+
}
668+
if (fullOpts.encryptionFormat) {
669+
const format = findEncryptionFormatByName(fullOpts.encryptionFormat)
670+
const ciphertext = encryptWith(format)
671+
fullOpts.content = ciphertext
672+
} else {
673+
const outputs = encryptionFormats.map((format) => {
674+
try {
673675
const ciphertext = encryptWith(format)
674-
fullOpts.content = ciphertext
675-
} else {
676-
const outputs = encryptionFormats.map((format) => {
677-
try {
678-
const ciphertext = encryptWith(format)
679-
if (!ciphertext) return [new Error('encryption failed')]
680-
return [null, { ciphertext, name: format.name }]
681-
} catch (err) {
682-
return [err]
683-
}
684-
})
685-
const successes = outputs
686-
.filter(([err]) => !err)
687-
.map(([, success]) => success)
688-
const errors = outputs
689-
.filter(([err]) => err)
690-
.map(([error]) => error.message)
691-
if (successes.length === 0) {
692-
// prettier-ignore
693-
return cb(new Error('create() failed to encrypt content: ' + errors.join('; ')))
694-
}
695-
fullOpts.content = successes[0].ciphertext
676+
if (!ciphertext) return [new Error('encryption failed')]
677+
return [null, { ciphertext, name: format.name }]
678+
} catch (err) {
679+
return [err]
696680
}
697-
}
698-
699-
fullOpts.hmacKey = hmacKey
700-
const nativeMsg = feedFormat.newNativeMsg(fullOpts)
701-
const msgId = feedFormat.getMsgId(nativeMsg)
702-
const encodedMsg = feedFormat.fromNativeMsg(nativeMsg, encoding)
703-
state.update(feedId, nativeMsg)
704-
705-
log.add(msgId, encodedMsg, feedId, encoding, (err, kvt) => {
706-
if (err) return cb(clarify(err, 'create() failed in the log'))
707-
708-
onMsgAdded.set({
709-
kvt,
710-
nativeMsg: nativeMsg,
711-
feedFormat: feedFormat.name,
712-
})
713-
cb(null, kvt)
714681
})
682+
const successes = outputs
683+
.filter(([err]) => !err)
684+
.map(([, success]) => success)
685+
const errors = outputs
686+
.filter(([err]) => err)
687+
.map(([error]) => error.message)
688+
if (successes.length === 0) {
689+
// prettier-ignore
690+
return cb(new Error('create() failed to encrypt content: ' + errors.join('; ')))
691+
}
692+
fullOpts.content = successes[0].ciphertext
715693
}
716-
)
694+
}
695+
696+
// Create the native message:
697+
const nativeMsg = feedFormat.newNativeMsg(fullOpts)
698+
const msgId = feedFormat.getMsgId(nativeMsg)
699+
const encodedMsg = feedFormat.fromNativeMsg(nativeMsg, encoding)
700+
state.update(feedId, nativeMsg)
701+
702+
// Encode the native message and append it to the log:
703+
log.add(msgId, encodedMsg, feedId, encoding, (err, kvt) => {
704+
if (err) return cb(clarify(err, 'create() failed in the log'))
705+
onMsgAdded.set({
706+
kvt,
707+
nativeMsg: nativeMsg,
708+
feedFormat: feedFormat.name,
709+
})
710+
cb(null, kvt)
711+
})
717712
}
718713

719714
function del(msgId, cb) {

encryption-formats/box2.js

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const { isFeedSSBURI, isBendyButtV1FeedSSBURI } = require('ssb-uri2')
88
const { keySchemes } = require('private-group-spec')
99
const { box, unbox } = require('envelope-js')
1010
const { directMessageKey, SecretKey } = require('ssb-private-group-keys')
11+
const { ReadyGate } = require('../utils')
1112

1213
function makeKeysManager(config) {
1314
const ownDMKeysCache = []
@@ -71,16 +72,19 @@ function makeKeysManager(config) {
7172
module.exports = {
7273
name: 'box2',
7374
init: function init(ssb) {
75+
const keyringSetup = new ReadyGate()
76+
7477
const encryptionFormat = {
7578
name: 'box2',
7679

7780
setup(config, cb) {
7881
encryptionFormat._selfId = config.keys.id
79-
if (!encryptionFormat._keysManager) {
82+
// Simulate a slow ssb-keyring loading process
83+
setTimeout(() => {
8084
encryptionFormat._keysManager = makeKeysManager(config)
81-
}
82-
// FIXME: load ssb-keyring here
83-
cb()
85+
keyringSetup.setReady()
86+
cb()
87+
}, 1000)
8488
},
8589

8690
_isGroup(recp) {
@@ -96,15 +100,21 @@ module.exports = {
96100
},
97101

98102
_addOwnDMKey(key) {
99-
encryptionFormat._keysManager.addOwnDMKey(key)
103+
keyringSetup.onReady(() => {
104+
encryptionFormat._keysManager.addOwnDMKey(key)
105+
})
100106
},
101107

102108
_addGroupKey(id, key) {
103-
encryptionFormat._keysManager.addGroupKey(id, key)
109+
keyringSetup.onReady(() => {
110+
encryptionFormat._keysManager.addGroupKey(id, key)
111+
})
104112
},
105113

106114
_removeGroupKey(id, key) {
107-
encryptionFormat._keysManager.removeGroupKey(id, key)
115+
keyringSetup.onReady(() => {
116+
encryptionFormat._keysManager.removeGroupKey(id, key)
117+
})
108118
},
109119

110120
encrypt(plaintextBuf, opts) {
@@ -178,21 +188,16 @@ module.exports = {
178188
trialGroupKeys,
179189
{ maxAttempts: 1 }
180190
)
181-
if (decryptedGroup)
182-
return decryptedGroup
191+
if (decryptedGroup) return decryptedGroup
183192

184193
const trialDMKeys =
185194
opts.author !== encryptionFormat._selfId
186195
? [keysManager.sharedDMKey(opts.author), ...keysManager.ownDMKeys()]
187196
: keysManager.ownDMKeys()
188197

189-
return unbox(
190-
ciphertextBuf,
191-
authorBFE,
192-
previousBFE,
193-
trialDMKeys,
194-
{ maxAttempts: 16 }
195-
)
198+
return unbox(ciphertextBuf, authorBFE, previousBFE, trialDMKeys, {
199+
maxAttempts: 16,
200+
})
196201
},
197202
}
198203

indexes/private.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,28 @@ module.exports = function (dir, sbot, config) {
4545
}
4646
}
4747

48-
decryptedIdx.loadFile(done())
48+
decryptedIdx.loadFile(err => done()(null, err))
4949
for (const idx of encryptedIdxMap.values()) {
50-
idx.loadFile(done())
50+
idx.loadFile(err => done()(null, err))
5151
}
5252

53-
done((err) => {
54-
if (err) {
55-
debug('failed to load encrypted or decrypted indexes')
53+
done((criticalError, results) => {
54+
const loadFileErrors = results.filter(Boolean)
55+
56+
if (criticalError) {
57+
return cb(clarify(criticalError, 'private plugin failed to load'))
58+
}
59+
60+
if (loadFileErrors.length > 0) {
61+
for (const err of loadFileErrors) {
62+
if (err.code === 'ENOENT') continue
63+
else if (err.message === 'Empty NumsFile') continue
64+
else return cb(clarify(err, 'private plugin failed to load'))
65+
}
66+
debug('encrypted or decrypted indexes seem to be empty')
5667
latestOffset.set(-1)
5768
stateLoaded.resolve()
58-
if (err.code === 'ENOENT') cb()
59-
else if (err.message === 'Empty NumsFile') cb()
60-
// prettier-ignore
61-
else cb(clarify(err, 'private plugin failed to load'))
69+
cb()
6270
return
6371
}
6472

utils.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ function onceWhen(obv, filter, cb) {
2626
})
2727
}
2828

29+
function onceWhenPromise(obv, filter) {
30+
return new Promise((resolve) => {
31+
onceWhen(obv, filter, resolve)
32+
})
33+
}
34+
2935
class ReadyGate {
3036
constructor() {
3137
this.waiting = new Set()
@@ -44,4 +50,4 @@ class ReadyGate {
4450
}
4551
}
4652

47-
module.exports = { onceWhen, ReadyGate }
53+
module.exports = { onceWhen, onceWhenPromise, ReadyGate }

0 commit comments

Comments
 (0)