update
This commit is contained in:
160
ts/bunq.classes.crypto.ts
Normal file
160
ts/bunq.classes.crypto.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
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<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();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user