git ssb

1+

Dominic / ssb-keys



Tree: cf80538bda5715b7fddc0dc1ffdab001895d0ac9

Files: cf80538bda5715b7fddc0dc1ffdab001895d0ac9 / index.js

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

Built with git-ssb-web