Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"license": "GPL-3.0",
"dependencies": {
"curve25519-js": "^0.0.4",
"pino": "^9.11.0",
"pino-pretty": "^13.1.1",
"protobufjs": "6.8.8"
},
"files": [
Expand All @@ -21,5 +23,12 @@
],
"devDependencies": {
"eslint": "6.0.1"
},
"prettier": {
"printWidth": 100,
"tabWidth": 4,
"useTabs": false,
"singleQuote": true,
"trailingComma": "none"
}
}
74 changes: 41 additions & 33 deletions src/curve.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@

'use strict';

const curveJs = require('curve25519-js');
const nodeCrypto = require('crypto');
const { createLogger } = require('./utils/logger');
// from: https://github.com/digitalbazaar/x25519-key-agreement-key-2019/blob/master/lib/crypto.js
const PUBLIC_KEY_DER_PREFIX = Buffer.from([
48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0
]);

const PUBLIC_KEY_DER_PREFIX = Buffer.from([48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0]);

const PRIVATE_KEY_DER_PREFIX = Buffer.from([
48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32
]);

const KEY_BUNDLE_TYPE = Buffer.from([5]);
const baseLogger = createLogger();

const prefixKeyInPublicKey = function (pubKey) {
return Buffer.concat([KEY_BUNDLE_TYPE, pubKey]);
return Buffer.concat([KEY_BUNDLE_TYPE, pubKey]);
};

function validatePrivKey(privKey) {
if (privKey === undefined) {
throw new Error("Undefined private key");
throw new Error('Undefined private key');
}
if (!(privKey instanceof Buffer)) {
throw new Error(`Invalid private key type: ${privKey.constructor.name}`);
Expand All @@ -34,13 +33,18 @@ function scrubPubKeyFormat(pubKey) {
if (!(pubKey instanceof Buffer)) {
throw new Error(`Invalid public key type: ${pubKey.constructor.name}`);
}
if (pubKey === undefined || ((pubKey.byteLength != 33 || pubKey[0] != 5) && pubKey.byteLength != 32)) {
throw new Error("Invalid public key");
if (
pubKey === undefined ||
((pubKey.byteLength != 33 || pubKey[0] != 5) && pubKey.byteLength != 32)
) {
throw new Error('Invalid public key');
}
if (pubKey.byteLength == 33) {
return pubKey.slice(1);
} else {
console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
baseLogger.error(
'WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey'
);
return pubKey;
}
}
Expand All @@ -58,46 +62,50 @@ function unclampEd25519PrivateKey(clampedSk) {
return unclampedSk;
}

exports.getPublicFromPrivateKey = function(privKey) {
exports.getPublicFromPrivateKey = function (privKey) {
const unclampedPK = unclampEd25519PrivateKey(privKey);
const keyPair = curveJs.generateKeyPair(unclampedPK);
return prefixKeyInPublicKey(Buffer.from(keyPair.public));
};

exports.generateKeyPair = function() {
exports.generateKeyPair = function () {
try {
const {publicKey: publicDerBytes, privateKey: privateDerBytes} = nodeCrypto.generateKeyPairSync(
'x25519',
{
const { publicKey: publicDerBytes, privateKey: privateDerBytes } =
nodeCrypto.generateKeyPairSync('x25519', {
publicKeyEncoding: { format: 'der', type: 'spki' },
privateKeyEncoding: { format: 'der', type: 'pkcs8' }
}
});
const pubKey = publicDerBytes.slice(
PUBLIC_KEY_DER_PREFIX.length,
PUBLIC_KEY_DER_PREFIX.length + 32
);
const pubKey = publicDerBytes.slice(PUBLIC_KEY_DER_PREFIX.length, PUBLIC_KEY_DER_PREFIX.length + 32);

const privKey = privateDerBytes.slice(PRIVATE_KEY_DER_PREFIX.length, PRIVATE_KEY_DER_PREFIX.length + 32);


const privKey = privateDerBytes.slice(
PRIVATE_KEY_DER_PREFIX.length,
PRIVATE_KEY_DER_PREFIX.length + 32
);

return {
pubKey: prefixKeyInPublicKey(pubKey),
privKey
};
} catch(e) {
} catch (e) {
const keyPair = curveJs.generateKeyPair(nodeCrypto.randomBytes(32));
return {
privKey: Buffer.from(keyPair.private),
pubKey: prefixKeyInPublicKey(Buffer.from(keyPair.public)),
pubKey: prefixKeyInPublicKey(Buffer.from(keyPair.public))
};
}
};

exports.calculateAgreement = function(pubKey, privKey) {
exports.calculateAgreement = function (pubKey, privKey) {
pubKey = scrubPubKeyFormat(pubKey);
validatePrivKey(privKey);
if (!pubKey || pubKey.byteLength != 32) {
throw new Error("Invalid public key");
throw new Error('Invalid public key');
}

if(typeof nodeCrypto.diffieHellman === 'function') {
if (typeof nodeCrypto.diffieHellman === 'function') {
const nodePrivateKey = nodeCrypto.createPrivateKey({
key: Buffer.concat([PRIVATE_KEY_DER_PREFIX, privKey]),
format: 'der',
Expand All @@ -108,35 +116,35 @@ exports.calculateAgreement = function(pubKey, privKey) {
format: 'der',
type: 'spki'
});

return nodeCrypto.diffieHellman({
privateKey: nodePrivateKey,
publicKey: nodePublicKey,
publicKey: nodePublicKey
});
} else {
const secret = curveJs.sharedKey(privKey, pubKey);
return Buffer.from(secret);
}
};

exports.calculateSignature = function(privKey, message) {
exports.calculateSignature = function (privKey, message) {
validatePrivKey(privKey);
if (!message) {
throw new Error("Invalid message");
throw new Error('Invalid message');
}
return Buffer.from(curveJs.sign(privKey, message));
};

exports.verifySignature = function(pubKey, msg, sig, isInit) {
exports.verifySignature = function (pubKey, msg, sig, isInit) {
pubKey = scrubPubKeyFormat(pubKey);
if (!pubKey || pubKey.byteLength != 32) {
throw new Error("Invalid public key");
throw new Error('Invalid public key');
}
if (!msg) {
throw new Error("Invalid message");
throw new Error('Invalid message');
}
if (!sig || sig.byteLength != 64) {
throw new Error("Invalid signature");
throw new Error('Invalid signature');
}
return isInit ? true : curveJs.verify(pubKey, msg, sig);
};
33 changes: 19 additions & 14 deletions src/queue_job.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// vim: ts=4:sw=4:expandtab
/*
* jobQueue manages multiple queues indexed by device to serialize
* session io ops on the database.
*/

/*
* jobQueue manages multiple queues indexed by device to serialize
* session io ops on the database.
*/
'use strict';

const { createLogger } = require('./utils/logger');

const _queueAsyncBuckets = new Map();
const _gcLimit = 10000;
Expand All @@ -18,7 +19,7 @@ async function _asyncQueueExecutor(queue, cleanup) {
const job = queue[i];
try {
job.resolve(await job.awaitable());
} catch(e) {
} catch (e) {
job.reject(e);
}
}
Expand All @@ -37,17 +38,19 @@ async function _asyncQueueExecutor(queue, cleanup) {
cleanup();
}

module.exports = function(bucket, awaitable) {
module.exports = function (bucket, awaitable) {
const logger = createLogger('QueueJob');

/* Run the async awaitable only when all other async calls registered
* here have completed (or thrown). The bucket argument is a hashable
* key representing the task queue to use. */
if (!awaitable.name) {
// Make debuging easier by adding a name to this function.
Object.defineProperty(awaitable, 'name', {writable: true});
Object.defineProperty(awaitable, 'name', { writable: true });
if (typeof bucket === 'string') {
awaitable.name = bucket;
} else {
console.warn("Unhandled bucket type (for naming):", typeof bucket, bucket);
logger.warn('Unhandled bucket type (for naming):', typeof bucket, bucket);
}
}
let inactive;
Expand All @@ -56,11 +59,13 @@ module.exports = function(bucket, awaitable) {
inactive = true;
}
const queue = _queueAsyncBuckets.get(bucket);
const job = new Promise((resolve, reject) => queue.push({
awaitable,
resolve,
reject
}));
const job = new Promise((resolve, reject) =>
queue.push({
awaitable,
resolve,
reject
})
);
if (inactive) {
/* An executor is not currently active; Start one now. */
_asyncQueueExecutor(queue, () => _queueAsyncBuckets.delete(bucket));
Expand Down
Loading