git ssb

1+

Dominic / ssb-keys



Tree: fc9ed669abb6408ec09aaf6c19f800e6f576d05b

Files: fc9ed669abb6408ec09aaf6c19f800e6f576d05b / index.js

7528 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('sodium').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 return JSON.parse(private)
124 } catch (_) {}
125
126 //else, reconstruct legacy curve...
127
128 var curve = getTag(private)
129
130 if(curve !== 'k256')
131 throw new Error('expected legacy curve (k256) but found:' + curve)
132
133 return keysToJSON(ecc.restore(toBuffer(private)), 'k256')
134}
135
136var toNameFile = exports.toNameFile = function (namefile) {
137 if(isObject(namefile))
138 return path.join(namefile.path, 'secret')
139 return namefile
140}
141
142exports.load = function(namefile, cb) {
143 namefile = toNameFile(namefile)
144 fs.readFile(namefile, 'ascii', function(err, privateKeyStr) {
145 if (err) return cb(err)
146 try { cb(null, reconstructKeys(privateKeyStr)) }
147 catch (e) { cb(err) }
148 })
149}
150
151exports.loadSync = function(namefile) {
152 namefile = toNameFile(namefile)
153 return reconstructKeys(fs.readFileSync(namefile, 'ascii'))
154}
155
156exports.create = function(namefile, curve, legacy, cb) {
157 if(isFunction(legacy))
158 cb = legacy, legacy = null
159 if(isFunction(curve))
160 cb = curve, curve = null
161
162 namefile = toNameFile(namefile)
163 var keys = exports.generate(curve)
164 var keyfile = constructKeys(keys, legacy)
165 mkdirp(path.dirname(namefile), function (err) {
166 if(err) return cb(err)
167 fs.writeFile(namefile, keyfile, function(err) {
168 if (err) return cb(err)
169 cb(null, keys)
170 })
171 })
172}
173
174exports.createSync = function(namefile, curve, legacy) {
175 namefile = toNameFile(namefile)
176 var keys = exports.generate(curve)
177 var keyfile = constructKeys(keys, legacy)
178 mkdirp.sync(path.dirname(namefile))
179 fs.writeFileSync(namefile, keyfile)
180 return keys
181}
182
183exports.loadOrCreate = function (namefile, cb) {
184 namefile = toNameFile(namefile)
185 exports.load(namefile, function (err, keys) {
186 if(!err) return cb(null, keys)
187 exports.create(namefile, cb)
188 })
189}
190
191exports.loadOrCreateSync = function (namefile) {
192 namefile = toNameFile(namefile)
193 try {
194 return exports.loadSync(namefile)
195 } catch (err) {
196 return exports.createSync(namefile)
197 }
198}
199
200
201// DIGITAL SIGNATURES
202
203var curves = {
204 ed25519 : require('./sodium'),
205 k256 : ecc //LEGACY
206}
207
208function getCurve(keys) {
209 var curve = keys.curve
210
211 if(!keys.curve && isString(keys.public))
212 keys = keys.public
213
214 if(!curve && isString(keys))
215 curve = getTag(keys)
216
217 if(!curves[curve]) {
218 throw new Error(
219 'unkown curve:' + curve +
220 ' expected: '+Object.keys(curves)
221 )
222 }
223
224 return curve
225}
226
227//this should return a key pair:
228// {curve: curve, public: Buffer, private: Buffer}
229
230exports.generate = function (curve, seed) {
231 curve = curve || 'ed25519'
232
233 if(!curves[curve])
234 throw new Error('unknown curve:'+curve)
235
236 return keysToJSON(curves[curve].generate(seed), curve)
237}
238
239//takes a public key and a hash and returns a signature.
240//(a signature must be a node buffer)
241
242exports.sign = function (keys, msg) {
243 if(isString(msg))
244 msg = new Buffer(msg)
245 if(!isBuffer(msg))
246 throw new Error('msg should be buffer')
247 var curve = getCurve(keys)
248
249 return curves[curve]
250 .sign(toBuffer(keys.private || keys), msg)
251 .toString('base64')+'.sig.'+curve
252
253}
254
255//takes a public key, signature, and a hash
256//and returns true if the signature was valid.
257exports.verify = function (keys, sig, msg) {
258 if(isObject(sig))
259 throw new Error('signature should be base64 string, did you mean verifyObj(public, signed_obj)')
260 return curves[getCurve(keys)].verify(
261 toBuffer(keys.public || keys),
262 toBuffer(sig),
263 isBuffer(msg) ? msg : new Buffer(msg)
264 )
265}
266
267// OTHER CRYTPO FUNCTIONS
268
269exports.hmac = function (data, key) {
270 return createHmac(createHash, 64, key)
271 .update(data).digest('base64')+'.sha256.hmac'
272}
273
274exports.signObj = function (keys, obj) {
275 var _obj = clone(obj)
276 var b = new Buffer(JSON.stringify(_obj, null, 2))
277 _obj.signature = exports.sign(keys, b)
278 return _obj
279}
280
281exports.verifyObj = function (keys, obj) {
282 obj = clone(obj)
283 var sig = obj.signature
284 delete obj.signature
285 var b = new Buffer(JSON.stringify(obj, null, 2))
286 return exports.verify(keys, sig, b)
287}
288
289exports.box = function (msg, recipients) {
290 msg = new Buffer(JSON.stringify(msg))
291
292 recipients = recipients.map(function (keys) {
293 var public = keys.public || keys
294 return sodium.crypto_sign_ed25519_pk_to_curve25519(toBuffer(public))
295 })
296
297 //it's since the nonce is 24 bytes (a multiple of 3)
298 //it's possible to concatenate the base64 strings
299 //and still have a valid base64 string.
300 return pb.multibox(msg, recipients).toString('base64')+'.box'
301}
302
303exports.unbox = function (boxed, keys) {
304 boxed = toBuffer(boxed)
305 var sk = sodium.crypto_sign_ed25519_sk_to_curve25519(toBuffer(keys.private || keys))
306
307 var msg = pb.multibox_open(boxed, sk)
308 if(msg) return JSON.parse(''+msg)
309}
310

Built with git-ssb-web