index.jsView |
---|
1 | | -var fs = require('fs') |
2 | | -var crypto = require('crypto') |
3 | | -var ecc = require('eccjs') |
4 | | -var k256 = ecc.curves.k256 |
5 | | -var Blake2s = require('blake2s') |
6 | | -var mkdirp = require('mkdirp') |
7 | | -var path = require('path') |
8 | | -var curve = ecc.curves.k256 |
| 1 | +var fs = require('fs') |
| 2 | +var mkdirp = require('mkdirp') |
| 3 | +var path = require('path') |
| 4 | +var deepEqual = require('deep-equal') |
| 5 | + |
| 6 | +var crypto = require('crypto') |
9 | 7 | var createHmac = require('hmac') |
10 | | -var deepEqual = require('deep-equal') |
| 8 | +var Blake2s = require('blake2s') |
11 | 9 | |
| 10 | +var ecc = require('./eccjs') |
| 11 | + |
| 12 | + |
| 13 | + |
12 | 14 | function clone (obj) { |
13 | 15 | var _obj = {} |
14 | 16 | for(var k in obj) { |
15 | 17 | if(Object.hasOwnProperty.call(obj, k)) |
21 | 23 | function hash (data, enc) { |
22 | 24 | return new Blake2s().update(data, enc).digest('base64') + '.blake2s' |
23 | 25 | } |
24 | 26 | |
25 | | - |
26 | 27 | function isHash (data) { |
27 | 28 | return isString(data) && /^[A-Za-z0-9\/+]{43}=\.blake2s$/.test(data) |
28 | 29 | } |
29 | 30 | |
30 | 31 | function isObject (o) { |
31 | 32 | return 'object' === typeof o |
32 | 33 | } |
33 | 34 | |
| 35 | +function isFunction (f) { |
| 36 | + return 'function' === typeof f |
| 37 | +} |
| 38 | + |
34 | 39 | exports.isHash = isHash |
35 | 40 | exports.hash = hash |
36 | 41 | |
37 | 42 | function isString(s) { |
39 | 44 | } |
40 | 45 | |
41 | 46 | function empty(v) { return !!v } |
42 | 47 | |
43 | | -function constructKeys() { |
44 | | - var privateKey = crypto.randomBytes(32) |
45 | | - var k = keysToBase64(ecc.restore(k256, privateKey)) |
46 | | - k.keyfile = [ |
| 48 | +function 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 | + |
| 55 | +function toUint8(buf) { |
| 56 | + return new Uint8Array(toBuffer(buf)) |
| 57 | +} |
| 58 | + |
| 59 | +function getTag (string) { |
| 60 | + var i = string.indexOf('.') |
| 61 | + return string.substring(i+1) |
| 62 | +} |
| 63 | + |
| 64 | +exports.getTag = getTag |
| 65 | + |
| 66 | +function tag (key, tag) { |
| 67 | + if(!tag) throw new Error('no tag for:' + key.toString('base64')) |
| 68 | + return key.toString('base64')+'.' + tag.replace(/^\./, '') |
| 69 | +} |
| 70 | + |
| 71 | +function 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 | + |
| 84 | + |
| 85 | +function constructKeys(keys, legacy) { |
| 86 | + if(!keys) throw new Error('*must* pass in keys') |
| 87 | + |
| 88 | + return [ |
47 | 89 | '# this is your SECRET name.', |
48 | 90 | '# this name gives you magical powers.', |
49 | 91 | '# with it you can mark your messages so that your friends can verify', |
50 | 92 | '# that they really did come from you.', |
51 | 93 | '#', |
52 | 94 | '# if any one learns this name, they can use it to destroy your identity', |
53 | 95 | '# NEVER show this to anyone!!!', |
54 | 96 | '', |
55 | | - k.private.toString('hex'), |
| 97 | + legacy ? keys.private : JSON.stringify(keys, null, 2), |
56 | 98 | '', |
57 | 99 | '# WARNING! It\'s vital that you DO NOT edit OR share your secret name', |
58 | 100 | '# instead, share your public name', |
59 | | - '# your public name: ' + k.id.toString('hex') |
| 101 | + '# your public name: ' + keys.id |
60 | 102 | ].join('\n') |
61 | | - return k |
62 | 103 | } |
63 | 104 | |
| 105 | +function reconstructKeys(keyfile) { |
| 106 | + var private = keyfile |
| 107 | + .replace(/\s*\#[^\n]*/g, '') |
| 108 | + .split('\n').filter(empty).join('') |
64 | 109 | |
65 | | -function toBuffer(buf) { |
66 | | - if(buf == null) return buf |
67 | | - return new Buffer(buf.substring(0, buf.indexOf('.')), 'base64') |
68 | | -} |
| 110 | + |
| 111 | + try { |
| 112 | + return JSON.parse(private) |
| 113 | + } catch (_) {} |
69 | 114 | |
70 | | -function keysToBase64 (keys) { |
71 | | - var pub = tag(keys.public, 'k256') |
72 | | - return { |
73 | | - public: pub, |
74 | | - private: tag(keys.private, 'k256'), |
75 | | - id: hash(pub) |
76 | | - } |
77 | | -} |
| 115 | + |
78 | 116 | |
79 | | -function hashToBuffer(hash) { |
80 | | - if(!isHash(hash)) throw new Error('sign expects a hash') |
81 | | - return toBuffer(hash) |
82 | | -} |
| 117 | + var curve = getTag(private) |
83 | 118 | |
84 | | -function keysToBuffer(key) { |
85 | | - return isString(key) ? toBuffer(key) : { |
86 | | - public: toBuffer(key.public), |
87 | | - private: toBuffer(key.private) |
88 | | - } |
89 | | -} |
| 119 | + if(curve !== 'k256') |
| 120 | + throw new Error('expected legacy curve (k256) but found:' + curve) |
90 | 121 | |
91 | | -function reconstructKeys(privateKeyStr) { |
92 | | - privateKeyStr = privateKeyStr |
93 | | - .replace(/\s*\#[^\n]*/g, '') |
94 | | - .split('\n').filter(empty).join('') |
95 | | - |
96 | | - var privateKey = ( |
97 | | - !/\./.test(privateKeyStr) |
98 | | - ? new Buffer(privateKeyStr, 'hex') |
99 | | - : toBuffer(privateKeyStr) |
100 | | - ) |
101 | | - |
102 | | - return keysToBase64(ecc.restore(k256, privateKey)) |
| 122 | + return keysToJSON(ecc.restore(toBuffer(private)), 'k256') |
103 | 123 | } |
104 | 124 | |
105 | | -function tag (key, tag) { |
106 | | - return key.toString('base64')+'.' + tag.replace(/^\./, '') |
107 | | -} |
108 | | - |
109 | 125 | var toNameFile = exports.toNameFile = function (namefile) { |
110 | 126 | if(isObject(namefile)) |
111 | 127 | return path.join(namefile.path, 'secret') |
112 | 128 | return namefile |
125 | 141 | namefile = toNameFile(namefile) |
126 | 142 | return reconstructKeys(fs.readFileSync(namefile, 'ascii')) |
127 | 143 | } |
128 | 144 | |
129 | | -exports.create = function(namefile, cb) { |
| 145 | +exports.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 | + |
130 | 151 | namefile = toNameFile(namefile) |
131 | | - var k = constructKeys() |
| 152 | + var keys = exports.generate(curve) |
| 153 | + var keyfile = constructKeys(keys, legacy) |
132 | 154 | mkdirp(path.dirname(namefile), function (err) { |
133 | 155 | if(err) return cb(err) |
134 | | - fs.writeFile(namefile, k.keyfile, function(err) { |
| 156 | + fs.writeFile(namefile, keyfile, function(err) { |
135 | 157 | if (err) return cb(err) |
136 | | - delete k.keyfile |
137 | | - cb(null, k) |
| 158 | + cb(null, keys) |
138 | 159 | }) |
139 | 160 | }) |
140 | 161 | } |
141 | 162 | |
142 | | -exports.createSync = function(namefile) { |
| 163 | +exports.createSync = function(namefile, curve, legacy) { |
143 | 164 | namefile = toNameFile(namefile) |
144 | | - var k = constructKeys() |
| 165 | + var keys = exports.generate(curve) |
| 166 | + var keyfile = constructKeys(keys, legacy) |
145 | 167 | mkdirp.sync(path.dirname(namefile)) |
146 | | - fs.writeFileSync(namefile, k.keyfile) |
147 | | - delete k.keyfile |
148 | | - return k |
| 168 | + fs.writeFileSync(namefile, keyfile) |
| 169 | + return keys |
149 | 170 | } |
150 | 171 | |
151 | 172 | exports.loadOrCreate = function (namefile, cb) { |
152 | 173 | namefile = toNameFile(namefile) |
154 | 175 | if(!err) return cb(null, keys) |
155 | 176 | exports.create(namefile, cb) |
156 | 177 | }) |
157 | 178 | } |
| 179 | + |
158 | 180 | exports.loadOrCreateSync = function (namefile) { |
159 | 181 | namefile = toNameFile(namefile) |
160 | 182 | try { |
161 | 183 | return exports.loadSync(namefile) |
163 | 185 | return exports.createSync(namefile) |
164 | 186 | } |
165 | 187 | } |
166 | 188 | |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | +var curves = { |
| 193 | + ed25519 : require('./sodium'), |
| 194 | + k256 : ecc |
| 195 | +} |
| 196 | + |
| 197 | +function 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 | + |
167 | 216 | |
168 | | - |
| 217 | + |
169 | 218 | |
170 | | -exports.generate = function () { |
171 | | - return keysToBase64(ecc.restore(curve, crypto.randomBytes(32))) |
| 219 | +exports.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) |
172 | 226 | } |
173 | 227 | |
174 | 228 | |
175 | 229 | |
| 230 | + |
176 | 231 | exports.sign = function (keys, hash) { |
| 232 | + if(isObject(hash)) |
| 233 | + throw new Error('hash should be base64 string, did you mean signObj(private, unsigned_obj)') |
177 | 234 | var hashTag = hash.substring(hash.indexOf('.')) |
178 | | - return tag( |
179 | | - ecc.sign(curve, keysToBuffer(keys), hashToBuffer(hash)), |
180 | | - hashTag + '.k256' |
181 | | - ) |
| 235 | + var curve = getCurve(keys) |
| 236 | + |
| 237 | + return curves[curve] |
| 238 | + .sign(toBuffer(keys.private || keys), toBuffer(hash)) |
| 239 | + .toString('base64')+'.blake2s.'+curve |
| 240 | + |
182 | 241 | } |
183 | 242 | |
184 | 243 | |
185 | 244 | |
186 | | -exports.verify = function (pub, sig, hash) { |
187 | | - return ecc.verify(curve, keysToBuffer(pub), toBuffer(sig), hashToBuffer(hash)) |
| 245 | +exports.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 | + ) |
188 | 253 | } |
189 | 254 | |
| 255 | + |
| 256 | + |
190 | 257 | function createHash() { |
191 | 258 | return new Blake2s() |
192 | 259 | } |
193 | 260 | |
212 | 279 | var h = hash(str, 'utf8') |
213 | 280 | return exports.verify(keys, sig, h) |
214 | 281 | } |
215 | 282 | |
| 283 | + |
| 284 | + |
216 | 285 | exports.signObjHmac = function (secret, obj) { |
217 | 286 | obj = clone(obj) |
218 | 287 | var str = JSON.stringify(obj, null, 2) |
219 | 288 | obj.hmac = exports.hmac(str, secret) |
236 | 305 | public: keys.public |
237 | 306 | }) |
238 | 307 | } |
239 | 308 | |
240 | | -exports.codec = { |
241 | | - decode: function (string) { |
242 | | - return JSON.parse(string) |
243 | | - }, |
244 | | - encode: function (obj) { |
245 | | - return JSON.stringify(obj, null, 2) |
246 | | - }, |
247 | | - buffer: false |
248 | | -} |
249 | | - |
250 | | -exports.keys = exports |