git ssb

1+

Dominic / ssb-keys



Tree: 4f4b6ae4dcf19cd0df779beb4ca34328ec9247df

Files: 4f4b6ae4dcf19cd0df779beb4ca34328ec9247df / index.js

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

Built with git-ssb-web