Преглед на файлове

v1.2.6: Make neuter public, replace bin with installer

tags/v1.2.6
AJ ONeal преди 6 месеца
родител
ревизия
34d0ca9f13
променени са 3 файла, в които са добавени 13 реда и са изтрити 609 реда
  1. 6
    605
      bin/keypairs.js
  2. 5
    2
      keypairs.js
  3. 2
    2
      package.json

+ 6
- 605
bin/keypairs.js Целия файл

@@ -1,611 +1,12 @@
1 1
 #!/usr/bin/env node
2 2
 'use strict';
3 3
 
4
-// I'm not proud of the way this code is written - it snowballed from a thought
5
-// experiment into a full-fledged CLI, literally overnight (as it it's 4:30am
6
-// right now), but I love what it accomplishes!
7
-
8
-/*global Promise*/
9
-var fs = require('fs');
10
-var Rasha = require('rasha');
11
-var Eckles = require('eckles');
12
-var Keypairs = require('../');
13
-var pkg = require('../package.json');
14
-
15
-var args = process.argv.slice(2);
16
-var opts = { keys: [], jwts: [], jwss: [], payloads: [], names: [], filenames: [], files: [] };
17
-var conflicts = {
18
-  'namedCurve': 'modulusLength'
19
-, 'public': 'private'
20
-};
21
-Object.keys(conflicts).forEach(function (k) {
22
-  conflicts[conflicts[k]] = k;
23
-});
24
-function set(key, val) {
25
-  if (opts[conflicts[key]]) {
26
-    console.error("cannot set '" + key + "' to '" + val + "': '" + conflicts[key] + "' already set as '" + opts[conflicts[key]] + "'");
27
-    process.exit(1);
28
-  }
29
-  if (opts[key]) {
30
-    console.error("cannot set '" + key + "' to '" + val + "': already set as '" + opts[key] + "'");
31
-    process.exit(1);
32
-  }
33
-  opts[key] = val;
34
-}
35
-
36
-// duck type all the things
37
-// TODO segment off by actions (gen, sign, verify) and allow parse/convert or gen before sign
38
-args.forEach(function (arg) {
39
-  var larg = arg.toLowerCase().replace(/[^\w]/g, '');
40
-  var narg = parseInt(arg, 10) || 0;
41
-  if (narg.toString() !== arg) {
42
-    // i.e. 2048.pem is a valid file name
43
-    narg = false;
44
-  }
45
-
46
-  if ('version' === arg) {
47
-    console.info(pkg.name, 'v' + pkg.version);
48
-    process.exit(0);
49
-  }
50
-
51
-  if (setTimes(arg)) {
52
-    return;
53
-  }
54
-  if (setIssuer(arg)) {
55
-    return;
56
-  }
57
-  if (setSubject(arg)) {
58
-    return;
59
-  }
60
-
61
-  if ('ecdsa' === larg || 'ec' === larg) {
62
-    set('kty', "EC");
63
-    if (opts.modulusLength) {
64
-      console.error("EC keys do not have bit lengths such as '" + opts.modulusLength + "'. Choose either the P-256 or P-384 'curve' instead.");
65
-      process.exit(1);
66
-    }
67
-  }
68
-  if ('rsa' === larg) {
69
-    set('kty', "RSA");
70
-    if (opts.namedCurve) {
71
-      console.error("RSA keys do not have curves such as '" + opts.namedCurve + "'. Choose a modulus bit length, such as 2048 instead.");
72
-      process.exit(1);
73
-    }
74
-    return;
75
-  }
76
-
77
-  // P-384
78
-  if (-1 !== ['256', 'p256', 'prime256v1', 'secp256r1'].indexOf(larg)) {
79
-    set('namedCurve', "P-256");
80
-    return;
81
-  }
82
-
83
-  // P-384
84
-  if (-1 !== ['384', 'p384', 'secp384r1'].indexOf(larg)) {
85
-    set('namedCurve', "P-384");
86
-    return;
87
-  }
88
-
89
-  // RSA Modulus Length
90
-  if (narg) {
91
-    if (narg < 2048 || narg % 8 || narg > 8192) {
92
-      console.error("RSA modulusLength must be >=2048, <=8192 and divisible by 8");
93
-      process.exit(1);
94
-    }
95
-    set('modulusLength', narg);
96
-    return;
97
-  }
98
-
99
-  // Booleans
100
-  if (-1 !== [ 'private', 'public', 'nocompact', 'nofetch', 'debug', 'overwrite' ].indexOf(arg)) {
101
-    set(arg, true);
4
+var cmd = "npm install --global keypairs-cli";
5
+console.error(cmd);
6
+require('child_process').exec(cmd, function (err) {
7
+  if (err) {
8
+    console.error(err);
102 9
     return;
103 10
   }
104
-  if ('uncompressed' === arg) {
105
-    set('uncompressed', true);
106
-    return;
107
-  }
108
-  if (-1 !== [ 'gen', 'sign', 'verify', 'decode' ].indexOf(arg)) {
109
-    set('action', arg);
110
-    return;
111
-  }
112
-
113
-  // Key format and encoding
114
-  if (-1 !== [ 'spki', 'pkix' ].indexOf(larg)) {
115
-    set('pubFormat', 'spki');
116
-    return;
117
-  }
118
-  // TODO add ssh private key support (it's already built in jwk-to-ssh)
119
-  if ('ssh' === larg) {
120
-    set('pubFormat', 'ssh');
121
-    return;
122
-  }
123
-  if (-1 !== [ 'openssh', 'sec1', 'pkcs1', 'pkcs8' ].indexOf(larg)) {
124
-    // pkcs1 can be public or private, it's ambiguous
125
-    if (!opts.privFormat) {
126
-      set('privFormat', larg);
127
-      return;
128
-    }
129
-
130
-    if ('pkcs1' === larg || 'ssh' === larg) {
131
-      set('pubFormat', larg);
132
-      return;
133
-    }
134
-    if ('openssh' === larg) {
135
-      console.warn("specifying 'openssh' twice? ...assuming that you meant 'ssh'");
136
-      set('pubFormat', 'ssh');
137
-      return;
138
-    }
139
-    if ('pkcs8' === larg) {
140
-      console.warn("specifying 'pkcs8' twice? ...assuming that you meant 'spki' (pkix)");
141
-      set('pubFormat', 'spki');
142
-      return;
143
-    }
144
-    if ('sec1' === larg) {
145
-      console.warn("specifying 'sec1' twice? ...assuming that you meant 'spki' (pkix)");
146
-      set('pubFormat', 'spki');
147
-      return;
148
-    }
149
-    return;
150
-  }
151
-  if ('jwk' === larg) {
152
-    if (!opts.privFormat) {
153
-      set('privFormat', larg);
154
-    } else {
155
-      set('pubFormat', larg);
156
-    }
157
-    return;
158
-  }
159
-  if ('pem' === larg || 'der' === larg || 'json' === larg) {
160
-    if (!opts.privEncoding) {
161
-      set('privEncoding', larg);
162
-    } else {
163
-      set('pubEncoding', larg);
164
-    }
165
-    return;
166
-  }
167
-
168
-  // Filename
169
-  try {
170
-    fs.accessSync(arg);
171
-    opts.filenames.push(arg);
172
-    opts.names.push({ taken: true, name: arg });
173
-    if (!guessFile(arg)) {
174
-      opts.files.push(arg);
175
-    }
176
-    return;
177
-  } catch(e) { /* not keypath */ }
178
-
179
-  // Test for JWK-ness / payload-ness
180
-  if (guess(arg)) {
181
-    return;
182
-  }
183
-
184
-  // Test for JWT-ness
185
-  if (setJwt(arg)) {
186
-    return;
187
-  }
188
-
189
-  // Possibly the output file
190
-  if (opts.names.length < 3) {
191
-    opts.names.push({ taken: false, name: arg });
192
-    return;
193
-  }
194
-  // check if it's a valid output key
195
-
196
-  console.error("too many arguments or didn't understand argument '" + arg + "'");
197
-  if (opts.debug) {
198
-    console.warn(opts);
199
-  }
200
-  process.exit(1);
11
+  console.info("Run 'keypairs help' to see what you can do!");
201 12
 });
202
-
203
-function guessFile(filename) {
204
-  try {
205
-    // TODO der support
206
-    var txt = fs.readFileSync(filename).toString('utf8');
207
-    return guess(txt, filename);
208
-  } catch(e) {
209
-    return false;
210
-  }
211
-}
212
-
213
-function guess(txt, filename) {
214
-  try {
215
-    var json = JSON.parse(txt);
216
-    if (-1 !== [ 'RSA', 'EC' ].indexOf(json.kty)) {
217
-      opts.keys.push({ raw: txt, jwk: json, filename: filename });
218
-      return true;
219
-    } else if (json.signature && json.payload && (json.header || json.protected)) {
220
-      opts.jwss.push(json);
221
-      return true;
222
-    } else {
223
-      opts.payloads.push(txt);
224
-      return true;
225
-    }
226
-  } catch(e) {
227
-    try {
228
-      var jwk = Eckles.importSync({ pem: txt });
229
-      // pem._string = txt;
230
-      opts.keys.push({ jwk: jwk, pem: true, raw: txt });
231
-      return true;
232
-    } catch(e) {
233
-      try {
234
-        var jwk = Rasha.importSync({ pem: txt });
235
-        // pem._string = txt;
236
-        opts.keys.push({ jwk: jwk, pem: true, raw: txt });
237
-        return true;
238
-      } catch(e) {
239
-        // ignore
240
-      }
241
-    }
242
-  }
243
-  return false;
244
-}
245
-
246
-// node bin/keypairs.js debug spki pem json pkcs1 ~/.ssh/id_rsa.pub foo.pem bar.pem 'abc.abc.abc' '{"kty":"EC"}' '{}' '{"signature":"x", "payload":"x", "header":"x"}' '{"signature":"x", "payload":"x", "protected":"x"}' verify
247
-if (opts.debug) {
248
-  console.warn(opts);
249
-}
250
-
251
-var kp;
252
-
253
-if ('gen' === opts.action || (!opts.action && !opts.names.length)) {
254
-  if (opts.names.length > 2) {
255
-    console.error("there should only be two output files at most when generating keypairs");
256
-    console.error(opts.names.map(function (t) { return t.name; }));
257
-    process.exit(1);
258
-    return;
259
-  }
260
-
261
-  kp = genKeypair();
262
-} else if ('decode' === opts.action) {
263
-  if (!opts.jwts.length) {
264
-    console.error("no JWTs specified to decode");
265
-    process.exit(1);
266
-    return;
267
-  }
268
-
269
-  return Promise.all(opts.jwts.map(function (jwt, i) {
270
-    try {
271
-      var decoded = decodeJwt(jwt);
272
-      console.info("Decoded #" + (i + 1) + ":");
273
-      console.info(JSON.stringify(decoded, null, 2));
274
-    } catch(e) {
275
-      console.error("Failed to decode #" + (i + 1) + ":");
276
-      console.error(e);
277
-    }
278
-  }));
279
-} else if ('verify' === opts.action || (!opts.action && opts.jwts.length)) {
280
-  if (!opts.jwts.length) {
281
-    console.error("no JWTs specified to verify");
282
-    process.exit(1);
283
-    return;
284
-  }
285
-
286
-  return Promise.all(opts.jwts.map(function (jwt, i) {
287
-    return require('keyfetch').verify({ jwt: jwt }).then(function (decoded) {
288
-      console.info("Verified #" + (i + 1) + ":");
289
-      console.info(JSON.stringify(decoded, null, 2));
290
-    }).catch(function (err) {
291
-      console.error("Failed to verify #" + (i + 1) + ":");
292
-      console.error(err);
293
-    });
294
-  }));
295
-} else {
296
-  if (opts.names.length > 3) {
297
-    console.error("there should only be one input file and up to two output files when converting keypairs");
298
-    console.error(opts.names.map(function (t) { return t.name; }));
299
-    process.exit(1);
300
-    return;
301
-  }
302
-  var pair = readKeypair();
303
-  pair._convert = true;
304
-  kp = Promise.resolve(pair);
305
-}
306
-
307
-if ('sign' === opts.action) {
308
-  return kp.then(function (pair) {
309
-    var jwk = pair.private;
310
-    if (!jwk || !jwk.d) {
311
-      console.error("the first key was not a private key");
312
-      console.error(opts.names.map(function (t) { return t.name; }));
313
-      process.exit(1);
314
-      return;
315
-    }
316
-    if (!opts.payloads.length) {
317
-      opts.payloads.push('{}');
318
-    }
319
-    return Promise.all(opts.payloads.map(function (payload) {
320
-      var claims = JSON.parse(payload);
321
-      if (!claims.iss) { claims.iss = opts.issuer; }
322
-      if (!claims.iss) { console.warn("No issuer given, token will not be verifiable"); }
323
-      if (!claims.sub) { claims.sub = opts.sub; }
324
-      if (!claims.exp) {
325
-        if (!opts.expiresAt) { setTimes('15m'); }
326
-        claims.exp = opts.expiresAt;
327
-      }
328
-      if (!claims.iat) { claims.iat = opts.issuedAt; }
329
-      if (!claims.nbf) { claims.nbf = opts.nbf; }
330
-      return Keypairs.signJwt({ jwk: pair.private, claims: claims }).then(function (jwt) {
331
-        console.info(jwt);
332
-      });
333
-    }));
334
-  });
335
-} else {
336
-  return kp.then(function (pair) {
337
-    if (pair._convert) {
338
-      return convertKeypair(pair);
339
-    }
340
-  });
341
-}
342
-
343
-function readKeypair() {
344
-  // note that the jwk may be a string
345
-  var keyopts = opts.keys.shift();
346
-  var jwk = keyopts && keyopts.jwk;
347
-  if (!jwk) {
348
-    console.error("no keys could be parsed from the given arguments");
349
-    console.error(opts.names.map(function (t) { return t.name; }));
350
-    process.exit(1);
351
-    return;
352
-  }
353
-
354
-  // omit the primary private key from the list of actual (or soon-to-be) files
355
-  if (keyopts.filename) {
356
-    opts.names = opts.names.filter(function (name) {
357
-      return name.name !== keyopts.filename;
358
-    });
359
-  }
360
-
361
-  var pair = { private: null, public: null, pem: keyopts.pem, raw: keyopts.raw };
362
-  if (jwk.d) {
363
-    pair.private = jwk;
364
-  }
365
-  pair.public = Keypairs._neuter({ jwk: jwk });
366
-  return pair;
367
-}
368
-
369
-// Note: some of the conditions can be factored out
370
-// this was all built in high-speed iterative during the 3ams+
371
-function convertKeypair(pair) {
372
-  //var pair = readKeypair();
373
-
374
-  var ps = [];
375
-  // if it's private only, or if it's not public-only, produce the private key
376
-  if (pair.private || !opts.public) {
377
-    // if it came from pem (or is explicitly json), it should go to jwk
378
-    // otherwise, if it came from jwk, it should go to pem
379
-    if (((!opts.privEncoding && pair.pem) || 'json' === opts.privEncoding)
380
-      && ((!opts.privFormat && pair.pem) || 'jwk' === opts.privFormat)) {
381
-      ps.push(Promise.resolve(pair.private));
382
-    } else {
383
-      ps.push(Keypairs.export({ jwk: pair.private, format: opts.privFormat, encoding: opts.privEncoding }));
384
-    }
385
-  }
386
-
387
-  // if it's not private key only, we want to produce the public key
388
-  if (!opts.private) {
389
-    if (opts.public) {
390
-      // if it's public-only the ambigious options will fall to the private key
391
-      // so we need to fix that
392
-      if (!opts.pubFormat) { opts.pubFormat = opts.privFormat; }
393
-      if (!opts.pubEncoding) { opts.pubEncoding = opts.privEncoding; }
394
-    }
395
-
396
-    // same as above - swap formats by default
397
-    if (((!opts.pubEncoding && pair.pem) || 'json' === opts.pubEncoding)
398
-      && ((!opts.pubFormat && pair.pem) || 'jwk' === opts.pubFormat)) {
399
-      ps.push(Promise.resolve(pair.public));
400
-    } else {
401
-      ps.push(Keypairs.export({ jwk: pair.public, format: opts.pubFormat, encoding: opts.pubEncoding, public: true }));
402
-    }
403
-  }
404
-
405
-  return Promise.all(ps).then(function (exported) {
406
-    // start with the first key, annotating if it should be public
407
-    var index = 0;
408
-    var key = stringifyIfJson(index, opts.public);
409
-
410
-    // re: opts.names
411
-    // if we're only doing the public key we can end early
412
-    // (if the source key was from a file and was in opts.names,
413
-    // we're safe here because we already removed it earlier)
414
-
415
-    if (opts.public) {
416
-      if (opts.names.length) {
417
-        writeFile(opts.names[index].name, key, !opts.public);
418
-      } else {
419
-        // output public keys to stderr
420
-        printPublic(key);
421
-      }
422
-      // end <-- we're not outputting other keys
423
-      return;
424
-    }
425
-
426
-    // private key stuff
427
-    if (opts.names.length >= 1) {
428
-      writeFile(opts.names[index].name, key, true);
429
-    } else {
430
-      printPrivate(key);
431
-    }
432
-
433
-    // pub key stuff
434
-    // we have to output the private key,
435
-    // but the public key can be derived at any time
436
-    // so we don't need to put the same noise to the screen
437
-    if (!opts.private && opts.names.length >= 2) {
438
-      index = 1;
439
-      key = stringifyIfJson(index, false);
440
-      writeFile(opts.names[index].name, key, false);
441
-    }
442
-
443
-    return pair;
444
-
445
-    function stringifyIfJson(i, pub) {
446
-      if (exported[i].kty) {
447
-        if (pub) {
448
-          if (opts.expiresAt) { exported[i].exp = opts.expiresAt; }
449
-          exported[i].use = "sig";
450
-        }
451
-        exported[i] = JSON.stringify(exported[i]);
452
-      }
453
-      return exported[i];
454
-    }
455
-  });
456
-}
457
-
458
-function genKeypair() {
459
-  return Keypairs.generate({
460
-    kty: opts.kty
461
-  , modulusLength: opts.modulusLength
462
-  , namedCurve: opts.namedCurve
463
-  }).then(function (pair) {
464
-    // always generate as jwk by default
465
-    var ps = [];
466
-    if ((!opts.privEncoding || 'json' === opts.privEncoding) && (!opts.privFormat || 'jwk' === opts.privFormat)) {
467
-      ps.push(Promise.resolve(pair.private));
468
-    } else {
469
-      ps.push(Keypairs.export({ jwk: pair.private, format: opts.privFormat, encoding: opts.privEncoding }));
470
-    }
471
-    if ((!opts.pubEncoding || 'json' === opts.pubEncoding) && (!opts.pubFormat || 'jwk' === opts.pubFormat)) {
472
-      ps.push(Promise.resolve(pair.public));
473
-    } else {
474
-      ps.push(Keypairs.export({ jwk: pair.public, format: opts.pubFormat, encoding: opts.pubEncoding, public: true }));
475
-    }
476
-    return Promise.all(ps).then(function (arr) {
477
-      if (arr[0].kty) {
478
-        arr[0] = JSON.stringify(arr[0]);
479
-      }
480
-      if (arr[1].kty) {
481
-        if (opts.expiresAt) { arr[1].exp = opts.expiresAt; }
482
-        arr[1].use = "sig";
483
-        arr[1] = JSON.stringify(arr[1]);
484
-      }
485
-      if (!opts.names.length) {
486
-        console.info(arr[0] + "\n");
487
-        console.warn(arr[1] + "\n");
488
-      }
489
-      if (opts.names.length >= 1) {
490
-        writeFile(opts.names[0].name, arr[0], true);
491
-        if (!opts.private && opts.names.length >= 2) {
492
-          writeFile(opts.names[1].name, arr[1]);
493
-        }
494
-      }
495
-
496
-      return pair;
497
-    });
498
-  });
499
-}
500
-
501
-function writeFile(name, key, priv) {
502
-  var overwrite;
503
-  try {
504
-    fs.accessSync(name);
505
-    overwrite = opts.overwrite;
506
-    if (!opts.overwrite) {
507
-      if (priv) {
508
-        // output private keys to stdout
509
-        console.info(key + "\n");
510
-      } else {
511
-        // output public keys to stderr
512
-        console.warn(key + "\n");
513
-      }
514
-      console.error("'" + name + "' exists! force overwrite with 'overwrite'");
515
-      process.exit(1);
516
-      return;
517
-    }
518
-  } catch(e) {
519
-    // the file does not exist (or cannot be accessed)
520
-  }
521
-  fs.writeFileSync(name, key);
522
-  if (overwrite) {
523
-    console.info("Overwrote " + (priv ? "private" : "public") + " key at '" + name + "'");
524
-  } else {
525
-    console.info("Wrote " + (priv ? "private" : "public") + " key to '" + name + "'");
526
-  }
527
-}
528
-
529
-function setJwt(arg) {
530
-  try {
531
-    var jwt = arg.match(/^([\w-]+)\.([\w-]+)\.([\w-]+)$/);
532
-    // make sure header is a JWT header
533
-    JSON.parse(Buffer.from(jwt[1], 'base64'));
534
-    opts.jwts.push(arg);
535
-    return true;
536
-  } catch(e) {
537
-    // ignore
538
-  }
539
-}
540
-
541
-function setSubject(arg) {
542
-  if (!/.+@[a-z0-9_-]+\.[a-z0-9_-]+/i.test(arg)) {
543
-    return false;
544
-  }
545
-
546
-  opts.subject = arg;
547
-  return false;
548
-}
549
-
550
-function setIssuer(arg) {
551
-  if (!/^https?:\/\/[a-z0-9_-]+\.[a-z0-9_-]+/i.test(arg)) {
552
-    return false;
553
-  }
554
-
555
-  try {
556
-    new URL(arg);
557
-    opts.issuer = arg.replace(/\/$/, '');
558
-    return true;
559
-  } catch(e) {
560
-  }
561
-  return false;
562
-}
563
-
564
-function setTimes(arg) {
565
-  var t = arg.match(/^(\-?\d+)([dhms])$/i);
566
-  if (!t || !t[0]) {
567
-    return false;
568
-  }
569
-
570
-  var num = parseInt(t[1], 10);
571
-  var unit = t[2];
572
-  var mult = 1;
573
-  opts.issuedAt = Math.round(Date.now()/1000);
574
-  switch(unit) {
575
-    // fancy fallthrough, what fun!
576
-    case 'd':
577
-      mult *= 24;
578
-      /*falls through*/
579
-    case 'h':
580
-      mult *= 60;
581
-      /*falls through*/
582
-    case 'm':
583
-      mult *= 60;
584
-      /*falls through*/
585
-    case 's':
586
-      mult *= 1;
587
-  }
588
-  if (!opts.expiresIn) {
589
-    opts.expiresIn = mult * num;
590
-    opts.expiresAt = opts.issuedAt + opts.expiresIn;
591
-  } else {
592
-    opts.nbf = opts.issuedAt + (mult * num);
593
-  }
594
-  return true;
595
-}
596
-
597
-function decodeJwt(jwt) {
598
-  var parts = jwt.split('.');
599
-  return {
600
-    header: JSON.parse(Buffer.from(parts[0], 'base64'))
601
-  , payload: JSON.parse(Buffer.from(parts[1], 'base64'))
602
-  , signature: parts[2] //Buffer.from(parts[2], 'base64')
603
-  };
604
-}
605
-
606
-function printPrivate(key) {
607
-  console.info(key + "\n");
608
-}
609
-function printPublic(key) {
610
-  console.warn(key + "\n");
611
-}

+ 5
- 2
keypairs.js Целия файл

@@ -87,7 +87,9 @@ Keypairs.export = function (opts) {
87 87
   });
88 88
 };
89 89
 
90
-Keypairs._neuter = function (opts) {
90
+// Chopping off the private parts is now part of the public API.
91
+// I thought it sounded a little too crude at first, but it really is the best name in every possible way.
92
+Keypairs.neuter = Keypairs._neuter = function (opts) {
91 93
   // trying to find the best balance of an immutable copy with custom attributes
92 94
   var jwk = {};
93 95
   Object.keys(opts.jwk).forEach(function (k) {
@@ -97,10 +99,11 @@ Keypairs._neuter = function (opts) {
97 99
   });
98 100
   return jwk;
99 101
 };
102
+
100 103
 Keypairs.publish = function (opts) {
101 104
   if ('object' !== typeof opts.jwk || !opts.jwk.kty) { throw new Error("invalid jwk: " + JSON.stringify(opts.jwk)); }
102 105
 
103
-  var jwk = Keypairs._neuter(opts);
106
+  var jwk = Keypairs.neuter(opts);
104 107
 
105 108
   if (!jwk.exp) {
106 109
     if (opts.expiresIn) { jwk.exp = Math.round(Date.now()/1000) + opts.expiresIn; }

+ 2
- 2
package.json Целия файл

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "keypairs",
3
-  "version": "1.2.5",
3
+  "version": "1.2.6",
4 4
   "description": "Lightweight RSA/ECDSA keypair generation and JWK <-> PEM",
5 5
   "main": "keypairs.js",
6 6
   "files": [
@@ -11,7 +11,7 @@
11 11
     "test": "node test.js"
12 12
   },
13 13
   "bin": {
14
-    "keypairs": "bin/keypairs.js"
14
+    "keypairs-install": "bin/keypairs.js"
15 15
   },
16 16
   "repository": {
17 17
     "type": "git",

Loading…
Отказ
Запис