wechatpay-axios-plugin
Version:
微信支付APIv2及v3 NodeJS SDK,支持CLI模式请求OpenAPI,支持v3证书下载,v2付款码支付、企业付款、退款,企业微信-企业支付-企业红包/向员工付款,v2&v3 Native支付、扫码支付、H5支付、JSAPI/小程序支付、合单支付...
199 lines (177 loc) • 6.72 kB
JavaScript
const {
publicEncrypt, privateDecrypt, createSign, createVerify,
KeyObject, createPrivateKey, createPublicKey,
constants: { RSA_PKCS1_PADDING, RSA_PKCS1_OAEP_PADDING },
} = require('crypto');
const { readFileSync } = require('fs');
const { isString } = require('./utils');
/**
* @typedef {import('node:crypto').KeyLike} KeyLike
* @typedef {import('node:crypto').BinaryLike} BinaryLike
*/
const sha1 = 'sha1';
const utf8 = 'utf8';
const base64 = 'base64';
const sha256WithRSAEncryption = 'sha256WithRSAEncryption';
const KEY_TYPE_PUBLIC = 'public';
const KEY_TYPE_PRIVATE = 'private';
const RULES = {
/** -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- */
'private.pkcs1': ['der', 'pkcs1', 16],
/** -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- */
'private.pkcs8': ['der', 'pkcs8', 16],
/** -----BEGIN RSA PUBLIC KEY----- ... -----BEGIN RSA PUBLIC KEY----- */
'public.pkcs1': ['der', 'pkcs1', 15],
/** -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- */
'public.spki': ['der', 'spki', 14],
};
/**
* @param {number} code - Supporting `RSA_PKCS1_OAEP_PADDING, default is `RSA_PKCS1_OAEP_PADDING`.
* @throws {RangeError} - While the padding isn't `RSA_PKCS1_OAEP_PADDING`.
* @returns {void}
*/
function paddingModeLimitedCheck(code) {
if (code !== RSA_PKCS1_OAEP_PADDING) {
throw new RangeError(`Doesn't supported the padding mode(${code}), here's only support RSA_PKCS1_OAEP_PADDING.`);
}
}
/**
* Provides some methods for the RSA `sha256WithRSAEncryption` with `RSA_PKCS1_OAEP_PADDING`.
*/
class Rsa {
static RSA_PKCS1_OAEP_PADDING = RSA_PKCS1_OAEP_PADDING
/**
* @deprecated see [CVE-2023-46809](https://nodejs.org/en/blog/vulnerability/february-2024-security-releases)
*/
static RSA_PKCS1_PADDING = RSA_PKCS1_PADDING
/** Type string of the asymmetric key */
static KEY_TYPE_PUBLIC = KEY_TYPE_PUBLIC
/** Type string of the asymmetric key */
static KEY_TYPE_PRIVATE = KEY_TYPE_PRIVATE
static isKeyObject(thing) {
return (thing instanceof KeyObject) && [KEY_TYPE_PUBLIC, KEY_TYPE_PRIVATE].includes(thing.type);
}
/**
* Sugar for loading input `privateKey` string.
*
* @param {string} str - The string in `PKCS#8` format.
*
* @return {KeyLike} - The KeyLike.
*/
static fromPkcs8(str) { return this.from(`${KEY_TYPE_PRIVATE}.pkcs8://${str}`, KEY_TYPE_PRIVATE); }
/**
* Sugar for loading input `privateKey/publicKey` string.
*
* @param {string} str - The string in `PKCS#1` format.
* @param {'public'|'private'} [type = 'private'] - The `str` is public key string.
*
* @return {KeyLike} - The KeyLike.
*/
static fromPkcs1(str, type = KEY_TYPE_PRIVATE) {
return this.from(`${type === KEY_TYPE_PUBLIC ? KEY_TYPE_PUBLIC : KEY_TYPE_PRIVATE}.pkcs1://${str}`, type);
}
/**
* Sugar for loading input `publicKey` string.
*
* @param {string} str - The string in `SPKI` format.
*
* @return {object} - The KeyLike.
*/
static fromSpki(str) { return this.from(`${KEY_TYPE_PUBLIC}.spki://${str}`, KEY_TYPE_PUBLIC); }
/**
* Loading the privateKey/publicKey from a protocol like string.
*
* @param {KeyLike} thing - The KeyLike.
* @param {'public'|'private'} [type = 'private'] - Kind of the `thing` in `public` or `private`.
*
* @return {KeyObject} - The KeyObject.
*/
static from(thing, type = KEY_TYPE_PRIVATE) {
if (this.isKeyObject(thing) && thing.type === type) { return thing; }
let input = thing;
let protocol = type;
if (isString(thing)) {
if (thing.startsWith('file://')) {
input = readFileSync(thing.slice(7));
} else if (
(thing.startsWith(KEY_TYPE_PRIVATE) || thing.startsWith(KEY_TYPE_PUBLIC))
&& [11, 12, 13].includes(thing.indexOf('://'))
) {
protocol = thing.slice(0, thing.indexOf('://'));
const [format, kind, offset] = RULES[protocol] || [];
if (format && kind && offset) {
input = { key: Buffer.from(thing.slice(offset), base64), format, type: kind };
}
}
}
return (protocol.startsWith(KEY_TYPE_PRIVATE) || type === KEY_TYPE_PRIVATE ? createPrivateKey : createPublicKey)(input);
}
/**
* Encrypts text with sha256WithRSAEncryption/RSA_PKCS1_OAEP_PADDING.
*
* @param {string} plaintext - Cleartext to encode.
* @param {KeyLike} publicKey - The `RsaPublicKey`.
* @param {number} [padding = RSA_PKCS1_OAEP_PADDING] - Value of the `RSA_PKCS1_OAEP_PADDING, default is `RSA_PKCS1_OAEP_PADDING`.
*
* @returns {string} Base64-encoded ciphertext.
* @throws {RangeError} - While the padding isn't `RSA_PKCS1_OAEP_PADDING`.
*/
static encrypt(plaintext, publicKey, padding = RSA_PKCS1_OAEP_PADDING) {
paddingModeLimitedCheck(padding);
return publicEncrypt({
oaepHash: sha1,
key: publicKey,
padding,
}, Buffer.from(plaintext, utf8)).toString(base64);
}
/**
* Decrypts base64 encoded string with `privateKey`.
*
* @param {string} ciphertext - Was previously encrypted string using the corresponding public certificate.
* @param {KeyLike} privateKey - The `RsaPrivateKey`.
* @param {number} [padding = RSA_PKCS1_OAEP_PADDING] - Value of the `RSA_PKCS1_OAEP_PADDING, default is `RSA_PKCS1_OAEP_PADDING`.
*
* @returns {string} Utf-8 plaintext.
* @throws {RangeError} - While the padding isn't `RSA_PKCS1_OAEP_PADDING`.
*/
static decrypt(ciphertext, privateKey, padding = RSA_PKCS1_OAEP_PADDING) {
paddingModeLimitedCheck(padding);
return privateDecrypt({
oaepHash: sha1,
key: privateKey,
padding,
}, Buffer.from(ciphertext, base64)).toString(utf8);
}
/**
* Creates and returns a `Sign` string that uses `sha256WithRSAEncryption`.
*
* @param {BinaryLike} message - Content will be `crypto.Sign`.
* @param {KeyLike} privateKey - The `RsaPrivateKey`.
*
* @returns {string} Base64-encoded signature.
*/
static sign(message, privateKey) {
return createSign(sha256WithRSAEncryption).update(message).sign(
privateKey,
base64,
);
}
/**
* Verifying the `message` with given `signature` string that uses `sha256WithRSAEncryption`.
*
* @param {BinaryLike} message - Content will be `crypto.Verify`.
* @param {string} signature - The base64-encoded ciphertext.
* @param {KeyLike} publicKey - The `RsaPublicKey`.
*
* @returns {boolean} True is passed, false is failed.
*/
static verify(message, signature, publicKey) {
return createVerify(sha256WithRSAEncryption).update(message).verify(
publicKey,
signature,
base64,
);
}
static get default() { return this; }
}
module.exports = Rsa;