Skip to content

Commit dc6d44e

Browse files
author
sevenbitbyte
committed
secure-config documentation
1 parent 030ef02 commit dc6d44e

5 files changed

Lines changed: 177 additions & 40 deletions

File tree

examples/i2p-host.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Path = require('path')
2-
const debug = require('debug')('test.server-db')
2+
const debug = require('debug')('example.i2p')
33
const Dataparty = require('../src')
44
const dataparty_crypto = require('@dataparty/crypto')
55

@@ -55,7 +55,6 @@ async function main(){
5555

5656
const runner = new Dataparty.ServiceRunnerNode({
5757
party,
58-
//prefix: 'foo',
5958
service: live,
6059
sendFullErrors: false,
6160
useNative: false
@@ -71,7 +70,9 @@ async function main(){
7170
runner: runnerRouter,
7271
trust_proxy: true,
7372
wsEnabled: true,
74-
i2pEnabled: true
73+
i2pEnabled: true,
74+
i2pSamHost: 'localhost',
75+
i2pSamPort: 7656
7576
})
7677

7778
debug(runner.party.identity)

examples/secure-config.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ async function main(){
1616

1717
secureConfig = new Dataparty.Config.SecureConfig({
1818
config: jsonConfig,
19-
//timeoutMs: 15*1000
19+
timeoutMs: 15*1000
2020
})
2121

2222

@@ -122,10 +122,9 @@ async function main(){
122122

123123
let timer = setTimeout(async ()=>{
124124

125-
126125
console.log('timer config', await secureConfig.readAll())
127126

128-
}, 1000*60*6)
127+
}, 1000*30)
129128

130129
}
131130

src/config/json-file.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class JsonFileConfig extends IConfig {
6868

6969
async write(key, value){
7070

71+
logger('writing path:', key)
7172
deepSet(this.content, key, value)
7273
await this.save()
7374
}

src/config/secure-config.js

Lines changed: 152 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const dataparty_crypto = require('@dataparty/crypto')
22

33
const debug = require('debug')('dataparty.config.secure-config')
4-
54
const deepSet = require('lodash').set
5+
66
const reach = require('../utils/reach')
77

88
const IConfig = require('./iconfig')
@@ -11,6 +11,16 @@ const PASSWORD_HASHING_ROUNDS = 1000000
1111
const DEFAULT_TIMEOUT_MS = 5*60*1000 //! 5min
1212

1313
class SecureConfig extends IConfig {
14+
15+
/**
16+
* @class module:Config.SecureConfig
17+
* @implements {module:Config.SecureConfig}
18+
* @link module.Config
19+
* @param {string} id The id of this secure config. Multiple secure configs can be stored within a single `IConfig`
20+
* @param {IConfig} config The underlying IConfig to use for storage
21+
* @param {number} timeoutMs Timeout since last unlock, after which the config will be locked. Defaults to 5 minutes.
22+
* @param {boolean} includeActivity When set to `true` the timeout is reset after any read/write activity. Defaults to `true`
23+
*/
1424
constructor({
1525
id = 'secure-config',
1626
config, timeoutMs=DEFAULT_TIMEOUT_MS, includeActivity=true
@@ -24,78 +34,175 @@ class SecureConfig extends IConfig {
2434
this.timer = null
2535
this.lastActivity = null
2636
this.timeoutMs = timeoutMs || DEFAULT_TIMEOUT_MS
27-
this.includeActivity = includeActivity || true
37+
this.includeActivity = (typeof includeActivity === 'boolean') ? includeActivity : true
2838

2939
this.blocked = false
3040
}
3141

42+
/**
43+
* Start the secure storage
44+
* @fires module:Config.SecureConfig#setup-required
45+
* @fires module:Config.SecureConfig#ready
46+
*/
3247
async start(){
3348
await this.config.start()
3449

3550
const isReady = await this.isInitialized()
3651
if(!isReady){
52+
/**
53+
* Setup required event. The secure config has not yet has a password
54+
* or key configured.
55+
* @event module:Config.SecureConfig#setup-required
56+
*/
3757
this.emit('setup-required')
3858
} else {
59+
/**
60+
* Ready event. The secure config is ready to be unlocked and have
61+
* configuration values read or written.
62+
* @event module:Config.SecureConfig#ready
63+
*/
3964
this.emit('ready')
4065
}
4166
}
4267

68+
/**
69+
* Checks if the secure config has initialized with a password or key
70+
* @returns {boolean}
71+
*/
4372
async isInitialized(){
44-
let salt = await this.config.read('salt')
45-
let rounds = await this.config.read('rounds')
4673

74+
let keyType = await this.config.read(this.id+'.settings.type')
75+
76+
if(keyType == 'pbkdf2'){
4777

48-
return (salt != undefined && salt.length > 16 && rounds > 100000)
78+
let salt = await this.config.read(this.id+'.settings.salt')
79+
let rounds = await this.config.read(this.id+'.settings.rounds')
80+
81+
return (salt != undefined && salt.length > 16 && rounds > 100000)
82+
83+
} else if(keyType == 'key'){
84+
return true
85+
} else if(!keyType) {
86+
return false
87+
} else {
88+
debug('type', keyType)
89+
throw new Error('unexpected key type')
90+
}
4991
}
5092

93+
/**
94+
* Checks if the secure config is locked. If not locked the secure config
95+
* can be used without blocking waiting for user to unlock.
96+
* @returns {boolean}
97+
*/
5198
isLocked(){
5299
return this.content == null || this.identity == null
53100
}
54101

55-
async setPassword(password, defaults={}){
102+
/**
103+
* Initialize the secure config with a password
104+
* @fires module:Config.SecureConfig#intialized
105+
* @fires module:Config.SecureConfig#ready
106+
* @param {string} password
107+
* @param {object} defaults
108+
* @param {('pbkdf2')} type
109+
* @async
110+
*/
111+
async setPassword(password, defaults={}, type='pbkdf2'){
56112
debug('setPassword')
57113
if(await this.isInitialized()){ throw new Error('already initialized') }
58114

59-
const salt = await dataparty_crypto.Routines.generateSalt()
60-
const rounds = PASSWORD_HASHING_ROUNDS
115+
let key = null
116+
let settings = null
61117

118+
if(type == 'pbkdf2'){
119+
const salt = await dataparty_crypto.Routines.generateSalt()
120+
const rounds = PASSWORD_HASHING_ROUNDS
62121

63-
await this.config.write('salt', salt.toString('hex'))
64-
await this.config.write('rounds', rounds)
122+
settings = {
123+
type: type,
124+
salt: salt.toString('hex'),
125+
rounds
126+
}
127+
128+
key = await dataparty_crypto.Routines.createKeyFromPassword(password, salt, rounds)
65129

66-
let key = await dataparty_crypto.Routines.createKeyFromPassword(password, salt, rounds)
130+
} else {
131+
throw new Error('unsupported KDF['+type+']')
132+
}
67133

68-
await this.setIdentity(key)
134+
await this.initialize(key, defaults, settings)
69135
}
70136

71-
async setIdentity(key){
137+
/**
138+
* Initialize the secure config with a key
139+
* @fires module:Config.SecureConfig#intialized
140+
* @fires module:Config.SecureConfig#ready
141+
* @param {dataparty_crypto/IKey} key
142+
* @param {object} defaults
143+
* @async
144+
*/
145+
async setIdentity(key, defaults){
72146
debug('setIdentity')
73147
if(await this.isInitialized()){ throw new Error('already initialized') }
74148

149+
const settings = {
150+
key_type: 'key'
151+
}
152+
153+
await this.initialize(key, defaults, settings)
154+
}
155+
156+
157+
async initialize(key, defaults, settings){
158+
debug('initialize - type:', settings.key_type)
159+
if(await this.isInitialized()){ throw new Error('already initialized') }
160+
75161
const pwIdentity = new dataparty_crypto.Identity({
76162
key,
77163
id: this.id,
78164
})
79165

80-
const initialContent = new dataparty_crypto.Message({ msg: defaults })
166+
const insecureContent = {
167+
...settings
168+
}
169+
170+
const secureContent = {
171+
created: Date.now(),
172+
...defaults
173+
}
174+
175+
const initialContent = new dataparty_crypto.Message({ msg: secureContent })
81176

82177
await initialContent.encrypt(pwIdentity, pwIdentity.toMini())
83178

84-
await this.config.write('content', initialContent.toJSON())
179+
await this.config.write(this.id+'.settings', insecureContent)
180+
await this.config.write(this.id+'.content', initialContent.toJSON())
85181

86182
debug('\t', 'identity', pwIdentity)
87183

88-
debug('\t', 'content', initialContent.toJSON())
184+
debug('\t', 'insecure content', insecureContent)
185+
186+
debug('\t', 'secure content', secureContent)
187+
188+
debug('\t', 'encrypted content', initialContent.toJSON())
89189

90190
await this.config.save()
91191

92192

93193
const contentMsg = new dataparty_crypto.Message( initialContent )
94194
//! Verify message
95195
await contentMsg.decrypt(pwIdentity)
196+
/**
197+
* The secure config has been successfully initialized with a passowrd
198+
* or key.
199+
* @event module:Config.SecureConfig#intialized
200+
*/
201+
this.emit('initialized')
96202
this.emit('ready')
97203
}
98204

205+
99206
async waitForUnlocked(reason){
100207

101208
if(!this.isLocked()){
@@ -105,6 +212,12 @@ class SecureConfig extends IConfig {
105212
debug('waitForUnlocked', reason)
106213

107214
if(!this.blocked){
215+
/**
216+
* An read/write operation has been blocked due to the secure config
217+
* being locked.
218+
* @event module:Config.SecureConfig#blocked
219+
* @type
220+
*/
108221
this.emit('blocked', reason)
109222
}
110223

@@ -124,15 +237,21 @@ class SecureConfig extends IConfig {
124237
await waiting
125238
}
126239

240+
/**
241+
* Unlocks the secure config
242+
* @fires module:Config.SecureConfig#unlocked
243+
* @param {string} password
244+
* @returns {Promise}
245+
*/
127246
async unlock(password){
128247

129248
if(this.timer != null){
130249
clearTimeout(this.timer)
131250
this.timer = null
132251
}
133252

134-
let salt = Buffer.from(await this.config.read('salt'),'hex')
135-
let rounds = await this.config.read('rounds')
253+
let salt = Buffer.from(await this.config.read(this.id+'.settings.salt'),'hex')
254+
let rounds = await this.config.read(this.id+'.settings.rounds')
136255

137256
let key = await dataparty_crypto.Routines.createKeyFromPassword(password, salt, rounds)
138257

@@ -142,7 +261,7 @@ class SecureConfig extends IConfig {
142261
})
143262

144263

145-
this.content = await this.config.read('content')
264+
this.content = await this.config.read(this.id+'.content')
146265

147266
const contentMsg = new dataparty_crypto.Message( this.content )
148267

@@ -155,9 +274,18 @@ class SecureConfig extends IConfig {
155274
this.timer = setTimeout(this.onTimeout.bind(this), this.timeoutMs)
156275
}
157276

277+
/**
278+
* The secure config has been unlocked
279+
* @event module:Config.SecureConfig#unlocked
280+
*/
158281
this.emit('unlocked')
159282
}
160283

284+
/**
285+
* Locks the secure config
286+
* @fires module:Config.SecureConfig#locked
287+
* @param {string} password
288+
*/
161289
lock(){
162290
if(this.timer != null){
163291
clearTimeout(this.timer)
@@ -170,6 +298,10 @@ class SecureConfig extends IConfig {
170298
this.content = null
171299
this.identity = null
172300

301+
/**
302+
* The secure config has been locked
303+
* @event module:Config.SecureConfig#locked
304+
*/
173305
this.emit('locked')
174306
}
175307

@@ -269,7 +401,7 @@ class SecureConfig extends IConfig {
269401

270402
this.updateTimeout()
271403

272-
await this.config.write('content',{
404+
await this.config.write(this.id+'.content',{
273405
enc: this.content.enc,
274406
sig: this.content.sig
275407
})

0 commit comments

Comments
 (0)