120 lines
2.7 KiB
TypeScript
120 lines
2.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 request signature header (signs only body per bunq docs)
|
|
*/
|
|
public createSignatureHeader(
|
|
method: string,
|
|
endpoint: string,
|
|
headers: { [key: string]: string },
|
|
body: string = ''
|
|
): string {
|
|
// According to bunq docs, only sign the request body
|
|
return this.signData(body);
|
|
}
|
|
|
|
/**
|
|
* Verify response signature (signs only body per bunq API behavior)
|
|
*/
|
|
public verifyResponseSignature(
|
|
statusCode: number,
|
|
headers: { [key: string]: string },
|
|
body: string,
|
|
serverPublicKey: string
|
|
): boolean {
|
|
const responseSignature = headers['x-bunq-server-signature'];
|
|
if (!responseSignature) {
|
|
return false;
|
|
}
|
|
|
|
// According to bunq API behavior, only the response body is signed
|
|
return this.verifyData(body, responseSignature, serverPublicKey);
|
|
}
|
|
|
|
/**
|
|
* Generate a random request ID
|
|
*/
|
|
public generateRequestId(): string {
|
|
return plugins.crypto.randomUUID();
|
|
}
|
|
} |