chattervox
Version:
An AX.25 packet radio chat protocol with support for digital signatures and binary compression. Like IRC over radio waves 📡〰.
174 lines • 5.88 kB
JavaScript
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const crypto_1 = require("crypto");
const elliptic_1 = require("elliptic");
const ec = new elliptic_1.ec('p192');
/**
* @class Keystore
* A class for managing keys, signatures, and signature verification
*/
class Keystore {
/**@constructor
* @param {string} path The path to a JSON keystore file. If path does not exist it is created.
*/
constructor(path) {
this.path = path;
this._keystore = {};
if (!this._exists()) {
this._save();
}
else {
this._keystore = this._load();
}
}
/**
* @method addPublicKey
* @param {string} callsign
* @param {string} pubkeyHex
*/
addPublicKey(callsign, pubkeyHex) {
this._addKey(callsign, pubkeyHex, undefined);
}
// only used for you
/**
* @param {string} callsign
* @returns {object} keypair object
*/
genKeyPair(callsign) {
const key = ec.genKeyPair();
const pub = key.getPublic('hex');
const priv = key.getPrivate('hex');
this._addKey(callsign, pub, priv);
return { public: pub, private: priv, curve: 'p192' };
}
/**
* Remove a Key object from the keystore, using the public key.
* @param {string} callsign
* @param {string} pubkeyHex
* @returns {boolean} True if a key was removed from the keystore.
*/
revoke(callsign, pubkeyHex) {
// callsign not in keystore, no key is revoked
if (!this._keystore.hasOwnProperty(callsign))
return false;
// number of keys before the filter
const length = this._keystore[callsign].length;
this._keystore[callsign] = this._keystore[callsign].filter((key) => {
return key.public !== pubkeyHex;
});
if (this._keystore[callsign].length !== length) {
this._save();
return true;
}
return false;
}
/** Get a list of callsigns.
* @method getCallsigns
* @returns {string[]}
*/
getCallsigns() {
return Object.keys(this._keystore);
}
/**
* @method getPublicKeys
* @param {string} callsign
* @returns {string[]} An array of public keys associated with a call sign.
*/
getPublicKeys(callsign) {
if (!this._keystore.hasOwnProperty(callsign))
return [];
return this._keystore[callsign].map(key => key.public);
}
/**
* Get Key objects that have both public and private keys
* @method getKeyPairs
* @param {string} callsign
* @returns {Key[]} An array of keypair objects that have both { public: 'hex', private: 'hex' }
*/
getKeyPairs(callsign) {
return this.getKeys(callsign).filter(key => {
return typeof key.public === 'string' && typeof key.private === 'string';
});
}
/** Get Key objects that have at least a public key
* @method getKeys
* @param {string} callsign
* @returns {Key[]} An array of key objects that have at least a public key
*/
getKeys(callsign) {
if (!this._keystore.hasOwnProperty(callsign))
return [];
return this._keystore[callsign];
}
/**@method sign
* @param {string} message A string containing a message to sign.
* @param {string} privateHex A private key as a string of hex characters.
* @returns {Buffer} A message signature.
*/
sign(message, privateHex) {
const key = ec.keyFromPrivate(privateHex);
const hash = this.sha256(message);
const signature = key.sign(hash).toDER();
return Buffer.from(signature);
}
/**@method verify
* @param {string} callsign
* @param {string} message
* @param {Buffer} signature
* @returns {boolean} True if the message signature is valid
*/
verify(callsign, message, signature) {
const hash = this.sha256(message);
for (let publicKey of this.getPublicKeys(callsign)) {
const key = ec.keyFromPublic(publicKey, 'hex');
if (key.verify(hash, signature))
return true;
}
return false;
}
/**
* @method sha256
* @param {string} message
* @return {Buffer} The SHA 256 message digest
*/
sha256(message) {
return crypto_1.createHash('sha256')
.update(message, 'utf8')
.digest();
}
_addKey(callsign, publicHex, privateHex) {
if (typeof callsign !== 'string')
throw TypeError('callsign must be a string type');
if (typeof publicHex !== 'string')
throw TypeError('publicHex must be a string type');
if (typeof privateHex !== 'undefined' && typeof privateHex !== 'string')
throw TypeError('if privateHex is not undefined it must be a string type');
if (!this._keystore.hasOwnProperty(callsign)) {
this._keystore[callsign] = [];
}
const key = { public: publicHex, curve: 'p192' };
if (privateHex)
key.private = privateHex;
this._keystore[callsign].push(key);
this._save();
}
_save() {
fs.writeFileSync(this.path, JSON.stringify(this._keystore, null, '\t'));
}
_load() {
return JSON.parse(fs.readFileSync(this.path).toString('utf8'));
}
_exists() {
return fs.existsSync(this.path);
}
}
exports.Keystore = Keystore;
//# sourceMappingURL=Keystore.js.map