UNPKG

@bnnsoftvn/mobile-sic-sdk

Version:
600 lines (589 loc) 22.4 kB
/*! * MIT License * * Copyright (c) Peculiar Ventures. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); require('reflect-metadata'); var ReactNativeBiometrics = require('@bnnsoftvn/react-native-biometrics-sic'); var asn1Csr = require('@peculiar/asn1-csr'); var x509 = require('@peculiar/x509'); var asn1X509 = require('@peculiar/asn1-x509'); var asn1Schema = require('@peculiar/asn1-schema'); var DeviceInfo = require('react-native-device-info'); var xmldom = require('@xmldom/xmldom'); var asn1Rsa = require('@peculiar/asn1-rsa'); var pvtsutils = require('pvtsutils'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var ReactNativeBiometrics__default = /*#__PURE__*/_interopDefaultLegacy(ReactNativeBiometrics); var DeviceInfo__default = /*#__PURE__*/_interopDefaultLegacy(DeviceInfo); var xmldom__namespace = /*#__PURE__*/_interopNamespace(xmldom); class SignResult { constructor() { this.status = -1; this.data = ""; this.message = ""; } } const keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; const Base64Utils = { encode: (input) => { let output = []; let chr1, chr2, chr3; let enc1, enc2, enc3, enc4; let i = 0; while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output.push(keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4)); } return output.join(''); }, encodeFromByteArray: (input) => { let output = []; let chr1, chr2, chr3; let enc1, enc2, enc3, enc4; let i = 0; while (i < input.length) { chr1 = input[i++]; chr2 = input[i++]; chr3 = input[i++]; enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output.push(keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4)); } return output.join(''); }, decode: (input) => { let output = ''; let chr1, chr2, chr3; let enc1, enc2, enc3, enc4; let i = 0; const base64test = /[^A-Za-z0-9+/=]/g; if (base64test.test(input)) { throw new Error("Invalid base64 characters in input. Allowed: A-Z, a-z, 0-9, '+', '/', '='."); } input = input.replace(/[^A-Za-z0-9+/=]/g, ''); while (i < input.length) { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output += String.fromCharCode(chr1); if (enc3 !== 64) { output += String.fromCharCode(chr2); } if (enc4 !== 64) { output += String.fromCharCode(chr3); } } return output; } }; const appVersion = "2.11.3"; const SAD_FORMAT = { SAD_XML: 0, SAD_JWK: 1, SAD_BIN: 2 }; const ERROR_CODES = { NguoiDungHuy: { code: -2, message: "Người dùng hủy.", }, KyLoi: { code: -3, message: "Quá nhiều lần thử. Hãy thử lại sau.", }, KhongHoTro: { code: -4, message: "Thiết bị không hỗ trợ.", }, ChuaDangKy: { code: -5, message: "Thiết bị chưa đăng ký.", }, KhongXacDinh: { code: -9, message: "Có lỗi xảy ra. Vui lòng thử lại sau.", }, KeyKhongTonTai: { code: -6, message: "Không tìm thấy key.", }, LoiTrongQuaTrinhDongGoiChuKy: { code: -7, message: "Lỗi trong quá trình đóng gói chữ ký.", }, LoiPhienBanSDK: { code: -8, message: "Phiên bản SDK cần update.", }, LoiChuKy: { code: -10, message: "Ký thành công nhưng chữ ký bị sai format hoặc trống.", }, }; const compareVersions = (v1, v2) => { const v1Parts = v1.split(".").map(Number); const v2Parts = v2.split(".").map(Number); for (let i = 0; i < v1Parts.length; i++) { if ((v1Parts[i] || 0) > (v2Parts[i] || 0)) return true; if ((v1Parts[i] || 0) < (v2Parts[i] || 0)) return false; } return false; }; function escapeFilterValue(input) { let escapedResult = ""; for (const inputChar of input) { switch (inputChar) { case "*": escapedResult += "\\2a"; break; case "(": escapedResult += "\\28"; break; case ")": escapedResult += "\\29"; break; case "\\": escapedResult += "\\5c"; break; case "\0": escapedResult += "\\00"; break; default: escapedResult += inputChar; break; } } return escapedResult; } const isNullOrEmpty = (value) => { return value === null || value === undefined || value === ""; }; async function GenerateCsr_V2(data) { var res = new SignResult(); const rnBiometrics = new ReactNativeBiometrics__default["default"](); const { available, biometryType } = await rnBiometrics.isSensorAvailable(); if (available && biometryType === ReactNativeBiometrics.BiometryTypes.TouchID) { console.log('TouchID is supported'); } else if (available && biometryType === ReactNativeBiometrics.BiometryTypes.FaceID) { console.log('FaceID is supported'); } else if (available && biometryType === ReactNativeBiometrics.BiometryTypes.Biometrics) { console.log('Biometrics is supported'); } else { console.log('Biometrics not supported'); res.message = ERROR_CODES.KhongHoTro.message; res.status = ERROR_CODES.KhongHoTro.code; return res; } let promptMessage = "Sinh khoá thiết bị"; let keytype = 0; let keylength = 2048; console.log("data input", data); if (!isNullOrEmpty(data.version)) { console.log("data.version", data.version); if (compareVersions(appVersion, data.version)) { console.log("appVersion"); res.message = ERROR_CODES.LoiPhienBanSDK.message; res.status = ERROR_CODES.LoiPhienBanSDK.code; return res; } } if (!isNullOrEmpty(data.algorithm)) { console.log("data.algorithm", data.algorithm); data.algorithm.name; data.algorithm.hash; } if (!isNullOrEmpty(data.keytype)) { console.log("data.keytype", data.keytype); keytype = data.keytype; } if (!isNullOrEmpty(data.keylength)) { console.log("data.keylength", data.keylength); keylength = data.keylength; } if (!isNullOrEmpty(data.promptMessage)) { console.log("data.promptMessage", data.promptMessage); promptMessage = data.promptMessage; } if (data.orgcode != null && (data.orgcode.includes("VNPT-SMARTCA") || data.orgcode.includes("VNPT") || data.orgcode.includes("vnpt"))) { let publicKey = ""; let checkkey = await rnBiometrics.biometricKeysExist(data.userid); if (checkkey.keysExist) { console.log("get publickey begin"); let keys = await rnBiometrics.getPublicKey(data.userid); console.log("get publickey done"); publicKey = keys.publicKey; } else { console.log("create publickey", { keytag: data.userid, keytype: keytype, keysize: keylength }); const keys = await rnBiometrics.createKeys({ keytag: data.userid, keytype: keytype, keysize: keylength }); console.log("create publickey done"); publicKey = keys.publicKey; } var obj = { subject: escapeFilterValue("CN=" + data.userid), subjectPublicKeyInfo: publicKey }; console.log("publicKey", publicKey); const key = new x509.PublicKey(publicKey); const asnSpki = asn1Schema.AsnConvert.parse(key.rawData, asn1X509.SubjectPublicKeyInfo); console.log(asnSpki); const rsapublicKey = asn1Schema.AsnConvert.parse(asnSpki.subjectPublicKey, asn1Rsa.RSAPublicKey); console.log(rsapublicKey); console.log(rsapublicKey.modulus, rsapublicKey.publicExponent); let modulus = rsapublicKey.modulus; let exponent = rsapublicKey.publicExponent; var challenge = new Uint8Array(modulus.byteLength + exponent.byteLength); challenge.set(new Uint8Array(modulus), 0); challenge.set(new Uint8Array(exponent), modulus.byteLength); var device = DeviceInfo__default["default"].getUniqueId(); let deviceuniqdn = "O=" + device; let devicesyndn = "OU=" + await DeviceInfo__default["default"].syncUniqueId(); let devicesysname = "L=" + DeviceInfo__default["default"].getSystemName(); let devicesysver = "ST=" + DeviceInfo__default["default"].getSystemVersion(); obj.subject += "," + escapeFilterValue(deviceuniqdn) + "," + escapeFilterValue(devicesyndn) + "," + escapeFilterValue(devicesysname) + "," + escapeFilterValue(devicesysver); const asnReq = new asn1Csr.CertificationRequest({ certificationRequestInfo: new asn1Csr.CertificationRequestInfo({ subjectPKInfo: asnSpki, version: 0, subject: asn1Schema.AsnConvert.parse(new x509.Name(obj.subject).toArrayBuffer(), asn1X509.Name) }), }); const tbs = asn1Schema.AsnConvert.serialize(asnReq.certificationRequestInfo); const { success, signature } = await rnBiometrics.createSignature({ promptMessage: promptMessage, payload: pvtsutils.Convert.ToBase64(challenge), keytag: data.userid, type: 1 }); if (success) { let result = { csr: pvtsutils.Convert.ToBase64(tbs), subjectDN: obj.subject, signature: signature }; res.data = Base64Utils.encode(JSON.stringify(result)), res.status = 0; } else { res.status = ERROR_CODES.NguoiDungHuy.code; res.message = ERROR_CODES.NguoiDungHuy.message; } return res; } var checkkey = await rnBiometrics.biometricKeysExist(data.userid); if (checkkey.keysExist) { console.log("publickey true"); } else { console.log("create publickey", { keytag: data.userid, keytype: keytype, keysize: keylength }); await rnBiometrics.createKeys({ keytag: data.userid, keytype: keytype, keysize: keylength }); console.log("create publickey done"); } var device = DeviceInfo__default["default"].getUniqueId(); let devicesyndn = await DeviceInfo__default["default"].syncUniqueId(); let devicesysname = DeviceInfo__default["default"].getSystemName(); let devicesysver = DeviceInfo__default["default"].getSystemVersion(); var csrResult = await rnBiometrics.createCsr({ keytag: data.userid, promptMessage: promptMessage, commonName: data.userid, organization: device, organizationalUnit: devicesyndn, locality: devicesysname, state: devicesysver }); if (csrResult.success && csrResult.csr) { res.status = 0; res.data = csrResult.csr; } else { res.status = ERROR_CODES.KhongXacDinh.code; res.message = csrResult.error ? csrResult.error : ERROR_CODES.KhongXacDinh.message; } return res; } async function GenerateSad_V2(data) { const rnBiometrics = new ReactNativeBiometrics__default["default"](); var res = new SignResult(); if (!isNullOrEmpty(data.version)) { if (!compareVersions(appVersion, data.version)) { res.message = ERROR_CODES.LoiPhienBanSDK.message; res.status = ERROR_CODES.LoiPhienBanSDK.code; return res; } } var uniqueId = DeviceInfo__default["default"].getUniqueId(); if (data.deviceid != uniqueId) { res.status = ERROR_CODES.ChuaDangKy.code; res.message = ERROR_CODES.ChuaDangKy.message; return res; } const { available, biometryType } = await rnBiometrics.isSensorAvailable(); if (available && biometryType === ReactNativeBiometrics.BiometryTypes.TouchID) { console.log('TouchID is supported'); } else if (available && biometryType === ReactNativeBiometrics.BiometryTypes.FaceID) { console.log('FaceID is supported'); } else if (available && biometryType === ReactNativeBiometrics.BiometryTypes.Biometrics) { console.log('Biometrics is supported'); } else { console.log('Biometrics not supported'); res.message = ERROR_CODES.KhongHoTro.message; res.status = ERROR_CODES.KhongHoTro.code; return res; } if (data.format == SAD_FORMAT.SAD_BIN) { let sign = await signbinary(rnBiometrics, data); res.status = sign.status; res.data = sign.data; res.message = sign.message; } else if (data.format == SAD_FORMAT.SAD_JWK) { let sign = await signjson(rnBiometrics, data); res.status = sign.status; res.data = sign.data; res.message = sign.message; } else if (data.format == SAD_FORMAT.SAD_XML) { let sign = await signxml(rnBiometrics, data); res.status = sign.status; res.data = sign.data; res.message = sign.message; } return res; } async function signbinary(rnBiometrics, data) { var res = new SignResult(); console.log("signbinary0"); try { let keytag = data.userid; let keylength = 2048; var checkkey = await rnBiometrics.biometricKeysExist(keytag); if (checkkey.keysExist != true) { res.status = ERROR_CODES.KeyKhongTonTai.code; res.message = ERROR_CODES.KeyKhongTonTai.message; return res; } console.log("signbinary1"); try { const { success, signature } = await rnBiometrics.createSignature({ promptMessage: data.promptMessage, payload: data.sad, keytag: keytag, type: 1 }); if (success && (signature == null || signature == undefined || signature.length == 0)) { res.status = ERROR_CODES.NguoiDungHuy.code; res.message = ERROR_CODES.NguoiDungHuy.message; } else if (success && signature) { res.data = signature; res.status = 0; } else { res.status = ERROR_CODES.NguoiDungHuy.code; res.message = ERROR_CODES.NguoiDungHuy.message; } } catch (err) { res.status = ERROR_CODES.KyLoi.code; res.message = ERROR_CODES.KyLoi.message; } } catch (err) { res.status = ERROR_CODES.KhongXacDinh.code; res.message = ERROR_CODES.KhongXacDinh.message; } return res; } async function signjson(rnBiometrics, data) { var res = new SignResult(); try { let keytag = data.userid; let keylength = 2048; var checkkey = await rnBiometrics.biometricKeysExist(keytag); if (checkkey.keysExist != true) { res.status = ERROR_CODES.KeyKhongTonTai.code; res.message = ERROR_CODES.KeyKhongTonTai.message; return res; } console.log("checkkey b", checkkey); try { const { success, signature } = await rnBiometrics.createSignature({ promptMessage: data.promptMessage, payload: data.sad, keytag: keytag, type: 0 }); console.log("return b", signature); if (success && signature) { var tokensign = data.sad + "." + signature.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); console.log("jwt ", tokensign); res.data = tokensign; res.status = 0; } else { res.status = ERROR_CODES.NguoiDungHuy.code; res.message = ERROR_CODES.NguoiDungHuy.message; } } catch (err) { res.status = ERROR_CODES.KyLoi.code; res.message = ERROR_CODES.KyLoi.message + "Chi tiết: " + err; } } catch (err) { res.status = ERROR_CODES.KhongXacDinh.code; res.message = ERROR_CODES.KhongXacDinh.message; } return res; } async function signxml(rnBiometrics, data) { var res = new SignResult(); console.log("signxml0"); try { let keytag = data.userid; let keylength = 2048; var checkkey = await rnBiometrics.biometricKeysExist(keytag); if (checkkey.keysExist != true) { res.status = ERROR_CODES.KeyKhongTonTai.code; res.message = ERROR_CODES.KeyKhongTonTai.message; return res; } console.log("signxml2"); try { const { success, signature } = await rnBiometrics.createSignature({ promptMessage: data.promptMessage, payload: data.sad, keytag: keytag, type: 1 }); if (success) { const doc = new xmldom__namespace.DOMParser().parseFromString(Base64Utils.decode(data.sad), "application/xml"); let signatureXml = "<Signature><DigestMethod>SHA256</DigestMethod><SignatureValue>" + signature + "</SignatureValue></Signature>"; const docsig = new xmldom__namespace.DOMParser().parseFromString(signatureXml, "application/xml"); if (doc.documentElement == null || docsig.documentElement == null) { res.status = ERROR_CODES.LoiTrongQuaTrinhDongGoiChuKy.code; res.message = ERROR_CODES.LoiTrongQuaTrinhDongGoiChuKy.message; return res; } else { doc.documentElement.appendChild(docsig.documentElement); res.data = Base64Utils.encode(doc.toString()); res.status = 0; return res; } } else { res.status = ERROR_CODES.NguoiDungHuy.code; res.message = ERROR_CODES.NguoiDungHuy.message; } } catch (err) { res.status = ERROR_CODES.KyLoi.code; res.message = ERROR_CODES.KyLoi.message; } } catch (err) { res.status = ERROR_CODES.KhongXacDinh.code; res.message = ERROR_CODES.KhongXacDinh.message; } return res; } exports.GenerateCsr_V2 = GenerateCsr_V2; exports.GenerateSad_V2 = GenerateSad_V2;