mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-20 03:26:47 +08:00
feat(server): improve crypto (#13822)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Resolved cryptographic compatibility issues with OpenSSL 3.x. * **New Features** * Broader support for multiple key types for signing and verification. * OTP generation adjusted (affects digit distribution/values). * **Refactor** * Internal cryptographic key handling reworked for improved stability, compatibility, and multi-key-type support. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2,13 +2,17 @@ import {
|
||||
createCipheriv,
|
||||
createDecipheriv,
|
||||
createHash,
|
||||
createPrivateKey,
|
||||
createPublicKey,
|
||||
createSign,
|
||||
createVerify,
|
||||
generateKeyPairSync,
|
||||
type KeyObject,
|
||||
randomBytes,
|
||||
randomInt,
|
||||
sign,
|
||||
timingSafeEqual,
|
||||
verify,
|
||||
} from 'node:crypto';
|
||||
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
@@ -32,20 +36,31 @@ function generatePrivateKey(): string {
|
||||
namedCurve: 'prime256v1',
|
||||
});
|
||||
|
||||
// Export EC private key as PKCS#8 PEM. This avoids OpenSSL 3.x decoder issues
|
||||
// in Node.js 22 when later deriving the public key via createPublicKey.
|
||||
const key = privateKey.export({
|
||||
type: 'sec1',
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
});
|
||||
|
||||
return key.toString('utf8');
|
||||
}
|
||||
|
||||
function generatePublicKey(privateKey: string) {
|
||||
return createPublicKey({
|
||||
key: Buffer.from(privateKey),
|
||||
})
|
||||
.export({ format: 'pem', type: 'spki' })
|
||||
.toString('utf8');
|
||||
function parseKey(privateKey: string) {
|
||||
const keyBuf = Buffer.from(privateKey);
|
||||
let priv: KeyObject;
|
||||
try {
|
||||
priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'pkcs8' });
|
||||
} catch (e1) {
|
||||
try {
|
||||
priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'sec1' });
|
||||
} catch (e2) {
|
||||
// As a last resort rely on auto-detection
|
||||
priv = createPrivateKey(keyBuf);
|
||||
}
|
||||
}
|
||||
const pub = createPublicKey(priv);
|
||||
return { priv, pub };
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -53,8 +68,8 @@ export class CryptoHelper implements OnModuleInit {
|
||||
logger = new Logger(CryptoHelper.name);
|
||||
|
||||
keyPair!: {
|
||||
publicKey: Buffer;
|
||||
privateKey: Buffer;
|
||||
publicKey: KeyObject;
|
||||
privateKey: KeyObject;
|
||||
sha256: {
|
||||
publicKey: Buffer;
|
||||
privateKey: Buffer;
|
||||
@@ -87,11 +102,14 @@ export class CryptoHelper implements OnModuleInit {
|
||||
|
||||
private setup() {
|
||||
const privateKey = this.config.crypto.privateKey || generatePrivateKey();
|
||||
const publicKey = generatePublicKey(privateKey);
|
||||
const { priv, pub } = parseKey(privateKey);
|
||||
const publicKey = pub
|
||||
.export({ format: 'pem', type: 'spki' })
|
||||
.toString('utf8');
|
||||
|
||||
this.keyPair = {
|
||||
publicKey: Buffer.from(publicKey),
|
||||
privateKey: Buffer.from(privateKey),
|
||||
publicKey: pub,
|
||||
privateKey: priv,
|
||||
sha256: {
|
||||
publicKey: this.sha256(publicKey),
|
||||
privateKey: this.sha256(privateKey),
|
||||
@@ -99,11 +117,23 @@ export class CryptoHelper implements OnModuleInit {
|
||||
};
|
||||
}
|
||||
|
||||
private get keyType() {
|
||||
return (this.keyPair.privateKey.asymmetricKeyType as string) || 'ec';
|
||||
}
|
||||
|
||||
sign(data: string) {
|
||||
const sign = createSign('rsa-sha256');
|
||||
sign.update(data, 'utf-8');
|
||||
sign.end();
|
||||
return `${data},${sign.sign(this.keyPair.privateKey, 'base64')}`;
|
||||
const input = Buffer.from(data, 'utf-8');
|
||||
if (this.keyType === 'ed25519') {
|
||||
// Ed25519 signs the message directly (no pre-hash)
|
||||
const sig = sign(null, input, this.keyPair.privateKey);
|
||||
return `${data},${sig.toString('base64')}`;
|
||||
} else {
|
||||
// ECDSA with SHA-256 for EC keys
|
||||
const sign = createSign('sha256');
|
||||
sign.update(input);
|
||||
sign.end();
|
||||
return `${data},${sign.sign(this.keyPair.privateKey, 'base64')}`;
|
||||
}
|
||||
}
|
||||
|
||||
verify(signatureWithData: string) {
|
||||
@@ -111,10 +141,18 @@ export class CryptoHelper implements OnModuleInit {
|
||||
if (!signature) {
|
||||
return false;
|
||||
}
|
||||
const verify = createVerify('rsa-sha256');
|
||||
verify.update(data, 'utf-8');
|
||||
verify.end();
|
||||
return verify.verify(this.keyPair.privateKey, signature, 'base64');
|
||||
const input = Buffer.from(data, 'utf-8');
|
||||
const sigBuf = Buffer.from(signature, 'base64');
|
||||
if (this.keyType === 'ed25519') {
|
||||
// Ed25519 verifies the message directly
|
||||
return verify(null, input, this.keyPair.publicKey, sigBuf);
|
||||
} else {
|
||||
// ECDSA with SHA-256
|
||||
const verify = createVerify('sha256');
|
||||
verify.update(input);
|
||||
verify.end();
|
||||
return verify.verify(this.keyPair.publicKey, sigBuf);
|
||||
}
|
||||
}
|
||||
|
||||
encrypt(data: string) {
|
||||
@@ -179,7 +217,7 @@ export class CryptoHelper implements OnModuleInit {
|
||||
let otp = '';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
otp += this.randomInt(0, 9).toString();
|
||||
otp += this.randomInt(0, 10).toString();
|
||||
}
|
||||
|
||||
return otp;
|
||||
|
||||
Reference in New Issue
Block a user