Files: dbfcdc3e700706a37cf8e462ae1d1560d58ac44f / index.js
5676 bytesRaw
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 |
9 | var createHmac = require('hmac') |
10 | var deepEqual = require('deep-equal') |
11 | |
12 | function 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 | |
21 | function hash (data, enc) { |
22 | return new Blake2s().update(data, enc).digest('base64') + '.blake2s' |
23 | } |
24 | |
25 | |
26 | function isHash (data) { |
27 | return isString(data) && /^[A-Za-z0-9\/+]{43}=\.blake2s$/.test(data) |
28 | } |
29 | |
30 | function isObject (o) { |
31 | return 'object' === typeof o |
32 | } |
33 | |
34 | exports.isHash = isHash |
35 | exports.hash = hash |
36 | |
37 | function isString(s) { |
38 | return 'string' === typeof s |
39 | } |
40 | |
41 | function empty(v) { return !!v } |
42 | |
43 | function constructKeys() { |
44 | var privateKey = crypto.randomBytes(32) |
45 | var k = keysToBase64(ecc.restore(k256, privateKey)) |
46 | k.keyfile = [ |
47 | '# this is your SECRET name.', |
48 | '# this name gives you magical powers.', |
49 | '# with it you can mark your messages so that your friends can verify', |
50 | '# that they really did come from you.', |
51 | '#', |
52 | '# if any one learns this name, they can use it to destroy your identity', |
53 | '# NEVER show this to anyone!!!', |
54 | '', |
55 | k.private.toString('hex'), |
56 | '', |
57 | '# WARNING! It\'s vital that you DO NOT edit OR share your secret name', |
58 | '# instead, share your public name', |
59 | '# your public name: ' + k.id.toString('hex') |
60 | ].join('\n') |
61 | return k |
62 | } |
63 | |
64 | |
65 | function toBuffer(buf) { |
66 | if(buf == null) return buf |
67 | return new Buffer(buf.substring(0, buf.indexOf('.')), 'base64') |
68 | } |
69 | |
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 | } |
78 | |
79 | function hashToBuffer(hash) { |
80 | if(!isHash(hash)) throw new Error('sign expects a hash') |
81 | return toBuffer(hash) |
82 | } |
83 | |
84 | function keysToBuffer(key) { |
85 | return isString(key) ? toBuffer(key) : { |
86 | public: toBuffer(key.public), |
87 | private: toBuffer(key.private) |
88 | } |
89 | } |
90 | |
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)) |
103 | } |
104 | |
105 | function tag (key, tag) { |
106 | return key.toString('base64')+'.' + tag.replace(/^\./, '') |
107 | } |
108 | |
109 | var toNameFile = exports.toNameFile = function (namefile) { |
110 | if(isObject(namefile)) |
111 | return path.join(namefile.path, 'secret') |
112 | return namefile |
113 | } |
114 | |
115 | exports.load = function(namefile, cb) { |
116 | namefile = toNameFile(namefile) |
117 | fs.readFile(namefile, 'ascii', function(err, privateKeyStr) { |
118 | if (err) return cb(err) |
119 | try { cb(null, reconstructKeys(privateKeyStr)) } |
120 | catch (e) { cb(err) } |
121 | }) |
122 | } |
123 | |
124 | exports.loadSync = function(namefile) { |
125 | namefile = toNameFile(namefile) |
126 | return reconstructKeys(fs.readFileSync(namefile, 'ascii')) |
127 | } |
128 | |
129 | exports.create = function(namefile, cb) { |
130 | namefile = toNameFile(namefile) |
131 | var k = constructKeys() |
132 | mkdirp(path.dirname(namefile), function (err) { |
133 | if(err) return cb(err) |
134 | fs.writeFile(namefile, k.keyfile, function(err) { |
135 | if (err) return cb(err) |
136 | delete k.keyfile |
137 | cb(null, k) |
138 | }) |
139 | }) |
140 | } |
141 | |
142 | exports.createSync = function(namefile) { |
143 | namefile = toNameFile(namefile) |
144 | var k = constructKeys() |
145 | mkdirp.sync(path.dirname(namefile)) |
146 | fs.writeFileSync(namefile, k.keyfile) |
147 | delete k.keyfile |
148 | return k |
149 | } |
150 | |
151 | exports.loadOrCreate = function (namefile, cb) { |
152 | namefile = toNameFile(namefile) |
153 | exports.load(namefile, function (err, keys) { |
154 | if(!err) return cb(null, keys) |
155 | exports.create(namefile, cb) |
156 | }) |
157 | } |
158 | exports.loadOrCreateSync = function (namefile) { |
159 | namefile = toNameFile(namefile) |
160 | try { |
161 | return exports.loadSync(namefile) |
162 | } catch (err) { |
163 | return exports.createSync(namefile) |
164 | } |
165 | } |
166 | |
167 | //this should return a key pair: |
168 | // {public: Buffer, private: Buffer} |
169 | |
170 | exports.generate = function () { |
171 | return keysToBase64(ecc.restore(curve, crypto.randomBytes(32))) |
172 | } |
173 | |
174 | //takes a public key and a hash and returns a signature. |
175 | //(a signature must be a node buffer) |
176 | exports.sign = function (keys, hash) { |
177 | var hashTag = hash.substring(hash.indexOf('.')) |
178 | return tag( |
179 | ecc.sign(curve, keysToBuffer(keys), hashToBuffer(hash)), |
180 | hashTag + '.k256' |
181 | ) |
182 | } |
183 | |
184 | //takes a public key, signature, and a hash |
185 | //and returns true if the signature was valid. |
186 | exports.verify = function (pub, sig, hash) { |
187 | return ecc.verify(curve, keysToBuffer(pub), toBuffer(sig), hashToBuffer(hash)) |
188 | } |
189 | |
190 | function createHash() { |
191 | return new Blake2s() |
192 | } |
193 | |
194 | exports.hmac = function (data, key) { |
195 | return createHmac(createHash, 64, key) |
196 | .update(data).digest('base64')+'.blake2s.hmac' |
197 | } |
198 | |
199 | exports.signObj = function (keys, obj) { |
200 | var _obj = clone(obj) |
201 | var str = JSON.stringify(_obj, null, 2) |
202 | var h = hash(str, 'utf8') |
203 | _obj.signature = exports.sign(keys, h) |
204 | return _obj |
205 | } |
206 | |
207 | exports.verifyObj = function (keys, obj) { |
208 | obj = clone(obj) |
209 | var sig = obj.signature |
210 | delete obj.signature |
211 | var str = JSON.stringify(obj, null, 2) |
212 | var h = hash(str, 'utf8') |
213 | return exports.verify(keys, sig, h) |
214 | } |
215 | |
216 | exports.signObjHmac = function (secret, obj) { |
217 | obj = clone(obj) |
218 | var str = JSON.stringify(obj, null, 2) |
219 | obj.hmac = exports.hmac(str, secret) |
220 | return obj |
221 | } |
222 | |
223 | exports.verifyObjHmac = function (secret, obj) { |
224 | obj = clone(obj) |
225 | var hmac = obj.hmac |
226 | delete obj.hmac |
227 | var str = JSON.stringify(obj, null, 2) |
228 | var _hmac = exports.hmac(str, secret) |
229 | return deepEqual(hmac, _hmac) |
230 | } |
231 |
Built with git-ssb-web