git ssb

1+

Dominic / ssb-keys



Tree: f39581dcdc2e1fb69fc7d8390596cf5a47d9f1bd

Files: f39581dcdc2e1fb69fc7d8390596cf5a47d9f1bd / index.js

7326 bytesRaw
1var fs = require('fs')
2var mkdirp = require('mkdirp')
3var path = require('path')
4var deepEqual = require('deep-equal')
5
6var crypto = require('crypto')
7var createHmac = require('hmac')
8var Blake2s = require('blake2s')
9
10var ecc = require('./eccjs')
11
12//UTILS
13
14function clone (obj) {
15 var _obj = {}
16 for(var k in obj) {
17 if(Object.hasOwnProperty.call(obj, k))
18 _obj[k] = obj[k]
19 }
20 return _obj
21}
22
23function hash (data, enc) {
24 return new Blake2s().update(data, enc).digest('base64') + '.blake2s'
25}
26
27function isHash (data) {
28 return isString(data) && /^[A-Za-z0-9\/+]{43}=\.blake2s$/.test(data)
29}
30
31function isObject (o) {
32 return 'object' === typeof o
33}
34
35function isFunction (f) {
36 return 'function' === typeof f
37}
38
39exports.isHash = isHash
40exports.hash = hash
41
42function isString(s) {
43 return 'string' === typeof s
44}
45
46function empty(v) { return !!v }
47
48function toBuffer(buf) {
49 if(buf == null) return buf
50 if(Buffer.isBuffer(buf)) throw new Error('already a buffer')
51 var i = buf.indexOf('.')
52 return new Buffer(buf.substring(0, ~i ? i : buf.length), 'base64')
53}
54
55function toUint8(buf) {
56 return new Uint8Array(toBuffer(buf))
57}
58
59function getTag (string) {
60 var i = string.indexOf('.')
61 return string.substring(i+1)
62}
63
64exports.getTag = getTag
65
66function tag (key, tag) {
67 if(!tag) throw new Error('no tag for:' + key.toString('base64'))
68 return key.toString('base64')+'.' + tag.replace(/^\./, '')
69}
70
71function keysToJSON(keys, curve) {
72 curve = (keys.curve || curve)
73
74 var pub = tag(keys.public.toString('base64'), curve)
75 return {
76 curve: curve,
77 public: pub,
78 private: keys.private ? tag(keys.private.toString('base64'), curve) : undefined,
79 id: hash(pub)
80 }
81}
82
83//(DE)SERIALIZE KEYS
84
85function constructKeys(keys, legacy) {
86 if(!keys) throw new Error('*must* pass in keys')
87
88 return [
89 '# this is your SECRET name.',
90 '# this name gives you magical powers.',
91 '# with it you can mark your messages so that your friends can verify',
92 '# that they really did come from you.',
93 '#',
94 '# if any one learns this name, they can use it to destroy your identity',
95 '# NEVER show this to anyone!!!',
96 '',
97 legacy ? keys.private : JSON.stringify(keys, null, 2),
98 '',
99 '# WARNING! It\'s vital that you DO NOT edit OR share your secret name',
100 '# instead, share your public name',
101 '# your public name: ' + keys.id
102 ].join('\n')
103}
104
105function reconstructKeys(keyfile) {
106 var private = keyfile
107 .replace(/\s*\#[^\n]*/g, '')
108 .split('\n').filter(empty).join('')
109
110 //if the key is in JSON format, we are good.
111 try {
112 return JSON.parse(private)
113 } catch (_) {}
114
115 //else, reconstruct legacy curve...
116
117 var curve = getTag(private)
118
119 if(curve !== 'k256')
120 throw new Error('expected legacy curve (k256) but found:' + curve)
121
122 return keysToJSON(ecc.restore(toBuffer(private)), 'k256')
123}
124
125var toNameFile = exports.toNameFile = function (namefile) {
126 if(isObject(namefile))
127 return path.join(namefile.path, 'secret')
128 return namefile
129}
130
131exports.load = function(namefile, cb) {
132 namefile = toNameFile(namefile)
133 fs.readFile(namefile, 'ascii', function(err, privateKeyStr) {
134 if (err) return cb(err)
135 try { cb(null, reconstructKeys(privateKeyStr)) }
136 catch (e) { cb(err) }
137 })
138}
139
140exports.loadSync = function(namefile) {
141 namefile = toNameFile(namefile)
142 return reconstructKeys(fs.readFileSync(namefile, 'ascii'))
143}
144
145exports.create = function(namefile, curve, legacy, cb) {
146 if(isFunction(legacy))
147 cb = legacy, legacy = null
148 if(isFunction(curve))
149 cb = curve, curve = null
150
151 namefile = toNameFile(namefile)
152 var keys = exports.generate(curve)
153 var keyfile = constructKeys(keys, legacy)
154 mkdirp(path.dirname(namefile), function (err) {
155 if(err) return cb(err)
156 fs.writeFile(namefile, keyfile, function(err) {
157 if (err) return cb(err)
158 cb(null, keys)
159 })
160 })
161}
162
163exports.createSync = function(namefile, curve, legacy) {
164 namefile = toNameFile(namefile)
165 var keys = exports.generate(curve)
166 var keyfile = constructKeys(keys, legacy)
167 mkdirp.sync(path.dirname(namefile))
168 fs.writeFileSync(namefile, keyfile)
169 return keys
170}
171
172exports.loadOrCreate = function (namefile, cb) {
173 namefile = toNameFile(namefile)
174 exports.load(namefile, function (err, keys) {
175 if(!err) return cb(null, keys)
176 exports.create(namefile, cb)
177 })
178}
179
180exports.loadOrCreateSync = function (namefile) {
181 namefile = toNameFile(namefile)
182 try {
183 return exports.loadSync(namefile)
184 } catch (err) {
185 return exports.createSync(namefile)
186 }
187}
188
189
190// DIGITAL SIGNATURES
191
192var curves = {
193 ed25519 : require('./sodium'),
194 k256 : ecc //LEGACY
195}
196
197function getCurve(keys) {
198 var curve = keys.curve
199
200 if(!keys.curve && isString(keys.public))
201 keys = keys.public
202
203 if(!curve && isString(keys))
204 curve = getTag(keys)
205
206 if(!curves[curve]) {
207 throw new Error(
208 'unkown curve:' + curve +
209 ' expected: '+Object.keys(curves)
210 )
211 }
212
213 return curve
214}
215
216//this should return a key pair:
217// {curve: curve, public: Buffer, private: Buffer}
218
219exports.generate = function (curve, seed) {
220 curve = curve || 'ed25519'
221
222 if(!curves[curve])
223 throw new Error('unknown curve:'+curve)
224
225 return keysToJSON(curves[curve].generate(seed), curve)
226}
227
228//takes a public key and a hash and returns a signature.
229//(a signature must be a node buffer)
230
231exports.sign = function (keys, hash) {
232 if(isObject(hash))
233 throw new Error('hash should be base64 string, did you mean signObj(private, unsigned_obj)')
234 var hashTag = hash.substring(hash.indexOf('.'))
235 var curve = getCurve(keys)
236
237 return curves[curve]
238 .sign(toBuffer(keys.private || keys), toBuffer(hash))
239 .toString('base64')+'.blake2s.'+curve
240
241}
242
243//takes a public key, signature, and a hash
244//and returns true if the signature was valid.
245exports.verify = function (keys, sig, hash) {
246 if(isObject(sig))
247 throw new Error('signature should be base64 string, did you mean verifyObj(public, signed_obj)')
248 return curves[getCurve(keys)].verify(
249 toBuffer(keys.public || keys),
250 toBuffer(sig),
251 toBuffer(hash)
252 )
253}
254
255// OTHER CRYTPO FUNCTIONS
256
257function createHash() {
258 return new Blake2s()
259}
260
261exports.hmac = function (data, key) {
262 return createHmac(createHash, 64, key)
263 .update(data).digest('base64')+'.blake2s.hmac'
264}
265
266exports.signObj = function (keys, obj) {
267 var _obj = clone(obj)
268 var str = JSON.stringify(_obj, null, 2)
269 var h = hash(str, 'utf8')
270 _obj.signature = exports.sign(keys, h)
271 return _obj
272}
273
274exports.verifyObj = function (keys, obj) {
275 obj = clone(obj)
276 var sig = obj.signature
277 delete obj.signature
278 var str = JSON.stringify(obj, null, 2)
279 var h = hash(str, 'utf8')
280 return exports.verify(keys, sig, h)
281}
282
283//TODO: remove these (use asymmetric auth for everything)
284
285exports.signObjHmac = function (secret, obj) {
286 obj = clone(obj)
287 var str = JSON.stringify(obj, null, 2)
288 obj.hmac = exports.hmac(str, secret)
289 return obj
290}
291
292exports.verifyObjHmac = function (secret, obj) {
293 obj = clone(obj)
294 var hmac = obj.hmac
295 delete obj.hmac
296 var str = JSON.stringify(obj, null, 2)
297 var _hmac = exports.hmac(str, secret)
298 return deepEqual(hmac, _hmac)
299}
300
301exports.createAuth = function (keys, role) {
302 return exports.signObj(keys, {
303 role: role || 'client',
304 ts: Date.now(),
305 public: keys.public
306 })
307}
308
309

Built with git-ssb-web