git ssb

1+

Dominic / ssb-keys



Tree: 846e0d32e32e26a7f44d1af8f6315b457b5070a1

Files: 846e0d32e32e26a7f44d1af8f6315b457b5070a1 / index.js

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

Built with git-ssb-web