@yeepay/yop-nodejs-sdk
Version:
YOP NodeJS SDK with TypeScript support
218 lines • 7.43 kB
JavaScript
import crypto from 'crypto';
import URLSafeBase64 from 'urlsafe-base64';
export class VerifyUtils {
/**
* Validates RSA signature for business results
* @param params - Parameters containing data, sign, and publicKey
* @returns Whether the signature is valid
*/
static isValidRsaResult(params) {
const result = this.getResult(params.data);
let sign = params.sign.replace('$SHA256', '');
let public_key = params.publicKey;
let sb = "";
if (!result) {
sb = "";
}
else {
sb += result.trim();
}
sb = sb.replace(/[\s]{2,}/g, "");
sb = sb.replace(/\n/g, "");
sb = sb.replace(/[\s]/g, "");
let r = public_key + "";
let a = "-----BEGIN PUBLIC KEY-----";
let b = "-----END PUBLIC KEY-----";
public_key = "";
let len = r.length;
let start = 0;
while (start <= len) {
if (public_key.length) {
public_key += r.substr(start, 64) + '\n';
}
else {
public_key = r.substr(start, 64) + '\n';
}
start += 64;
}
public_key = a + '\n' + public_key + b;
let verify = crypto.createVerify('RSA-SHA256');
verify.update(sb);
sign = sign + "";
// sign = sign.substr(0,-7);
sign = sign.replace(/[-]/g, '+');
sign = sign.replace(/[_]/g, '/');
let res = verify.verify(public_key, sign, 'base64');
return res;
}
/**
* Extracts result from response string
* @param str - Response string
* @returns Extracted result
*/
static getResult(str) {
const match = str.match(/"result"\s*:\s*({.*}),\s*"ts"/s);
return match ? match[1] : '';
}
/**
* Handles digital envelope decryption
* @param content - Digital envelope content
* @param isv_private_key - Merchant private key
* @param yop_public_key - YOP platform public key
* @returns Processing result
*/
static digital_envelope_handler(content, isv_private_key, yop_public_key) {
let event = {
status: 'failed',
result: '',
message: ''
};
if (!content) {
event.message = '数字信封参数为空';
}
else if (!isv_private_key) {
event.message = '商户私钥参数为空';
}
else if (!yop_public_key) {
event.message = '易宝开放平台公钥参数为空';
}
else {
try {
const digital_envelope_arr = content.split('$');
const encryted_key_safe = this.base64_safe_handler(digital_envelope_arr[0]);
const decryted_key = this.rsaDecrypt(encryted_key_safe, this.key_format(isv_private_key));
const biz_param_arr = this.aesDecrypt(this.base64_safe_handler(digital_envelope_arr[1]), decryted_key).split('$');
const sign = biz_param_arr.pop() || '';
event.result = biz_param_arr.join('$');
if (this.isValidNotifyResult(event.result, sign, yop_public_key)) {
event.status = 'success';
}
else {
event.message = '验签失败';
}
}
catch (error) {
event.message = error instanceof Error ? error.message : String(error);
}
}
return event;
}
/**
* Validates merchant notification signature
* @param result - Result data
* @param sign - Signature
* @param public_key - Public key
* @returns Whether the signature is valid
*/
static isValidNotifyResult(result, sign, public_key) {
let sb = "";
if (!result) {
sb = "";
}
else {
sb += result;
}
let r = public_key + "";
let a = "-----BEGIN PUBLIC KEY-----";
let b = "-----END PUBLIC KEY-----";
public_key = "";
let len = r.length;
let start = 0;
while (start <= len) {
if (public_key.length) {
public_key += r.substr(start, 64) + '\n';
}
else {
public_key = r.substr(start, 64) + '\n';
}
start += 64;
}
public_key = a + '\n' + public_key + b;
let verify = crypto.createVerify('RSA-SHA256');
verify.update(sb);
sign = sign + "";
// sign = sign.substr(0,-7);
sign = sign.replace(/[-]/g, '+');
sign = sign.replace(/[_]/g, '/');
let res = verify.verify(public_key, sign, 'base64');
return res;
}
/**
* Restores base64 safe data
* @param data - Data to restore
* @returns Restored data
*/
static base64_safe_handler(data) {
return URLSafeBase64.decode(data).toString('base64');
}
/**
* Formats private key with header
* @param key - Private key without header
* @returns Formatted private key
*/
static key_format(key) {
return '-----BEGIN PRIVATE KEY-----\n' + key + '\n-----END PRIVATE KEY-----';
}
/**
* Decrypts data using RSA
* @param content - Encrypted content
* @param privateKey - Private key
* @returns Decrypted data
*/
static rsaDecrypt(content, privateKey) {
const block = Buffer.from(content, 'base64');
const decodeData = crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING
}, block);
return decodeData;
}
/**
* Decrypts data using AES
* @param encrypted - Encrypted content
* @param key - Encryption key
* @returns Decrypted data
*/
static aesDecrypt(encrypted, key) {
const decipher = crypto.createDecipheriv('aes-128-ecb', key, Buffer.alloc(0));
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* Gets business result from content
* @param content - Content to extract from
* @param format - Format of the content
* @returns Extracted business result
*/
static getBizResult(content, format) {
if (!format) {
return content;
}
let local = -1;
let result = "";
let tmp_result = "";
let length = 0;
switch (format) {
case 'json':
local = content.indexOf('"result"');
result = content.substr(local);
length = tmp_result.length;
result = result.substr(length + 3);
result = result.substr(0, result.lastIndexOf('"ts"'));
result = result.substr(0, result.length - 4);
return result;
default:
local = content.indexOf('"</state>"');
result = content.substr(local);
tmp_result = '</state>';
length = tmp_result.length;
result = result.substr(length + 4);
result = result.substr(0, result.lastIndexOf('"ts"'));
result = result.substr(0, -2);
return result;
}
}
}
export default VerifyUtils;
//# sourceMappingURL=VerifyUtils.js.map