import * as plugins from './bunq.plugins'; export class BunqCrypto { private privateKey: string; private publicKey: string; constructor() {} /** * Generate a new RSA key pair for bunq API communication */ public async generateKeyPair(): Promise { const keyPair = plugins.crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); this.privateKey = keyPair.privateKey; this.publicKey = keyPair.publicKey; } /** * Get the public key */ public getPublicKey(): string { if (!this.publicKey) { throw new Error('Public key not generated yet'); } return this.publicKey; } /** * Get the private key */ public getPrivateKey(): string { if (!this.privateKey) { throw new Error('Private key not generated yet'); } return this.privateKey; } /** * Set keys from stored values */ public setKeys(privateKey: string, publicKey: string): void { this.privateKey = privateKey; this.publicKey = publicKey; } /** * Sign data with the private key */ public signData(data: string): string { if (!this.privateKey) { throw new Error('Private key not set'); } const sign = plugins.crypto.createSign('SHA256'); sign.update(data); sign.end(); return sign.sign(this.privateKey, 'base64'); } /** * Verify data with the server's public key */ public verifyData(data: string, signature: string, serverPublicKey: string): boolean { const verify = plugins.crypto.createVerify('SHA256'); verify.update(data); verify.end(); return verify.verify(serverPublicKey, signature, 'base64'); } /** * Create the signing string for bunq API requests */ public createSigningString( method: string, endpoint: string, headers: { [key: string]: string }, body: string = '' ): string { const sortedHeaderNames = Object.keys(headers) .filter(name => name.startsWith('X-Bunq-') || name === 'Cache-Control' || name === 'User-Agent') .sort(); let signingString = `${method} ${endpoint}\n`; for (const headerName of sortedHeaderNames) { signingString += `${headerName}: ${headers[headerName]}\n`; } signingString += '\n'; if (body) { signingString += body; } return signingString; } /** * Create request signature headers */ public createSignatureHeader( method: string, endpoint: string, headers: { [key: string]: string }, body: string = '' ): string { const signingString = this.createSigningString(method, endpoint, headers, body); return this.signData(signingString); } /** * Verify response signature */ public verifyResponseSignature( statusCode: number, headers: { [key: string]: string }, body: string, serverPublicKey: string ): boolean { const responseSignature = headers['x-bunq-server-signature']; if (!responseSignature) { return false; } // Create signing string for response const sortedHeaderNames = Object.keys(headers) .filter(name => name.startsWith('x-bunq-') && name !== 'x-bunq-server-signature') .sort(); let signingString = `${statusCode}\n`; for (const headerName of sortedHeaderNames) { signingString += `${headerName}: ${headers[headerName]}\n`; } signingString += '\n' + body; return this.verifyData(signingString, responseSignature, serverPublicKey); } /** * Generate a random request ID */ public generateRequestId(): string { return plugins.crypto.randomUUID(); } }