160 lines
3.8 KiB
JavaScript
160 lines
3.8 KiB
JavaScript
import crypto from 'node:crypto';
|
|
|
|
// Generate a new RSA key pair using Node.js crypto API
|
|
export const generateRSAKeyPair = async () => {
|
|
try {
|
|
console.log('Generating new RSA keypair...');
|
|
|
|
return new Promise((resolve, reject) => {
|
|
crypto.generateKeyPair('rsa', {
|
|
modulusLength: 2048,
|
|
publicKeyEncoding: {
|
|
type: 'spki',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem'
|
|
}
|
|
}, (err, publicKey, privateKey) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
// Convert PEM to base64 (remove headers and newlines)
|
|
const rsaPublic = publicKey
|
|
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|
.replace(/-----END PUBLIC KEY-----/, '')
|
|
.replace(/\n/g, '');
|
|
|
|
resolve({
|
|
rsaPublic,
|
|
rsaPrivate: privateKey
|
|
});
|
|
}
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('Error generating RSA key pair:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const generateClientId = () => {
|
|
try {
|
|
return (crypto.randomBytes(8).toString('hex'));
|
|
} catch (error) {
|
|
logEvent('generateClientId', error, 'error');
|
|
return (null);
|
|
}
|
|
};
|
|
|
|
export const encryptMessage = (message, key) => {
|
|
|
|
let encrypted = '';
|
|
|
|
try {
|
|
|
|
const messageBuffer = Buffer.from(JSON.stringify(message), 'utf8');
|
|
|
|
// Match server.js padding logic exactly
|
|
const paddedBuffer = (messageBuffer.length % 16) !== 0 ?
|
|
Buffer.concat([messageBuffer, Buffer.alloc(16 - (messageBuffer.length % 16))]) :
|
|
messageBuffer;
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
cipher.setAutoPadding(false);
|
|
|
|
encrypted = iv.toString('base64') + '|' + cipher.update(paddedBuffer, '', 'base64') + cipher.final('base64');
|
|
|
|
} catch (error) {
|
|
logEvent('encryptMessage', error, 'error');
|
|
}
|
|
|
|
return (encrypted);
|
|
|
|
};
|
|
|
|
export const decryptMessage = (message, key) => {
|
|
|
|
let decrypted = {};
|
|
|
|
try {
|
|
|
|
const parts = message.split('|');
|
|
const decipher = crypto.createDecipheriv(
|
|
'aes-256-cbc',
|
|
key,
|
|
Buffer.from(parts[0], 'base64')
|
|
);
|
|
|
|
decipher.setAutoPadding(false);
|
|
|
|
const decryptedText = decipher.update(parts[1], 'base64', 'utf8') + decipher.final('utf8');
|
|
decrypted = JSON.parse(decryptedText.replace(/\0+$/, ''));
|
|
|
|
} catch (error) {
|
|
logEvent('decryptMessage', error, 'error');
|
|
}
|
|
|
|
return (decrypted);
|
|
|
|
};
|
|
|
|
export const logEvent = (source, message, level) => {
|
|
if (
|
|
level !== 'debug' ||
|
|
true // config.debug would go here
|
|
) {
|
|
|
|
const date = new Date(),
|
|
dateString = date.getFullYear() + '-' +
|
|
('0' + (date.getMonth() + 1)).slice(-2) + '-' +
|
|
('0' + date.getDate()).slice(-2) + ' ' +
|
|
('0' + date.getHours()).slice(-2) + ':' +
|
|
('0' + date.getMinutes()).slice(-2) + ':' +
|
|
('0' + date.getSeconds()).slice(-2);
|
|
|
|
console.log('[' + dateString + ']', (level ? level.toUpperCase() : 'INFO'), source + (message ? ':' : ''), (message ? message : ''));
|
|
|
|
}
|
|
};
|
|
|
|
export const getTime = () => {
|
|
return (new Date().getTime());
|
|
};
|
|
|
|
export const isString = (value) => {
|
|
return (
|
|
value &&
|
|
Object.prototype.toString.call(value) === '[object String]' ?
|
|
true :
|
|
false
|
|
);
|
|
};
|
|
|
|
export const isArray = (value) => {
|
|
return (
|
|
value &&
|
|
Object.prototype.toString.call(value) === '[object Array]' ?
|
|
true :
|
|
false
|
|
);
|
|
};
|
|
|
|
export const isObject = (value) => {
|
|
return (
|
|
value &&
|
|
Object.prototype.toString.call(value) === '[object Object]' ?
|
|
true :
|
|
false
|
|
);
|
|
};
|
|
|
|
// Note: Since Cloudflare Workers don't have access to global.gc,
|
|
// we're not including the garbage collection interval that's in server.js
|
|
// setInterval(() => {
|
|
// if (global.gc) {
|
|
// global.gc();
|
|
// }
|
|
// }, 30000);
|