Files
bunq/ts/bunq.classes.crypto.ts
Juergen Kunz 596efa3f06 update
2025-07-18 10:43:39 +00:00

160 lines
3.7 KiB
TypeScript

import * as plugins from './bunq.plugins.js';
export class BunqCrypto {
private privateKey: string;
private publicKey: string;
constructor() {}
/**
* Generate a new RSA key pair for bunq API communication
*/
public async generateKeyPair(): Promise<void> {
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();
}
}