119 lines
3.6 KiB
JavaScript
119 lines
3.6 KiB
JavaScript
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
// Copyright (c) 2015-2025 MariaDB Corporation Ab
|
|
|
|
'use strict';
|
|
|
|
import PluginAuth from './plugin-auth.js';
|
|
import crypto from 'node:crypto';
|
|
import * as Errors from '../../../misc/errors.js';
|
|
|
|
const pkcs8Ed25519header = Buffer.from([
|
|
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20
|
|
]);
|
|
|
|
/**
|
|
* Standard authentication plugin
|
|
*/
|
|
class ParsecAuth extends PluginAuth {
|
|
#hash;
|
|
constructor(packSeq, compressPackSeq, pluginData, cmdParam, reject, multiAuthResolver) {
|
|
super(cmdParam, multiAuthResolver, reject);
|
|
this.multiAuthResolver = multiAuthResolver;
|
|
this.pluginData = pluginData;
|
|
this.sequenceNo = packSeq;
|
|
this.compressSequenceNo = compressPackSeq;
|
|
}
|
|
|
|
start(out, opts, info) {
|
|
if (!info.extSalt) {
|
|
out.startPacket(this);
|
|
out.writeEmptyPacket(true); // indicate need salt
|
|
this.onPacketReceive = this.requestForSalt;
|
|
} else {
|
|
this.parseExtSalt(Buffer.from(info.extSalt, 'hex'), info);
|
|
this.sendScramble(out, opts, info);
|
|
}
|
|
}
|
|
|
|
requestForSalt(packet, out, opts, info) {
|
|
this.parseExtSalt(packet.readBufferRemaining(), info);
|
|
this.sendScramble(out, opts, info);
|
|
}
|
|
|
|
parseExtSalt(extSalt, info) {
|
|
let pos = 0;
|
|
if (extSalt[pos] == 0x01) pos += 1;
|
|
|
|
if (extSalt.length < 2 || extSalt[pos] !== 0x50 || extSalt[pos + 1] > 20) {
|
|
// expected 'P' for KDF algorithm (PBKDF2) and maximum iteration
|
|
return this.throwError(
|
|
Errors.createFatalError('Wrong parsec authentication format', Errors.client.ER_AUTHENTICATION_BAD_PACKET, info),
|
|
info
|
|
);
|
|
}
|
|
this.iterations = extSalt[pos + 1];
|
|
this.salt = extSalt.slice(pos + 2);
|
|
|
|
// disable for now until https://jira.mariadb.org/browse/MDEV-34846
|
|
// info.extSalt = extSalt.toString('hex');
|
|
}
|
|
|
|
sendScramble(out, opts, info) {
|
|
const derivedKey = crypto.pbkdf2Sync(opts.password || '', this.salt, 1024 << this.iterations, 32, 'sha512');
|
|
const privateKey = toPkcs8der(derivedKey);
|
|
|
|
const rawPublicKey = this.getEd25519PublicKeyFromPrivateKey(derivedKey);
|
|
|
|
this.#hash = Buffer.concat([Buffer.from([0x50, this.iterations]), this.salt, rawPublicKey]);
|
|
|
|
const client_scramble = crypto.randomBytes(32);
|
|
const message = Buffer.concat([this.pluginData, client_scramble]);
|
|
const signature = crypto.sign(null, message, privateKey);
|
|
|
|
out.startPacket(this);
|
|
out.writeBuffer(client_scramble, 0, 32);
|
|
out.writeBuffer(signature, 0, 64);
|
|
out.flushPacket();
|
|
this.emit('send_end');
|
|
this.onPacketReceive = this.multiAuthResolver;
|
|
}
|
|
|
|
getEd25519PublicKeyFromPrivateKey(privateKeyBuffer) {
|
|
// Create a KeyObject from the raw private key
|
|
const privateKey = crypto.createPrivateKey({
|
|
key: Buffer.concat([pkcs8Ed25519header, privateKeyBuffer]),
|
|
format: 'der',
|
|
type: 'pkcs8',
|
|
name: 'ed25519'
|
|
});
|
|
|
|
// Get the corresponding public key
|
|
const publicKey = crypto.createPublicKey(privateKey);
|
|
|
|
// Export the public key in raw format
|
|
return publicKey
|
|
.export({
|
|
type: 'spki',
|
|
format: 'der'
|
|
})
|
|
.subarray(-32); // The last 32 bytes contain the raw key
|
|
}
|
|
|
|
isMitmProof() {
|
|
return true;
|
|
}
|
|
|
|
hash(conf) {
|
|
return this.#hash;
|
|
}
|
|
}
|
|
|
|
const toPkcs8der = (rawB64) => {
|
|
// prefix for a private Ed25519
|
|
const prefixPrivateEd25519 = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
const der = Buffer.concat([prefixPrivateEd25519, rawB64]);
|
|
return crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
};
|
|
|
|
export default ParsecAuth;
|