UNPKG

@bnnsoftvn/mobile-sic-sdk

Version:
571 lines (565 loc) 21.1 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. * */ import 'reflect-metadata'; import ReactNativeBiometrics, { BiometryTypes } from '@bnnsoftvn/react-native-biometrics-sic'; import { CertificationRequest, CertificationRequestInfo } from '@peculiar/asn1-csr'; import { PublicKey, Name } from '@peculiar/x509'; import { SubjectPublicKeyInfo, Name as Name$1 } from '@peculiar/asn1-x509'; import { AsnConvert } from '@peculiar/asn1-schema'; import DeviceInfo from 'react-native-device-info'; import * as xmldom from '@xmldom/xmldom'; import { RSAPublicKey } from '@peculiar/asn1-rsa'; import { Convert } from 'pvtsutils'; 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(); const { available, biometryType } = await rnBiometrics.isSensorAvailable(); if (available && biometryType === BiometryTypes.TouchID) { console.log('TouchID is supported'); } else if (available && biometryType === BiometryTypes.FaceID) { console.log('FaceID is supported'); } else if (available && biometryType === 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 PublicKey(publicKey); const asnSpki = AsnConvert.parse(key.rawData, SubjectPublicKeyInfo); console.log(asnSpki); const rsapublicKey = AsnConvert.parse(asnSpki.subjectPublicKey, 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.getUniqueId(); let deviceuniqdn = "O=" + device; let devicesyndn = "OU=" + await DeviceInfo.syncUniqueId(); let devicesysname = "L=" + DeviceInfo.getSystemName(); let devicesysver = "ST=" + DeviceInfo.getSystemVersion(); obj.subject += "," + escapeFilterValue(deviceuniqdn) + "," + escapeFilterValue(devicesyndn) + "," + escapeFilterValue(devicesysname) + "," + escapeFilterValue(devicesysver); const asnReq = new CertificationRequest({ certificationRequestInfo: new CertificationRequestInfo({ subjectPKInfo: asnSpki, version: 0, subject: AsnConvert.parse(new Name(obj.subject).toArrayBuffer(), Name$1) }), }); const tbs = AsnConvert.serialize(asnReq.certificationRequestInfo); const { success, signature } = await rnBiometrics.createSignature({ promptMessage: promptMessage, payload: Convert.ToBase64(challenge), keytag: data.userid, type: 1 }); if (success) { let result = { csr: 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.getUniqueId(); let devicesyndn = await DeviceInfo.syncUniqueId(); let devicesysname = DeviceInfo.getSystemName(); let devicesysver = DeviceInfo.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(); 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.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 === BiometryTypes.TouchID) { console.log('TouchID is supported'); } else if (available && biometryType === BiometryTypes.FaceID) { console.log('FaceID is supported'); } else if (available && biometryType === 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.DOMParser().parseFromString(Base64Utils.decode(data.sad), "application/xml"); let signatureXml = "<Signature><DigestMethod>SHA256</DigestMethod><SignatureValue>" + signature + "</SignatureValue></Signature>"; const docsig = new xmldom.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; } export { GenerateCsr_V2, GenerateSad_V2 };