diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..dd87e2d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..cc0f4cd --- /dev/null +++ b/.eslintrc @@ -0,0 +1,37 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "no-loops" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@shopify/esnext" + ], + "settings": { + "import/resolver": { + "node": { + "extensions": [ + ".ts", + ".tsx" + ] + } + } + }, + "rules": { + "no-undef": 0, + "no-process-env": 0, + "no-unused-vars": 0, + "require-await": 0, + "@typescript-eslint/no-explicit-any": 0, + "id-length": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-var-requires": 0, + "no-console": 0, + "import/no-unresolved": 0, + " import/no-unresolved": 0 + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..010a78f --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/ +node_modules +.DS_Store +/build +*.list +src/**.js +src/*.js +src/utils/*.js +**.map diff --git a/README.md b/README.md index df0c742..c10ef68 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,515 @@ -# E3372h-320-cli +# Huawei E3372h-320 console client client for E3372h-320 (CL4E3372HM) + +![](./docs/4889330.jpg) +![](./docs/4889332.jpg) + +# Tested on +E3372h-320 +version: 11.0.1.1(H697SP1C983) +Web UI version WEBUI 11.0.1.1(W13SP2C7201) + +![](./docs/info.png) +![](./docs/info2.png) + +## Requirement + - nodejs (>11) +## Installation +- install package +``` +npm i e3372h-320-cli -g +``` + +# Docker installation +TODO + +## How to use + +### Help + +``` +e3372h_320 --help +``` +result: +``` +e3372h_320 [command] + +Commands: + e3372h_320 sendSMS send SMS to contact or group of contacts + e3372h_320 contacts get contact list with the latest sms messages + e3372h_320 contactPages contact list pages + e3372h_320 sms get contact SMS list + e3372h_320 pages count of sms pages + e3372h_320 deleteSMS delete sms by smsId + e3372h_320 mobileData Enable/Disable or Reconnect Mobile Data + e3372h_320 monitoring current Monitoring status + +Options: + --help Show help [boolean] + --version Show version number [boolean] + [boolean] +``` +### Version + +``` +e3372h_320 --version +``` +result: +``` +1.0.0 +``` + +### Send SMS +- help +``` +e3372h_320 sendSMS --help +``` +result +``` +e3372h_320 sendSMS + +send SMS to contact or group of contacts + +Positionals: + url huawei host [default: "192.168.8.1"] + phone phones with ; as separator [string] + message text message [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +- Examples +Send message "Test message" to +11111111111 +``` +e3372h_320 sendSMS --phone=+11111111111 --message="Test message" +``` + + +### SMS Conversation API + +![](./docs/smsConversation1.png) + +result +- help +``` +e3372h_320 contacts --help +``` + +``` +e3372h_320 contacts + +get contact list with the latest sms messages + +Positionals: + url huawei host [default: "192.168.8.1"] + page sms page [default: 1] + exportFile export to file [default: "./contacts.list"] + exportFormat export format (xml, json, none) [default: "none"] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +- Example get all contacts +``` +./e3372h_320 contacts +``` +Result: +``` +MessageId: 40004 Phone: +22222222222 lastMessage: {} +MessageId: 40005 Phone: +11111111111 lastMessage: "Test message" +``` +- Example get all contacts and export as xml +``` +./e3372h_320 contacts --exportFormat=xml --exportFile='./contacts.xml' +cat ./contacts.xml +``` +Result: +```xml + +2 + + + + 0 + 40004 + +222222222222 + + 2021-10-28 22:27:09 + + 0 + 0 + 7 + 0 + + + 2 + 40005 + +11111111111 + Test message + 2021-10-28 21:36:20 + + 3 + 4 + 1 + 0 + + + +``` + +- Example get all contacts and export as json +``` +./e3372h_320 contacts --exportFormat=json --exportFile='./contacts.json' +cat ./contacts.json +``` +Result: +```json +{ + "response":{ + "count":"2", + "messages":{ + "message":[ + { + "smstat":"0", + "index":"40004", + "phone":"+222222222222", + "content":{ + + }, + "date":"2021-10-28 22:27:09", + "sca":{ + + }, + "savetype":"0", + "priority":"0", + "smstype":"7", + "unreadcount":"0" + }, + { + "smstat":"2", + "index":"40005", + "phone":"+11111111111", + "content":"Test message", + "date":"2021-10-28 21:36:20", + "sca":{ + + }, + "savetype":"3", + "priority":"4", + "smstype":"1", + "unreadcount":"0" + } + ] + } + } +} +``` + +### SMS Contact Conversation API + +![](./docs/smsConversation1.png) + +- help +``` +e3372h_320 sms --help +``` +Result: +``` +e3372h_320 sms + +get contact SMS list + +Positionals: + url huawei host [default: "192.168.8.1"] + phone contact phone number [string] + page sms page [default: 1] + exportFile export to file [default: "./sms.list"] + exportFormat export format (xml, json, none) [default: "none"] + deleteAfter delete all messages after reading [default: false] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +- Example get conversation for phone +111111111111 +``` +./e3372h_320 sms --phone=+111111111111 +``` +Result: +``` +MessageId: 40001 Phone: +111111111111 Message: "test123" +MessageId: 40003 Phone: +111111111111 Message: "test123" +MessageId: 40000 Phone: +111111111111 Message: "Sms" +MessageId: 40002 Phone: +111111111111 Message: {} +MessageId: 40004 Phone: +111111111111 Message: {} + +``` +- Example get conversation for phone +111111111111 export result as xml +``` +./e3372h_320 sms --exportFile=111111111111.xml --exportFormat=xml +cat ./111111111111.xml +``` +Result: +```xml + +5 + + +3 +40001 ++111111111111 +test123 +2021-10-28 21:26:02 + +1 +3 +4 +1 + + +3 +40003 ++111111111111 +test123 +2021-10-28 21:27:06 + +1 +3 +4 +1 + + +1 +40000 ++111111111111 +Sms +2021-10-28 21:58:39 + +0 +0 +0 +1 + + +1 +40002 ++111111111111 + +2021-10-28 22:26:05 + +0 +0 +0 +7 + + +1 +40004 ++111111111111 + +2021-10-28 22:27:09 + +0 +0 +0 +7 + + + + +``` + +- Example get all contacts and export as json +``` +./e3372h_320 sms --exportFile=111111111111.json --exportFormat=json +cat ./111111111111.json +``` +Result: +```json +{"response":{"count":"5","messages":{"message":[{"smstat":"3","index":"40001","phone":"+111111111111","content":"test123","date":"2021-10-28 21:26:02","sca":{},"curbox":"1","savetype":"3","priority":"4","smstype":"1"},{"smstat":"3","index":"40003","phone":"+111111111111","content":"test123","date":"2021-10-28 21:27:06","sca":{},"curbox":"1","savetype":"3","priority":"4","smstype":"1"},{"smstat":"1","index":"40000","phone":"+111111111111","content":"Sms","date":"2021-10-28 21:58:39","sca":{},"curbox":"0","savetype":"0","priority":"0","smstype":"1"},{"smstat":"1","index":"40002","phone":"+111111111111","content":{},"date":"2021-10-28 22:26:05","sca":{},"curbox":"0","savetype":"0","priority":"0","smstype":"7"},{"smstat":"1","index":"40004","phone":"+111111111111","content":{},"date":"2021-10-28 22:27:09","sca":{},"curbox":"0","savetype":"0","priority":"0","smstype":"7"}]}}} +``` + +### Delete sms message + +- help +``` +e3372h_320 deleteSMS --help +``` + +``` +e3372h_320 deleteSMS + +delete sms by smsId + +Positionals: + url huawei host [default: "192.168.8.1"] + messageId messageId or index [string] + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + +- Example delete message +``` +e3372h_320 deleteSMS --messageId=40005 +``` + +### mobileData + +--help +``` +./e3372h_320 mobileData --help +``` + +``` +e3372h_320 mobileData + +Enable/Disable or Reconnect Mobile Data + +Positionals: + url huawei host [default: "192.168.8.1"] + mode change mobile data to on,off or reconnect + +Options: + --help Show help [boolean] + --version Show version number [boolean] +``` + + +- Example disable mobile data +``` +e3372h_320 mobileData --mode=on +``` + +- Example enable mobile data +``` +e3372h_320 mobileData --mode=off +``` +- Example reconnect +``` +e3372h_320 mobileData --mode=reconnect +``` + +### current Monitoring status + +-help +``` + e3372h_320 monitoring --help +``` + +``` +e3372h_320 monitoring + +current Monitoring status + +Positionals: + url huawei host [default: "192.168.8.1"] + exportFile export to file [default: "./monitoring.log"] + exportFormat export format (xml, json, none) [default: "none"] + +Options: + --help Show help [boolean] + --version Show version number [boolean] + +``` + +- Example current status +``` +e3372h_320 monitoring +``` +``` +ConnectionStatus=901 +WifiConnectionStatus=[object Object] +SignalStrength=[object Object] +SignalIcon=4 +CurrentNetworkType=19 +CurrentServiceDomain=3 +RoamingStatus=0 +BatteryStatus=[object Object] +BatteryLevel=[object Object] +BatteryPercent=[object Object] +simlockStatus=0 +PrimaryDns=195.38.164.15 +SecondaryDns=195.38.164.16 +wififrequence=0 +flymode=0 +PrimaryIPv6Dns=[object Object] +SecondaryIPv6Dns=[object Object] +CurrentWifiUser=[object Object] +TotalWifiUser=[object Object] +currenttotalwifiuser=0 +ServiceStatus=2 +SimStatus=1 +WifiStatus=[object Object] +CurrentNetworkTypeEx=101 +maxsignal=5 +wifiindooronly=0 +classify=hilink +usbup=0 +wifiswitchstatus=0 +WifiStatusExCustom=0 +hvdcp_online=0 +speedLimitStatus=0 +poorSignalStatus=0 +``` + +- Example current status export result as xml +``` +./e3372h_320 monitoring --exportFile=status.xml --exportFormat=xml +cat ./status.xml +``` +Result: +```xml + + +901 + + +3 +19 +3 +0 + + + +0 +195.38.164.15 +195.38.164.16 +0 +0 + + + + +0 +2 +1 + +101 +5 +0 +hilink +0 +0 +0 +0 +0 +0 + +``` + +- Example current status export result as json +``` +./e3372h_320 monitoring --exportFile=status.json --exportFormat=json +cat ./status.json +``` +Result: +```json +{"response":{"ConnectionStatus":"901","WifiConnectionStatus":{},"SignalStrength":{},"SignalIcon":"3","CurrentNetworkType":"19","CurrentServiceDomain":"3","RoamingStatus":"0","BatteryStatus":{},"BatteryLevel":{},"BatteryPercent":{},"simlockStatus":"0","PrimaryDns":"195.38.164.15","SecondaryDns":"195.38.164.16","wififrequence":"0","flymode":"0","PrimaryIPv6Dns":{},"SecondaryIPv6Dns":{},"CurrentWifiUser":{},"TotalWifiUser":{},"currenttotalwifiuser":"0","ServiceStatus":"2","SimStatus":"1","WifiStatus":{},"CurrentNetworkTypeEx":"101","maxsignal":"5","wifiindooronly":"0","classify":"hilink","usbup":"0","wifiswitchstatus":"0","WifiStatusExCustom":"0","hvdcp_online":"0","speedLimitStatus":"0","poorSignalStatus":"0"}} +``` diff --git a/bin/e3372h_320 b/bin/e3372h_320 new file mode 100755 index 0000000..c387c31 --- /dev/null +++ b/bin/e3372h_320 @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../index.js'); diff --git a/docs/4889330.jpg b/docs/4889330.jpg new file mode 100644 index 0000000..5d85355 Binary files /dev/null and b/docs/4889330.jpg differ diff --git a/docs/4889332.jpg b/docs/4889332.jpg new file mode 100644 index 0000000..8d223a7 Binary files /dev/null and b/docs/4889332.jpg differ diff --git a/docs/info.png b/docs/info.png new file mode 100644 index 0000000..3488f80 Binary files /dev/null and b/docs/info.png differ diff --git a/docs/info2.png b/docs/info2.png new file mode 100644 index 0000000..6da2dca Binary files /dev/null and b/docs/info2.png differ diff --git a/docs/smsConversation1.png b/docs/smsConversation1.png new file mode 100644 index 0000000..8ba8a59 Binary files /dev/null and b/docs/smsConversation1.png differ diff --git a/docs/smsConversation2.png b/docs/smsConversation2.png new file mode 100644 index 0000000..df61a75 Binary files /dev/null and b/docs/smsConversation2.png differ diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..6fc735f --- /dev/null +++ b/index.ts @@ -0,0 +1,249 @@ +import yargs from 'yargs'; +import {hideBin} from 'yargs/helpers'; +import {startSession} from "./src/startSession"; +import { + deleteMessage, + getContactSMSPages, + getSMSByUsers, + getSMSContacts, + getSMSPages, + sendMessage +} from "./src/ListSMS"; +import {controlMobileData, reconnect, status} from "./src/MobileData"; + +// @ts-ignore +yargs(hideBin(process.argv)) + .command('sendSMS', 'send SMS to contact or group of contacts', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('phone', { + describe: 'phones with ; as separator ', + type:"string", + }).positional('message', { + describe: 'text message ', + type:"string", + }) + }, async (argv) => { + const sessionData = await startSession(argv.url); + if (!argv.phone) { + throw new Error('Phone number is not defined'); + return; + } + await sendMessage(sessionData, argv.phone, argv.message||''); + }).command('contacts', 'get contact list with the latest sms messages', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('page', { + describe: 'sms page', + default: 1 + }).positional('exportFile', { + describe: 'export to file', + default: './contacts.list' + }).positional('exportFormat', { + describe: 'export format (xml, json, none)', + default: 'none' + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } + } + await getSMSContacts(sessionData, argv.page, argv.exportFile, argv.exportFormat); +}).command('contactPages', 'contact list pages', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('exportFile', { + describe: 'export to file', + default: './contactsCount.list' + }).positional('exportFormat', { + describe: 'export format (xml, json, none)', + default: 'none' + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } + } + await getSMSPages(sessionData, argv.exportFile, argv.exportFormat); +}).command('sms', 'get contact SMS list', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1', + }).positional('phone', { + describe: 'contact phone number', + type: 'string' + }).positional('page', { + describe: 'sms page', + default: 1 + }).positional('exportFile', { + describe: 'export to file', + default: './sms.list' + }).positional('exportFormat', { + describe: 'export format (xml, json, none)', + default: 'none' + }).positional('deleteAfter', { + describe: 'delete all messages after reading ', + default: false + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + if (!argv.phone) { + throw new Error('phone is not defined'); + } + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } + } + await getSMSByUsers(sessionData, argv.phone, argv.page, argv.exportFile, argv.exportFormat, argv.deleteAfter); +}).command('pages', 'count of sms pages', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('phone', { + describe: 'contact phone number', + type: 'string' + }).positional('exportFile', { + describe: 'export to file', + default: './smsCount.list' + }).positional('exportFormat', { + describe: 'export format (xml, json, none)', + default: 'none' + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + if (!argv.phone) { + throw new Error('phone is not defined'); + } + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } + } + await getContactSMSPages(sessionData, argv.phone, argv.exportFile, argv.exportFormat); +}).command('deleteSMS', 'delete sms by smsId', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('messageId', { + describe: 'messageId or index', + type: 'string' + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + if (!argv.messageId) { + throw new Error('messageId is not defined'); + } + await deleteMessage(sessionData, argv.messageId); +}).command('mobileData', 'Enable/Disable or Reconnect Mobile Data', (yargs) => { + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('mode', { + describe: 'change mobile data to on,off or reconnect', + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + switch (argv.mode) { + case 'reconnect': { + await reconnect(sessionData); + return; + } + case 'on': { + break; + } + case 'off': { + break + } + default: { + throw new Error('Does not support Mode: ' + argv.mode + '. Supported only on,off,reconnect') + } + } + await controlMobileData(sessionData, argv.mode); +}).command('monitoring', 'current Monitoring status', (yargs) => { +// @ts-ignore + return yargs + .positional('url', { + describe: 'huawei host', + default: '192.168.8.1' + }).positional('exportFile', { + describe: 'export to file', + default: './monitoring.log' + }).positional('exportFormat', { + describe: 'export format (xml, json, none)', + default: 'none' + }) +}, async (argv) => { + const sessionData = await startSession(argv.url); + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } + } + await status(sessionData, argv.exportFile, argv.exportFormat); +}) + // .option('verbose', { + // alias: 'v', + // type: 'boolean', + // description: 'Run with verbose logging' + // }) + .parse() diff --git a/jslib/base64.js b/jslib/base64.js new file mode 100644 index 0000000..ad53bb8 --- /dev/null +++ b/jslib/base64.js @@ -0,0 +1,71 @@ +var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +var b64padchar="="; + +function hex2b64(h) { + var i; + var c; + var ret = ""; + for(i = 0; i+3 <= h.length; i+=3) { + c = parseInt(h.substring(i,i+3),16); + ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); + } + if(i+1 == h.length) { + c = parseInt(h.substring(i,i+1),16); + ret += b64map.charAt(c << 2); + } + else if(i+2 == h.length) { + c = parseInt(h.substring(i,i+2),16); + ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); + } + while((ret.length & 3) > 0) ret += b64padchar; + return ret; +} + +// convert a base64 string to hex +function b64tohex(s) { + var ret = "" + var i; + var k = 0; // b64 state, 0-3 + var slop; + for(i = 0; i < s.length; ++i) { + if(s.charAt(i) == b64padchar) break; + v = b64map.indexOf(s.charAt(i)); + if(v < 0) continue; + if(k == 0) { + ret += int2char(v >> 2); + slop = v & 3; + k = 1; + } + else if(k == 1) { + ret += int2char((slop << 2) | (v >> 4)); + slop = v & 0xf; + k = 2; + } + else if(k == 2) { + ret += int2char(slop); + ret += int2char(v >> 2); + slop = v & 3; + k = 3; + } + else { + ret += int2char((slop << 2) | (v >> 4)); + ret += int2char(v & 0xf); + k = 0; + } + } + if(k == 1) + ret += int2char(slop << 2); + return ret; +} + +// convert a base64 string to a byte/number array +function b64toBA(s) { + //piggyback on b64tohex for now, optimize later + var h = b64tohex(s); + var i; + var a = new Array(); + for(i = 0; 2*i < h.length; ++i) { + a[i] = parseInt(h.substring(2*i,2*i+2),16); + } + return a; +} diff --git a/jslib/crypto-1.1.js b/jslib/crypto-1.1.js new file mode 100644 index 0000000..16021f7 --- /dev/null +++ b/jslib/crypto-1.1.js @@ -0,0 +1,1524 @@ +/* crypto-1.2.4.js (c) 2013-2020 Kenji Urushima | kjur.github.io/jsrsasign/license + */ +/* + * crypto.js - Cryptographic Algorithm Provider class + * + * Copyright (c) 2013-2020 Kenji Urushima (kenji.urushima@gmail.com) + * + * This software is licensed under the terms of the MIT License. + * https://kjur.github.io/jsrsasign/license + * + * The above copyright and license notice shall be + * included in all copies or substantial portions of the Software. + */ +const CryptoJS = require('crypto-js'); +const { SecureRandom } = require('./rng'); +/** + * @fileOverview + * @name crypto-1.1.js + * @author Kenji Urushima kenji.urushima@gmail.com + * @version 1.2.4 (2020-Jul-28) + * @since jsrsasign 2.2 + * @license MIT License + */ + +/** + * kjur's class library name space + * @name KJUR + * @namespace kjur's class library name space + */ +if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; +/** + * kjur's cryptographic algorithm provider library name space + *

+ * This namespace privides following crytpgrahic classes. + *

+ * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. + *

+ * @name KJUR.crypto + * @namespace + */ +if (typeof KJUR.crypto == "undefined" || !KJUR.crypto) KJUR.crypto = {}; + +/** + * static object for cryptographic function utilities + * @name KJUR.crypto.Util + * @class static object for cryptographic function utilities + * @property {Array} DIGESTINFOHEAD PKCS#1 DigestInfo heading hexadecimal bytes for each hash algorithms + * @property {Array} DEFAULTPROVIDER associative array of default provider name for each hash and signature algorithms + * @description + */ +KJUR.crypto.Util = new function() { + this.DIGESTINFOHEAD = { + 'sha1': "3021300906052b0e03021a05000414", + 'sha224': "302d300d06096086480165030402040500041c", + 'sha256': "3031300d060960864801650304020105000420", + 'sha384': "3041300d060960864801650304020205000430", + 'sha512': "3051300d060960864801650304020305000440", + 'md2': "3020300c06082a864886f70d020205000410", + 'md5': "3020300c06082a864886f70d020505000410", + 'ripemd160': "3021300906052b2403020105000414", + }; + + /* + * @since crypto 1.1.1 + */ + this.DEFAULTPROVIDER = { + 'md5': 'cryptojs', + 'sha1': 'cryptojs', + 'sha224': 'cryptojs', + 'sha256': 'cryptojs', + 'sha384': 'cryptojs', + 'sha512': 'cryptojs', + 'ripemd160': 'cryptojs', + 'hmacmd5': 'cryptojs', + 'hmacsha1': 'cryptojs', + 'hmacsha224': 'cryptojs', + 'hmacsha256': 'cryptojs', + 'hmacsha384': 'cryptojs', + 'hmacsha512': 'cryptojs', + 'hmacripemd160': 'cryptojs', + + 'MD5withRSA': 'cryptojs/jsrsa', + 'SHA1withRSA': 'cryptojs/jsrsa', + 'SHA224withRSA': 'cryptojs/jsrsa', + 'SHA256withRSA': 'cryptojs/jsrsa', + 'SHA384withRSA': 'cryptojs/jsrsa', + 'SHA512withRSA': 'cryptojs/jsrsa', + 'RIPEMD160withRSA': 'cryptojs/jsrsa', + + 'MD5withECDSA': 'cryptojs/jsrsa', + 'SHA1withECDSA': 'cryptojs/jsrsa', + 'SHA224withECDSA': 'cryptojs/jsrsa', + 'SHA256withECDSA': 'cryptojs/jsrsa', + 'SHA384withECDSA': 'cryptojs/jsrsa', + 'SHA512withECDSA': 'cryptojs/jsrsa', + 'RIPEMD160withECDSA': 'cryptojs/jsrsa', + + 'SHA1withDSA': 'cryptojs/jsrsa', + 'SHA224withDSA': 'cryptojs/jsrsa', + 'SHA256withDSA': 'cryptojs/jsrsa', + + 'MD5withRSAandMGF1': 'cryptojs/jsrsa', + 'SHAwithRSAandMGF1': 'cryptojs/jsrsa', + 'SHA1withRSAandMGF1': 'cryptojs/jsrsa', + 'SHA224withRSAandMGF1': 'cryptojs/jsrsa', + 'SHA256withRSAandMGF1': 'cryptojs/jsrsa', + 'SHA384withRSAandMGF1': 'cryptojs/jsrsa', + 'SHA512withRSAandMGF1': 'cryptojs/jsrsa', + 'RIPEMD160withRSAandMGF1': 'cryptojs/jsrsa', + }; + + /* + * @since crypto 1.1.2 + */ + this.CRYPTOJSMESSAGEDIGESTNAME = { + 'md5': CryptoJS.algo.MD5, + 'sha1': CryptoJS.algo.SHA1, + 'sha224': CryptoJS.algo.SHA224, + 'sha256': CryptoJS.algo.SHA256, + 'sha384': CryptoJS.algo.SHA384, + 'sha512': CryptoJS.algo.SHA512, + 'ripemd160': CryptoJS.algo.RIPEMD160 + }; + + /** + * get hexadecimal DigestInfo + * @name getDigestInfoHex + * @memberOf KJUR.crypto.Util + * @function + * @param {String} hHash hexadecimal hash value + * @param {String} alg hash algorithm name (ex. 'sha1') + * @return {String} hexadecimal string DigestInfo ASN.1 structure + */ + this.getDigestInfoHex = function(hHash, alg) { + if (typeof this.DIGESTINFOHEAD[alg] == "undefined") + throw "alg not supported in Util.DIGESTINFOHEAD: " + alg; + return this.DIGESTINFOHEAD[alg] + hHash; + }; + + /** + * get PKCS#1 padded hexadecimal DigestInfo + * @name getPaddedDigestInfoHex + * @memberOf KJUR.crypto.Util + * @function + * @param {String} hHash hexadecimal hash value of message to be signed + * @param {String} alg hash algorithm name (ex. 'sha1') + * @param {Integer} keySize key bit length (ex. 1024) + * @return {String} hexadecimal string of PKCS#1 padded DigestInfo + */ + this.getPaddedDigestInfoHex = function(hHash, alg, keySize) { + var hDigestInfo = this.getDigestInfoHex(hHash, alg); + var pmStrLen = keySize / 4; // minimum PM length + + if (hDigestInfo.length + 22 > pmStrLen) // len(0001+ff(*8)+00+hDigestInfo)=22 + throw "key is too short for SigAlg: keylen=" + keySize + "," + alg; + + var hHead = "0001"; + var hTail = "00" + hDigestInfo; + var hMid = ""; + var fLen = pmStrLen - hHead.length - hTail.length; + for (var i = 0; i < fLen; i += 2) { + hMid += "ff"; + } + var hPaddedMessage = hHead + hMid + hTail; + return hPaddedMessage; + }; + + /** + * get hexadecimal hash of string with specified algorithm + * @name hashString + * @memberOf KJUR.crypto.Util + * @function + * @param {String} s raw input string to be hashed + * @param {String} alg hash algorithm name + * @return {String} hexadecimal string of hash value + * @since 1.1.1 + */ + this.hashString = function(s, alg) { + var md = new KJUR.crypto.MessageDigest({'alg': alg}); + return md.digestString(s); + }; + + /** + * get hexadecimal hash of hexadecimal string with specified algorithm + * @name hashHex + * @memberOf KJUR.crypto.Util + * @function + * @param {String} sHex input hexadecimal string to be hashed + * @param {String} alg hash algorithm name + * @return {String} hexadecimal string of hash value + * @since 1.1.1 + */ + this.hashHex = function(sHex, alg) { + var md = new KJUR.crypto.MessageDigest({'alg': alg}); + return md.digestHex(sHex); + }; + + /** + * get hexadecimal SHA1 hash of string + * @name sha1 + * @memberOf KJUR.crypto.Util + * @function + * @param {String} s raw input string to be hashed + * @return {String} hexadecimal string of hash value + * @since 1.0.3 + */ + this.sha1 = function(s) { + return this.hashString(s, 'sha1'); + }; + + /** + * get hexadecimal SHA256 hash of string + * @name sha256 + * @memberOf KJUR.crypto.Util + * @function + * @param {String} s raw input string to be hashed + * @return {String} hexadecimal string of hash value + * @since 1.0.3 + */ + this.sha256 = function(s) { + return this.hashString(s, 'sha256'); + }; + + this.sha256Hex = function(s) { + return this.hashHex(s, 'sha256'); + }; + + /** + * get hexadecimal SHA512 hash of string + * @name sha512 + * @memberOf KJUR.crypto.Util + * @function + * @param {String} s raw input string to be hashed + * @return {String} hexadecimal string of hash value + * @since 1.0.3 + */ + this.sha512 = function(s) { + return this.hashString(s, 'sha512'); + }; + + this.sha512Hex = function(s) { + return this.hashHex(s, 'sha512'); + }; + + /** + * check if key object (RSA/DSA/ECDSA) or not + * @name isKey + * @memberOf KJUR.crypto.Util + * @function + * @param {Object} obj any type argument to be checked + * @return {Boolean} true if this is key object otherwise false + * @since 1.0.3 + */ + this.isKey = function(obj) { + if (obj instanceof RSAKey || + obj instanceof KJUR.crypto.DSA || + obj instanceof KJUR.crypto.ECDSA) { + return true; + } else { + return false; + } + }; +}; + +/** + * get hexadecimal MD5 hash of string + * @name md5 + * @memberOf KJUR.crypto.Util + * @function + * @param {String} s input string to be hashed + * @return {String} hexadecimal string of hash value + * @since 1.0.3 + * @example + * Util.md5('aaa') → 47bce5c74f589f4867dbd57e9ca9f808 + */ +KJUR.crypto.Util.md5 = function(s) { + var md = new KJUR.crypto.MessageDigest({'alg':'md5', 'prov':'cryptojs'}); + return md.digestString(s); +}; + +/** + * get hexadecimal RIPEMD160 hash of string + * @name ripemd160 + * @memberOf KJUR.crypto.Util + * @function + * @param {String} s input string to be hashed + * @return {String} hexadecimal string of hash value + * @since 1.0.3 + * @example + * KJUR.crypto.Util.ripemd160("aaa") → 08889bd7b151aa174c21f33f59147fa65381edea + */ +KJUR.crypto.Util.ripemd160 = function(s) { + var md = new KJUR.crypto.MessageDigest({'alg':'ripemd160', 'prov':'cryptojs'}); + return md.digestString(s); +}; + +// @since jsrsasign 7.0.0 crypto 1.1.11 +KJUR.crypto.Util.SECURERANDOMGEN = new SecureRandom(); + +/** + * get hexadecimal string of random value from with specified byte length
+ * @name getRandomHexOfNbytes + * @memberOf KJUR.crypto.Util + * @function + * @param {Integer} n length of bytes of random + * @return {String} hexadecimal string of random + * @since jsrsasign 7.0.0 crypto 1.1.11 + * @example + * KJUR.crypto.Util.getRandomHexOfNbytes(3) → "6314af", "000000" or "001fb4" + * KJUR.crypto.Util.getRandomHexOfNbytes(128) → "8fbc..." in 1024bits + */ +KJUR.crypto.Util.getRandomHexOfNbytes = function(n) { + var ba = new Array(n); + KJUR.crypto.Util.SECURERANDOMGEN.nextBytes(ba); + return BAtohex(ba); +}; + +/** + * get BigInteger object of random value from with specified byte length
+ * @name getRandomBigIntegerOfNbytes + * @memberOf KJUR.crypto.Util + * @function + * @param {Integer} n length of bytes of random + * @return {BigInteger} BigInteger object of specified random value + * @since jsrsasign 7.0.0 crypto 1.1.11 + * @example + * KJUR.crypto.Util.getRandomBigIntegerOfNbytes(3) → 6314af of BigInteger + * KJUR.crypto.Util.getRandomBigIntegerOfNbytes(128) → 8fbc... of BigInteger + */ +KJUR.crypto.Util.getRandomBigIntegerOfNbytes = function(n) { + return new BigInteger(KJUR.crypto.Util.getRandomHexOfNbytes(n), 16); +}; + +/** + * get hexadecimal string of random value from with specified bit length
+ * @name getRandomHexOfNbits + * @memberOf KJUR.crypto.Util + * @function + * @param {Integer} n length of bits of random + * @return {String} hexadecimal string of random + * @since jsrsasign 7.0.0 crypto 1.1.11 + * @example + * KJUR.crypto.Util.getRandomHexOfNbits(24) → "6314af", "000000" or "001fb4" + * KJUR.crypto.Util.getRandomHexOfNbits(1024) → "8fbc..." in 1024bits + */ +KJUR.crypto.Util.getRandomHexOfNbits = function(n) { + var n_remainder = n % 8; + var n_quotient = (n - n_remainder) / 8; + var ba = new Array(n_quotient + 1); + KJUR.crypto.Util.SECURERANDOMGEN.nextBytes(ba); + ba[0] = (((255 << n_remainder) & 255) ^ 255) & ba[0]; + return BAtohex(ba); +}; + +/** + * get BigInteger object of random value from with specified bit length
+ * @name getRandomBigIntegerOfNbits + * @memberOf KJUR.crypto.Util + * @function + * @param {Integer} n length of bits of random + * @return {BigInteger} BigInteger object of specified random value + * @since jsrsasign 7.0.0 crypto 1.1.11 + * @example + * KJUR.crypto.Util.getRandomBigIntegerOfNbits(24) → 6314af of BigInteger + * KJUR.crypto.Util.getRandomBigIntegerOfNbits(1024) → 8fbc... of BigInteger + */ +KJUR.crypto.Util.getRandomBigIntegerOfNbits = function(n) { + return new BigInteger(KJUR.crypto.Util.getRandomHexOfNbits(n), 16); +}; + +/** + * get BigInteger object of random value from zero to max value
+ * @name getRandomBigIntegerZeroToMax + * @memberOf KJUR.crypto.Util + * @function + * @param {BigInteger} biMax max value of BigInteger object for random value + * @return {BigInteger} BigInteger object of specified random value + * @since jsrsasign 7.0.0 crypto 1.1.11 + * @description + * This static method generates a BigInteger object with random value + * greater than or equal to zero and smaller than or equal to biMax + * (i.e. 0 ≤ result ≤ biMax). + * @example + * biMax = new BigInteger("3fa411...", 16); + * KJUR.crypto.Util.getRandomBigIntegerZeroToMax(biMax) → 8fbc... of BigInteger + */ +KJUR.crypto.Util.getRandomBigIntegerZeroToMax = function(biMax) { + var bitLenMax = biMax.bitLength(); + while (1) { + var biRand = KJUR.crypto.Util.getRandomBigIntegerOfNbits(bitLenMax); + if (biMax.compareTo(biRand) != -1) return biRand; + } +}; + +/** + * get BigInteger object of random value from min value to max value
+ * @name getRandomBigIntegerMinToMax + * @memberOf KJUR.crypto.Util + * @function + * @param {BigInteger} biMin min value of BigInteger object for random value + * @param {BigInteger} biMax max value of BigInteger object for random value + * @return {BigInteger} BigInteger object of specified random value + * @since jsrsasign 7.0.0 crypto 1.1.11 + * @description + * This static method generates a BigInteger object with random value + * greater than or equal to biMin and smaller than or equal to biMax + * (i.e. biMin ≤ result ≤ biMax). + * @example + * biMin = new BigInteger("2fa411...", 16); + * biMax = new BigInteger("3fa411...", 16); + * KJUR.crypto.Util.getRandomBigIntegerMinToMax(biMin, biMax) → 32f1... of BigInteger + */ +KJUR.crypto.Util.getRandomBigIntegerMinToMax = function(biMin, biMax) { + var flagCompare = biMin.compareTo(biMax); + if (flagCompare == 1) throw "biMin is greater than biMax"; + if (flagCompare == 0) return biMin; + + var biDiff = biMax.subtract(biMin); + var biRand = KJUR.crypto.Util.getRandomBigIntegerZeroToMax(biDiff); + return biRand.add(biMin); +}; + +// === Mac =============================================================== + +/** + * MessageDigest class which is very similar to java.security.MessageDigest class
+ * @name KJUR.crypto.MessageDigest + * @class MessageDigest class which is very similar to java.security.MessageDigest class + * @param {Array} params parameters for constructor + * @property {Array} HASHLENGTH static Array of resulted byte length of hash (ex. HASHLENGTH["sha1"] == 20) + * @description + *
+ * Currently this supports following algorithm and providers combination: + * + * @example + * // CryptoJS provider sample + * var md = new KJUR.crypto.MessageDigest({alg: "sha1", prov: "cryptojs"}); + * md.updateString('aaa') + * var mdHex = md.digest() + * + * // SJCL(Stanford JavaScript Crypto Library) provider sample + * var md = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "sjcl"}); // sjcl supports sha256 only + * md.updateString('aaa') + * var mdHex = md.digest() + * + * // HASHLENGTH property + * KJUR.crypto.MessageDigest.HASHLENGTH['sha1'] &rarr 20 + * KJUR.crypto.MessageDigest.HASHLENGTH['sha512'] &rarr 64 + */ +KJUR.crypto.MessageDigest = function(params) { + var md = null; + var algName = null; + var provName = null; + + /** + * set hash algorithm and provider
+ * @name setAlgAndProvider + * @memberOf KJUR.crypto.MessageDigest# + * @function + * @param {String} alg hash algorithm name + * @param {String} prov provider name + * @description + * This methods set an algorithm and a cryptographic provider.
+ * Here is acceptable algorithm names ignoring cases and hyphens: + * + * NOTE: Since jsrsasign 6.2.0 crypto 1.1.10, this method ignores + * upper or lower cases. Also any hyphens (i.e. "-") will be ignored + * so that "SHA1" or "SHA-1" will be acceptable. + * @example + * // for SHA1 + * md.setAlgAndProvider('sha1', 'cryptojs'); + * md.setAlgAndProvider('SHA1'); + * // for RIPEMD160 + * md.setAlgAndProvider('ripemd160', 'cryptojs'); + */ + this.setAlgAndProvider = function(alg, prov) { + alg = KJUR.crypto.MessageDigest.getCanonicalAlgName(alg); + + if (alg !== null && prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; + + // for cryptojs + if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(alg) != -1 && + prov == 'cryptojs') { + try { + this.md = KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[alg].create(); + } catch (ex) { + throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; + } + this.updateString = function(str) { + this.md.update(str); + }; + this.updateHex = function(hex) { + var wHex = CryptoJS.enc.Hex.parse(hex); + this.md.update(wHex); + }; + this.digest = function() { + var hash = this.md.finalize(); + return hash.toString(CryptoJS.enc.Hex); + }; + this.digestString = function(str) { + this.updateString(str); + return this.digest(); + }; + this.digestHex = function(hex) { + this.updateHex(hex); + return this.digest(); + }; + } + if (':sha256:'.indexOf(alg) != -1 && + prov == 'sjcl') { + try { + this.md = new sjcl.hash.sha256(); + } catch (ex) { + throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; + } + this.updateString = function(str) { + this.md.update(str); + }; + this.updateHex = function(hex) { + var baHex = sjcl.codec.hex.toBits(hex); + this.md.update(baHex); + }; + this.digest = function() { + var hash = this.md.finalize(); + return sjcl.codec.hex.fromBits(hash); + }; + this.digestString = function(str) { + this.updateString(str); + return this.digest(); + }; + this.digestHex = function(hex) { + this.updateHex(hex); + return this.digest(); + }; + } + }; + + /** + * update digest by specified string + * @name updateString + * @memberOf KJUR.crypto.MessageDigest# + * @function + * @param {String} str string to update + * @description + * @example + * md.updateString('New York'); + */ + this.updateString = function(str) { + throw "updateString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; + }; + + /** + * update digest by specified hexadecimal string + * @name updateHex + * @memberOf KJUR.crypto.MessageDigest# + * @function + * @param {String} hex hexadecimal string to update + * @description + * @example + * md.updateHex('0afe36'); + */ + this.updateHex = function(hex) { + throw "updateHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; + }; + + /** + * completes hash calculation and returns hash result + * @name digest + * @memberOf KJUR.crypto.MessageDigest# + * @function + * @description + * @example + * md.digest() + */ + this.digest = function() { + throw "digest() not supported for this alg/prov: " + this.algName + "/" + this.provName; + }; + + /** + * performs final update on the digest using string, then completes the digest computation + * @name digestString + * @memberOf KJUR.crypto.MessageDigest# + * @function + * @param {String} str string to final update + * @description + * @example + * md.digestString('aaa') + */ + this.digestString = function(str) { + throw "digestString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; + }; + + /** + * performs final update on the digest using hexadecimal string, then completes the digest computation + * @name digestHex + * @memberOf KJUR.crypto.MessageDigest# + * @function + * @param {String} hex hexadecimal string to final update + * @description + * @example + * md.digestHex('0f2abd') + */ + this.digestHex = function(hex) { + throw "digestHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; + }; + + if (params !== undefined) { + if (params['alg'] !== undefined) { + this.algName = params['alg']; + if (params['prov'] === undefined) + this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; + this.setAlgAndProvider(this.algName, this.provName); + } + } +}; + +/** + * get canonical hash algorithm name
+ * @name getCanonicalAlgName + * @memberOf KJUR.crypto.MessageDigest + * @function + * @param {String} alg hash algorithm name (ex. MD5, SHA-1, SHA1, SHA512 et.al.) + * @return {String} canonical hash algorithm name + * @since jsrsasign 6.2.0 crypto 1.1.10 + * @description + * This static method normalizes from any hash algorithm name such as + * "SHA-1", "SHA1", "MD5", "sha512" to lower case name without hyphens + * such as "sha1". + * @example + * KJUR.crypto.MessageDigest.getCanonicalAlgName("SHA-1") &rarr "sha1" + * KJUR.crypto.MessageDigest.getCanonicalAlgName("MD5") &rarr "md5" + */ +KJUR.crypto.MessageDigest.getCanonicalAlgName = function(alg) { + if (typeof alg === "string") { + alg = alg.toLowerCase(); + alg = alg.replace(/-/, ''); + } + return alg; +}; + +/** + * get resulted hash byte length for specified algorithm name
+ * @name getHashLength + * @memberOf KJUR.crypto.MessageDigest + * @function + * @param {String} alg non-canonicalized hash algorithm name (ex. MD5, SHA-1, SHA1, SHA512 et.al.) + * @return {Integer} resulted hash byte length + * @since jsrsasign 6.2.0 crypto 1.1.10 + * @description + * This static method returns resulted byte length for specified algorithm name such as "SHA-1". + * @example + * KJUR.crypto.MessageDigest.getHashLength("SHA-1") &rarr 20 + * KJUR.crypto.MessageDigest.getHashLength("sha1") &rarr 20 + */ +KJUR.crypto.MessageDigest.getHashLength = function(alg) { + var MD = KJUR.crypto.MessageDigest + var alg2 = MD.getCanonicalAlgName(alg); + if (MD.HASHLENGTH[alg2] === undefined) + throw "not supported algorithm: " + alg; + return MD.HASHLENGTH[alg2]; +}; + +// described in KJUR.crypto.MessageDigest class (since jsrsasign 6.2.0 crypto 1.1.10) +KJUR.crypto.MessageDigest.HASHLENGTH = { + 'md5': 16, + 'sha1': 20, + 'sha224': 28, + 'sha256': 32, + 'sha384': 48, + 'sha512': 64, + 'ripemd160': 20 +}; + +// === Mac =============================================================== + +/** + * Mac(Message Authentication Code) class which is very similar to java.security.Mac class + * @name KJUR.crypto.Mac + * @class Mac class which is very similar to java.security.Mac class + * @param {Array} params parameters for constructor + * @description + *
+ * Currently this supports following algorithm and providers combination: + * + * NOTE: HmacSHA224 and HmacSHA384 issue was fixed since jsrsasign 4.1.4. + * Please use 'ext/cryptojs-312-core-fix*.js' instead of 'core.js' of original CryptoJS + * to avoid those issue. + *
+ * NOTE2: Hmac signature bug was fixed in jsrsasign 4.9.0 by providing CryptoJS + * bug workaround. + *
+ * Please see {@link KJUR.crypto.Mac.setPassword}, how to provide password + * in various ways in detail. + * @example + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": "pass"}); + * mac.updateString('aaa') + * mac.doFinal() → "5737da..." + * + * // other password representation + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"hex": "6161"}}); + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"utf8": "aa"}}); + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"rstr": "\x61\x61"}}); + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"b64": "Mi02/+...a=="}}); + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"b64u": "Mi02_-...a"}}); + */ +KJUR.crypto.Mac = function(params) { + var mac = null; + var pass = null; + var algName = null; + var provName = null; + var algProv = null; + + this.setAlgAndProvider = function(alg, prov) { + alg = alg.toLowerCase(); + + if (alg == null) alg = "hmacsha1"; + + alg = alg.toLowerCase(); + if (alg.substr(0, 4) != "hmac") { + throw "setAlgAndProvider unsupported HMAC alg: " + alg; + } + + if (prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; + this.algProv = alg + "/" + prov; + + var hashAlg = alg.substr(4); + + // for cryptojs + if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(hashAlg) != -1 && + prov == 'cryptojs') { + try { + var mdObj = KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[hashAlg]; + this.mac = CryptoJS.algo.HMAC.create(mdObj, this.pass); + } catch (ex) { + throw "setAlgAndProvider hash alg set fail hashAlg=" + hashAlg + "/" + ex; + } + this.updateString = function(str) { + this.mac.update(str); + }; + this.updateHex = function(hex) { + var wHex = CryptoJS.enc.Hex.parse(hex); + this.mac.update(wHex); + }; + this.doFinal = function() { + var hash = this.mac.finalize(); + return hash.toString(CryptoJS.enc.Hex); + }; + this.doFinalString = function(str) { + this.updateString(str); + return this.doFinal(); + }; + this.doFinalHex = function(hex) { + this.updateHex(hex); + return this.doFinal(); + }; + } + }; + + /** + * update digest by specified string
+ * @name updateString + * @memberOf KJUR.crypto.Mac# + * @function + * @param {String} str string to update + * + * @description + * @example + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": "pass"}); + * mac.updateString('aaa') + * mac.doFinal() → "5737da..." + */ + this.updateString = function(str) { + throw "updateString(str) not supported for this alg/prov: " + this.algProv; + }; + + /** + * update digest by specified hexadecimal string
+ * @name updateHex + * @memberOf KJUR.crypto.Mac# + * @function + * @param {String} hex hexadecimal string to update + * + * @description + * @example + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": "pass"}); + * mac.updateHex('616161') + * mac.doFinal() → "5737da..." + */ + this.updateHex = function(hex) { + throw "updateHex(hex) not supported for this alg/prov: " + this.algProv; + }; + + /** + * completes hash calculation and returns hash result
+ * @name doFinal + * @memberOf KJUR.crypto.Mac# + * @function + * @returns hexadecimal string of Mac result value + * + * @description + * @example + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": "pass"}); + * mac.updateString('aaa') + * mac.doFinal() → "5737da..." + */ + this.doFinal = function() { + throw "digest() not supported for this alg/prov: " + this.algProv; + }; + + /** + * performs final update on the digest using string, then completes the digest computation
+ * @name doFinalString + * @memberOf KJUR.crypto.Mac# + * @function + * @param {String} str raw string to final update + * @returns hexadecimal string of Mac result value + * + * @description + * @example + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": "pass"}); + * mac.doFinalString("aaa") → "5737da..." + */ + this.doFinalString = function(str) { + throw "digestString(str) not supported for this alg/prov: " + this.algProv; + }; + + /** + * performs final update on the digest using hexadecimal string, then completes the digest computation
+ * @name doFinalHex + * @memberOf KJUR.crypto.Mac# + * @function + * @param {String} hex hexadecimal string to final update + * @returns hexadecimal string of Mac result value + * + * @description + * @example + * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": "pass"}); + * mac.doFinalHex("616161") → "5737da..." + */ + this.doFinalHex = function(hex) { + throw "digestHex(hex) not supported for this alg/prov: " + this.algProv; + }; + + /** + * set password for Mac
+ * @name setPassword + * @memberOf KJUR.crypto.Mac# + * @function + * @param {Object} pass password for Mac + * @since crypto 1.1.7 jsrsasign 4.9.0 + * @description + * This method will set password for (H)Mac internally. + * Argument 'pass' can be specified as following: + * + * It is *STRONGLY RECOMMENDED* that explicit representation of password argument + * to avoid ambiguity. For example string "6161" can mean a string "6161" or + * a hexadecimal string of "aa" (i.e. \x61\x61). + * @example + * mac = KJUR.crypto.Mac({'alg': 'hmacsha256'}); + * // set password by implicit raw string + * mac.setPassword("\x65\x70\xb9\x0b"); + * mac.setPassword("password"); + * // set password by implicit hexadecimal string + * mac.setPassword("6570b90b"); + * mac.setPassword("6570B90B"); + * // set password by explicit raw string + * mac.setPassword({"rstr": "\x65\x70\xb9\x0b"}); + * // set password by explicit hexadecimal string + * mac.setPassword({"hex": "6570b90b"}); + * // set password by explicit utf8 string + * mac.setPassword({"utf8": "passwordパスワード"); + * // set password by explicit Base64 string + * mac.setPassword({"b64": "Mb+c3f/=="}); + * // set password by explicit Base64URL string + * mac.setPassword({"b64u": "Mb-c3f_"}); + */ + this.setPassword = function(pass) { + // internal this.pass shall be CryptoJS DWord Object for CryptoJS bug + // work around. CrytoJS HMac password can be passed by + // raw string as described in the manual however it doesn't + // work properly in some case. If password was passed + // by CryptoJS DWord which is not described in the manual + // it seems to work. (fixed since crypto 1.1.7) + + if (typeof pass == 'string') { + var hPass = pass; + if (pass.length % 2 == 1 || ! pass.match(/^[0-9A-Fa-f]+$/)) { // raw str + hPass = rstrtohex(pass); + } + this.pass = CryptoJS.enc.Hex.parse(hPass); + return; + } + + if (typeof pass != 'object') + throw "KJUR.crypto.Mac unsupported password type: " + pass; + + var hPass = null; + if (pass.hex !== undefined) { + if (pass.hex.length % 2 != 0 || ! pass.hex.match(/^[0-9A-Fa-f]+$/)) + throw "Mac: wrong hex password: " + pass.hex; + hPass = pass.hex; + } + if (pass.utf8 !== undefined) hPass = utf8tohex(pass.utf8); + if (pass.rstr !== undefined) hPass = rstrtohex(pass.rstr); + if (pass.b64 !== undefined) hPass = b64tohex(pass.b64); + if (pass.b64u !== undefined) hPass = b64utohex(pass.b64u); + + if (hPass == null) + throw "KJUR.crypto.Mac unsupported password type: " + pass; + + this.pass = CryptoJS.enc.Hex.parse(hPass); + }; + + if (params !== undefined) { + if (params.pass !== undefined) { + this.setPassword(params.pass); + } + if (params.alg !== undefined) { + this.algName = params.alg; + if (params['prov'] === undefined) + this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; + this.setAlgAndProvider(this.algName, this.provName); + } + } +}; + +// ====== Signature class ==================================================== +/** + * Signature class which is very similar to java.security.Signature class + * @name KJUR.crypto.Signature + * @class Signature class which is very similar to java.security.Signature class + * @param {Array} params parameters for constructor + * @property {String} state Current state of this signature object whether 'SIGN', 'VERIFY' or null + * @description + *
+ * As for params of constructor's argument, it can be specify following attributes: + * + *

SUPPORTED ALGORITHMS AND PROVIDERS

+ * This Signature class supports following signature algorithm and provider names: + * + * As for RSA-PSS signature algorithm names and signing parameters + * such as MGF function and salt length, please see + * {@link KJUR.asn1.x509.AlgorithmIdentifier} class. + * + * Here are supported elliptic cryptographic curve names and their aliases for ECDSA: + * + * NOTE1: DSA signing algorithm is also supported since crypto 1.1.5. + *

EXAMPLES

+ * @example + * // RSA signature generation + * var sig = new KJUR.crypto.Signature({"alg": "SHA1withRSA"}); + * sig.init(prvKeyPEM); + * sig.updateString('aaa'); + * var hSigVal = sig.sign(); + * + * // DSA signature validation + * var sig2 = new KJUR.crypto.Signature({"alg": "SHA1withDSA"}); + * sig2.init(certPEM); + * sig.updateString('aaa'); + * var isValid = sig2.verify(hSigVal); + * + * // ECDSA signing + * var sig = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); + * sig.init(prvKeyPEM); + * sig.updateString('aaa'); + * var sigValueHex = sig.sign(); + * + * // ECDSA verifying + * var sig2 = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); + * sig.init(certPEM); + * sig.updateString('aaa'); + * var isValid = sig.verify(sigValueHex); + */ +KJUR.crypto.Signature = function(params) { + var prvKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for signing + var pubKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for verifying + + var md = null; // KJUR.crypto.MessageDigest object + var sig = null; + var algName = null; + var provName = null; + var algProvName = null; + var mdAlgName = null; + var pubkeyAlgName = null; // rsa,ecdsa,rsaandmgf1(=rsapss) + var state = null; + var pssSaltLen = -1; + var initParams = null; + + var sHashHex = null; // hex hash value for hex + var hDigestInfo = null; + var hPaddedDigestInfo = null; + var hSign = null; + + this._setAlgNames = function() { + var matchResult = this.algName.match(/^(.+)with(.+)$/); + if (matchResult) { + this.mdAlgName = matchResult[1].toLowerCase(); + this.pubkeyAlgName = matchResult[2].toLowerCase(); + if (this.pubkeyAlgName == "rsaandmgf1" && + this.mdAlgName == "sha") { + this.mdAlgName = "sha1"; + } + } + }; + + this._zeroPaddingOfSignature = function(hex, bitLength) { + var s = ""; + var nZero = bitLength / 4 - hex.length; + for (var i = 0; i < nZero; i++) { + s = s + "0"; + } + return s + hex; + }; + + /** + * set signature algorithm and provider + * @name setAlgAndProvider + * @memberOf KJUR.crypto.Signature# + * @function + * @param {String} alg signature algorithm name + * @param {String} prov provider name + * @description + * @example + * md.setAlgAndProvider('SHA1withRSA', 'cryptojs/jsrsa'); + */ + this.setAlgAndProvider = function(alg, prov) { + this._setAlgNames(); + if (prov != 'cryptojs/jsrsa') + throw new Error("provider not supported: " + prov); + + if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(this.mdAlgName) != -1) { + try { + this.md = new KJUR.crypto.MessageDigest({'alg':this.mdAlgName}); + } catch (ex) { + throw new Error("setAlgAndProvider hash alg set fail alg=" + + this.mdAlgName + "/" + ex); + } + + this.init = function(keyparam, pass) { + var keyObj = null; + try { + if (pass === undefined) { + keyObj = KEYUTIL.getKey(keyparam); + } else { + keyObj = KEYUTIL.getKey(keyparam, pass); + } + } catch (ex) { + throw "init failed:" + ex; + } + + if (keyObj.isPrivate === true) { + this.prvKey = keyObj; + this.state = "SIGN"; + } else if (keyObj.isPublic === true) { + this.pubKey = keyObj; + this.state = "VERIFY"; + } else { + throw "init failed.:" + keyObj; + } + }; + + this.updateString = function(str) { + this.md.updateString(str); + }; + + this.updateHex = function(hex) { + this.md.updateHex(hex); + }; + + this.sign = function() { + this.sHashHex = this.md.digest(); + // hex parameter EC public key + if (this.prvKey === undefined && + this.ecprvhex !== undefined && + this.eccurvename !== undefined && + KJUR.crypto.ECDSA !== undefined) { + this.prvKey = new KJUR.crypto.ECDSA({'curve': this.eccurvename, + prv: this.ecprvhex}); + } + + // RSAPSS + if (this.prvKey instanceof RSAKey && + this.pubkeyAlgName === "rsaandmgf1") { + this.hSign = this.prvKey.signWithMessageHashPSS(this.sHashHex, + this.mdAlgName, + this.pssSaltLen); + // RSA + } else if (this.prvKey instanceof RSAKey && + this.pubkeyAlgName === "rsa") { + this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, + this.mdAlgName); + // ECDSA + } else if (this.prvKey instanceof KJUR.crypto.ECDSA) { + this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); + // DSA + } else if (this.prvKey instanceof KJUR.crypto.DSA) { + this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); + } else { + throw "Signature: unsupported private key alg: " + this.pubkeyAlgName; + } + return this.hSign; + }; + this.signString = function(str) { + this.updateString(str); + return this.sign(); + }; + this.signHex = function(hex) { + this.updateHex(hex); + return this.sign(); + }; + this.verify = function(hSigVal) { + this.sHashHex = this.md.digest(); + // hex parameter EC public key + if (this.pubKey === undefined && + this.ecpubhex !== undefined && + this.eccurvename !== undefined && + KJUR.crypto.ECDSA !== undefined) { + this.pubKey = new KJUR.crypto.ECDSA({curve: this.eccurvename, + pub: this.ecpubhex}); + } + + // RSAPSS + if (this.pubKey instanceof RSAKey && + this.pubkeyAlgName === "rsaandmgf1") { + return this.pubKey.verifyWithMessageHashPSS(this.sHashHex, hSigVal, + this.mdAlgName, + this.pssSaltLen); + // RSA + } else if (this.pubKey instanceof RSAKey && + this.pubkeyAlgName === "rsa") { + return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); + // ECDSA + } else if (KJUR.crypto.ECDSA !== undefined && + this.pubKey instanceof KJUR.crypto.ECDSA) { + return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); + // DSA + } else if (KJUR.crypto.DSA !== undefined && + this.pubKey instanceof KJUR.crypto.DSA) { + return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); + } else { + throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; + } + }; + } + }; + + /** + * Initialize this object for signing or verifying depends on key + * @name init + * @memberOf KJUR.crypto.Signature# + * @function + * @param {Object} key specifying public or private key as plain/encrypted PKCS#5/8 PEM file, certificate PEM or {@link RSAKey}, {@link KJUR.crypto.DSA} or {@link KJUR.crypto.ECDSA} object + * @param {String} pass (OPTION) passcode for encrypted private key + * @since crypto 1.1.3 + * @description + * This method is very useful initialize method for Signature class since + * you just specify key then this method will automatically initialize it + * using {@link KEYUTIL.getKey} method. + * As for 'key', following argument type are supported: + *
signing
+ * + *
verification
+ * + * @example + * sig.init(sCertPEM) + */ + this.init = function(key, pass) { + throw "init(key, pass) not supported for this alg:prov=" + + this.algProvName; + }; + + /** + * Updates the data to be signed or verified by a string + * @name updateString + * @memberOf KJUR.crypto.Signature# + * @function + * @param {String} str string to use for the update + * @description + * @example + * sig.updateString('aaa') + */ + this.updateString = function(str) { + throw "updateString(str) not supported for this alg:prov=" + this.algProvName; + }; + + /** + * Updates the data to be signed or verified by a hexadecimal string + * @name updateHex + * @memberOf KJUR.crypto.Signature# + * @function + * @param {String} hex hexadecimal string to use for the update + * @description + * @example + * sig.updateHex('1f2f3f') + */ + this.updateHex = function(hex) { + throw "updateHex(hex) not supported for this alg:prov=" + this.algProvName; + }; + + /** + * Returns the signature bytes of all data updates as a hexadecimal string + * @name sign + * @memberOf KJUR.crypto.Signature# + * @function + * @return the signature bytes as a hexadecimal string + * @description + * @example + * var hSigValue = sig.sign() + */ + this.sign = function() { + throw "sign() not supported for this alg:prov=" + this.algProvName; + }; + + /** + * performs final update on the sign using string, then returns the signature bytes of all data updates as a hexadecimal string + * @name signString + * @memberOf KJUR.crypto.Signature# + * @function + * @param {String} str string to final update + * @return the signature bytes of a hexadecimal string + * @description + * @example + * var hSigValue = sig.signString('aaa') + */ + this.signString = function(str) { + throw "digestString(str) not supported for this alg:prov=" + this.algProvName; + }; + + /** + * performs final update on the sign using hexadecimal string, then returns the signature bytes of all data updates as a hexadecimal string + * @name signHex + * @memberOf KJUR.crypto.Signature# + * @function + * @param {String} hex hexadecimal string to final update + * @return the signature bytes of a hexadecimal string + * @description + * @example + * var hSigValue = sig.signHex('1fdc33') + */ + this.signHex = function(hex) { + throw "digestHex(hex) not supported for this alg:prov=" + this.algProvName; + }; + + /** + * verifies the passed-in signature. + * @name verify + * @memberOf KJUR.crypto.Signature# + * @function + * @param {String} str string to final update + * @return {Boolean} true if the signature was verified, otherwise false + * @description + * @example + * var isValid = sig.verify('1fbcefdca4823a7(snip)') + */ + this.verify = function(hSigVal) { + throw "verify(hSigVal) not supported for this alg:prov=" + this.algProvName; + }; + + this.initParams = params; + + if (params !== undefined) { + if (params.alg !== undefined) { + this.algName = params.alg; + if (params.prov === undefined) { + this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; + } else { + this.provName = params.prov; + } + this.algProvName = this.algName + ":" + this.provName; + this.setAlgAndProvider(this.algName, this.provName); + this._setAlgNames(); + } + + if (params['psssaltlen'] !== undefined) this.pssSaltLen = params['psssaltlen']; + + if (params.prvkeypem !== undefined) { + if (params.prvkeypas !== undefined) { + throw "both prvkeypem and prvkeypas parameters not supported"; + } else { + try { + var prvKey = KEYUTIL.getKey(params.prvkeypem); + this.init(prvKey); + } catch (ex) { + throw "fatal error to load pem private key: " + ex; + } + } + } + } +}; + +// ====== Cipher class ============================================================ +/** + * Cipher class to encrypt and decrypt data
+ * @name KJUR.crypto.Cipher + * @class Cipher class to encrypt and decrypt data
+ * @param {Array} params parameters for constructor + * @since jsrsasign 6.2.0 crypto 1.1.10 + * @description + * Here is supported canonicalized cipher algorithm names and its standard names: + * + * NOTE: (*) is not supported in Java JCE.
+ * Currently this class supports only RSA encryption and decryption + * based on RSAES-OAEP and RSAES-PKCS1-v1_5 scheme. + * However it is planning to implement also symmetric ciphers near in the future */ +KJUR.crypto.Cipher = function(params) { +}; + +/** + * encrypt raw string by specified key and algorithm
+ * @name encrypt + * @memberOf KJUR.crypto.Cipher + * @function + * @param {String} s input string to encrypt + * @param {Object} keyObj RSAKey object or hexadecimal string of symmetric cipher key + * @param {String} algName short/long algorithm name for encryption/decryption + * @return {String} hexadecimal encrypted string + * @since jsrsasign 6.2.0 crypto 1.1.10 + * @description + * This static method encrypts raw string with specified key and algorithm. + * @example + * KJUR.crypto.Cipher.encrypt("aaa", pubRSAKeyObj) → "1abc2d..." + * KJUR.crypto.Cipher.encrypt("aaa", pubRSAKeyObj, "RSAOAEP") → "23ab02..." + */ +KJUR.crypto.Cipher.encrypt = function(s, keyObj, algName) { + if (keyObj instanceof RSAKey && keyObj.isPublic) { + var algName2 = KJUR.crypto.Cipher.getAlgByKeyAndName(keyObj, algName); + if (algName2 === "RSA") return keyObj.encrypt(s); + if (algName2 === "RSAOAEP") return keyObj.encryptOAEP(s, "sha1"); + + var a = algName2.match(/^RSAOAEP(\d+)$/); + if (a !== null) return keyObj.encryptOAEP(s, "sha" + a[1]); + + throw "Cipher.encrypt: unsupported algorithm for RSAKey: " + algName; + } else { + throw "Cipher.encrypt: unsupported key or algorithm"; + } +}; + +/** + * decrypt encrypted hexadecimal string with specified key and algorithm
+ * @name decrypt + * @memberOf KJUR.crypto.Cipher + * @function + * @param {String} hex hexadecial string of encrypted message + * @param {Object} keyObj RSAKey object or hexadecimal string of symmetric cipher key + * @param {String} algName short/long algorithm name for encryption/decryption + * @return {String} decrypted raw string + * @since jsrsasign 6.2.0 crypto 1.1.10 + * @description + * This static method decrypts encrypted hexadecimal string with specified key and algorithm. + * @example + * KJUR.crypto.Cipher.decrypt("aaa", prvRSAKeyObj) → "1abc2d..." + * KJUR.crypto.Cipher.decrypt("aaa", prvRSAKeyObj, "RSAOAEP) → "23ab02..." + */ +KJUR.crypto.Cipher.decrypt = function(hex, keyObj, algName) { + if (keyObj instanceof RSAKey && keyObj.isPrivate) { + var algName2 = KJUR.crypto.Cipher.getAlgByKeyAndName(keyObj, algName); + if (algName2 === "RSA") return keyObj.decrypt(hex); + if (algName2 === "RSAOAEP") return keyObj.decryptOAEP(hex, "sha1"); + + var a = algName2.match(/^RSAOAEP(\d+)$/); + if (a !== null) return keyObj.decryptOAEP(hex, "sha" + a[1]); + + throw "Cipher.decrypt: unsupported algorithm for RSAKey: " + algName; + } else { + throw "Cipher.decrypt: unsupported key or algorithm"; + } +}; + +/** + * get canonicalized encrypt/decrypt algorithm name by key and short/long algorithm name
+ * @name getAlgByKeyAndName + * @memberOf KJUR.crypto.Cipher + * @function + * @param {Object} keyObj RSAKey object or hexadecimal string of symmetric cipher key + * @param {String} algName short/long algorithm name for encryption/decryption + * @return {String} canonicalized algorithm name for encryption/decryption + * @since jsrsasign 6.2.0 crypto 1.1.10 + * @description + * Here is supported canonicalized cipher algorithm names and its standard names: + * + * NOTE: (*) is not supported in Java JCE. + * @example + * KJUR.crypto.Cipher.getAlgByKeyAndName(objRSAKey) → "RSA" + * KJUR.crypto.Cipher.getAlgByKeyAndName(objRSAKey, "RSAOAEP") → "RSAOAEP" + */ +KJUR.crypto.Cipher.getAlgByKeyAndName = function(keyObj, algName) { + if (keyObj instanceof RSAKey) { + if (":RSA:RSAOAEP:RSAOAEP224:RSAOAEP256:RSAOAEP384:RSAOAEP512:".indexOf(algName) != -1) + return algName; + if (algName === null || algName === undefined) return "RSA"; + throw "getAlgByKeyAndName: not supported algorithm name for RSAKey: " + algName; + } + throw "getAlgByKeyAndName: not supported algorithm name: " + algName; +} + +// ====== Other Utility class ===================================================== + +/** + * static object for cryptographic function utilities + * @name KJUR.crypto.OID + * @class static object for cryptography related OIDs + * @property {Array} oidhex2name key value of hexadecimal OID and its name + * (ex. '2a8648ce3d030107' and 'secp256r1') + * @since crypto 1.1.3 + * @description + */ +KJUR.crypto.OID = new function() { + this.oidhex2name = { + '2a864886f70d010101': 'rsaEncryption', + '2a8648ce3d0201': 'ecPublicKey', + '2a8648ce380401': 'dsa', + '2a8648ce3d030107': 'secp256r1', + '2b8104001f': 'secp192k1', + '2b81040021': 'secp224r1', + '2b8104000a': 'secp256k1', + '2b81040023': 'secp521r1', + '2b81040022': 'secp384r1', + '2a8648ce380403': 'SHA1withDSA', // 1.2.840.10040.4.3 + '608648016503040301': 'SHA224withDSA', // 2.16.840.1.101.3.4.3.1 + '608648016503040302': 'SHA256withDSA', // 2.16.840.1.101.3.4.3.2 + }; +}; + +module.exports.KJUR = KJUR; diff --git a/jslib/jsbn.js b/jslib/jsbn.js new file mode 100644 index 0000000..f325c6a --- /dev/null +++ b/jslib/jsbn.js @@ -0,0 +1,561 @@ +// Copyright (c) 2005 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Basic JavaScript BN library - subset useful for RSA encryption. + +// Bits per digit +var dbits; + +// JavaScript engine analysis +var canary = 0xdeadbeefcafe; +var j_lm = ((canary&0xffffff)==0xefcafe); + +// (public) Constructor +function BigInteger(a,b,c) { + if(a != null) + if("number" == typeof a) this.fromNumber(a,b,c); + else if(b == null && "string" != typeof a) this.fromString(a,256); + else this.fromString(a,b); +} + +// return new, unset BigInteger +function nbi() { return new BigInteger(null); } + +// am: Compute w_j += (x*this_i), propagate carries, +// c is initial carry, returns final carry. +// c < 3*dvalue, x < 2*dvalue, this_i < dvalue +// We need to select the fastest one that works in this environment. + +// am1: use a single mult and divide to get the high bits, +// max digit bits should be 26 because +// max internal value = 2*dvalue^2-2*dvalue (< 2^53) +function am1(i,x,w,j,c,n) { + while(--n >= 0) { + var v = x*this[i++]+w[j]+c; + c = Math.floor(v/0x4000000); + w[j++] = v&0x3ffffff; + } + return c; +} +// am2 avoids a big mult-and-extract completely. +// Max digit bits should be <= 30 because we do bitwise ops +// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) +function am2(i,x,w,j,c,n) { + var xl = x&0x7fff, xh = x>>15; + while(--n >= 0) { + var l = this[i]&0x7fff; + var h = this[i++]>>15; + var m = xh*l+h*xl; + l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); + c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); + w[j++] = l&0x3fffffff; + } + return c; +} +// Alternately, set max digit bits to 28 since some +// browsers slow down when dealing with 32-bit numbers. +function am3(i,x,w,j,c,n) { + var xl = x&0x3fff, xh = x>>14; + while(--n >= 0) { + var l = this[i]&0x3fff; + var h = this[i++]>>14; + var m = xh*l+h*xl; + l = xl*l+((m&0x3fff)<<14)+w[j]+c; + c = (l>>28)+(m>>14)+xh*h; + w[j++] = l&0xfffffff; + } + return c; +} +// if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { +// BigInteger.prototype.am = am2; +// dbits = 30; +// } +// else if(j_lm && (navigator.appName != "Netscape")) { +// BigInteger.prototype.am = am1; +// dbits = 26; +// } +// else { // Mozilla/Netscape seems to prefer am3 + BigInteger.prototype.am = am3; + dbits = 28; + + +BigInteger.prototype.DB = dbits; +BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; + r.t = this.t; + r.s = this.s; +} + +// (protected) set from integer value x, -DV <= x < DV +function bnpFromInt(x) { + this.t = 1; + this.s = (x<0)?-1:0; + if(x > 0) this[0] = x; + else if(x < -1) this[0] = x+this.DV; + else this.t = 0; +} + +// return bigint initialized to value +function nbv(i) { var r = nbi(); r.fromInt(i); return r; } + +// (protected) set from string and radix +function bnpFromString(s,b) { + var k; + if(b == 16) k = 4; + else if(b == 8) k = 3; + else if(b == 256) k = 8; // byte array + else if(b == 2) k = 1; + else if(b == 32) k = 5; + else if(b == 4) k = 2; + else { this.fromRadix(s,b); return; } + this.t = 0; + this.s = 0; + var i = s.length, mi = false, sh = 0; + while(--i >= 0) { + var x = (k==8)?s[i]&0xff:intAt(s,i); + if(x < 0) { + if(s.charAt(i) == "-") mi = true; + continue; + } + mi = false; + if(sh == 0) + this[this.t++] = x; + else if(sh+k > this.DB) { + this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); + } + else + this[this.t-1] |= x<= this.DB) sh -= this.DB; + } + if(k == 8 && (s[0]&0x80) != 0) { + this.s = -1; + if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; +} + +// (public) return string representation in given radix +function bnToString(b) { + if(this.s < 0) return "-"+this.negate().toString(b); + var k; + if(b == 16) k = 4; + else if(b == 8) k = 3; + else if(b == 2) k = 1; + else if(b == 32) k = 5; + else if(b == 4) k = 2; + else return this.toRadix(b); + var km = (1< 0) { + if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } + while(i >= 0) { + if(p < k) { + d = (this[i]&((1<>(p+=this.DB-k); + } + else { + d = (this[i]>>(p-=k))&km; + if(p <= 0) { p += this.DB; --i; } + } + if(d > 0) m = true; + if(m) r += int2char(d); + } + } + return m?r:"0"; +} + +// (public) -this +function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } + +// (public) |this| +function bnAbs() { return (this.s<0)?this.negate():this; } + +// (public) return + if this > a, - if this < a, 0 if equal +function bnCompareTo(a) { + var r = this.s-a.s; + if(r != 0) return r; + var i = this.t; + r = i-a.t; + if(r != 0) return (this.s<0)?-r:r; + while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; + return 0; +} + +// returns bit length of the integer x +function nbits(x) { + var r = 1, t; + if((t=x>>>16) != 0) { x = t; r += 16; } + if((t=x>>8) != 0) { x = t; r += 8; } + if((t=x>>4) != 0) { x = t; r += 4; } + if((t=x>>2) != 0) { x = t; r += 2; } + if((t=x>>1) != 0) { x = t; r += 1; } + return r; +} + +// (public) return the number of bits in "this" +function bnBitLength() { + if(this.t <= 0) return 0; + return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); +} + +// (protected) r = this << n*DB +function bnpDLShiftTo(n,r) { + var i; + for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; + for(i = n-1; i >= 0; --i) r[i] = 0; + r.t = this.t+n; + r.s = this.s; +} + +// (protected) r = this >> n*DB +function bnpDRShiftTo(n,r) { + for(var i = n; i < this.t; ++i) r[i-n] = this[i]; + r.t = Math.max(this.t-n,0); + r.s = this.s; +} + +// (protected) r = this << n +function bnpLShiftTo(n,r) { + var bs = n%this.DB; + var cbs = this.DB-bs; + var bm = (1<= 0; --i) { + r[i+ds+1] = (this[i]>>cbs)|c; + c = (this[i]&bm)<= 0; --i) r[i] = 0; + r[ds] = c; + r.t = this.t+ds+1; + r.s = this.s; + r.clamp(); +} + +// (protected) r = this >> n +function bnpRShiftTo(n,r) { + r.s = this.s; + var ds = Math.floor(n/this.DB); + if(ds >= this.t) { r.t = 0; return; } + var bs = n%this.DB; + var cbs = this.DB-bs; + var bm = (1<>bs; + for(var i = ds+1; i < this.t; ++i) { + r[i-ds-1] |= (this[i]&bm)<>bs; + } + if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; + } + if(a.t < this.t) { + c -= a.s; + while(i < this.t) { + c += this[i]; + r[i++] = c&this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while(i < a.t) { + c -= a[i]; + r[i++] = c&this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = (c<0)?-1:0; + if(c < -1) r[i++] = this.DV+c; + else if(c > 0) r[i++] = c; + r.t = i; + r.clamp(); +} + +// (protected) r = this * a, r != this,a (HAC 14.12) +// "this" should be the larger one if appropriate. +function bnpMultiplyTo(a,r) { + var x = this.abs(), y = a.abs(); + var i = x.t; + r.t = i+y.t; + while(--i >= 0) r[i] = 0; + for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); + r.s = 0; + r.clamp(); + if(this.s != a.s) BigInteger.ZERO.subTo(r,r); +} + +// (protected) r = this^2, r != this (HAC 14.16) +function bnpSquareTo(r) { + var x = this.abs(); + var i = r.t = 2*x.t; + while(--i >= 0) r[i] = 0; + for(i = 0; i < x.t-1; ++i) { + var c = x.am(i,x[i],r,2*i,0,1); + if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { + r[i+x.t] -= x.DV; + r[i+x.t+1] = 1; + } + } + if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); + r.s = 0; + r.clamp(); +} + +// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) +// r != q, this != m. q or r may be null. +function bnpDivRemTo(m,q,r) { + var pm = m.abs(); + if(pm.t <= 0) return; + var pt = this.abs(); + if(pt.t < pm.t) { + if(q != null) q.fromInt(0); + if(r != null) this.copyTo(r); + return; + } + if(r == null) r = nbi(); + var y = nbi(), ts = this.s, ms = m.s; + var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus + if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } + else { pm.copyTo(y); pt.copyTo(r); } + var ys = y.t; + var y0 = y[ys-1]; + if(y0 == 0) return; + var yt = y0*(1<1)?y[ys-2]>>this.F2:0); + var d1 = this.FV/yt, d2 = (1<= 0) { + r[r.t++] = 1; + r.subTo(t,r); + } + BigInteger.ONE.dlShiftTo(ys,t); + t.subTo(y,y); // "negative" y so we can replace sub with am later + while(y.t < ys) y[y.t++] = 0; + while(--j >= 0) { + // Estimate quotient digit + var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); + if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out + y.dlShiftTo(j,t); + r.subTo(t,r); + while(r[i] < --qd) r.subTo(t,r); + } + } + if(q != null) { + r.drShiftTo(ys,q); + if(ts != ms) BigInteger.ZERO.subTo(q,q); + } + r.t = ys; + r.clamp(); + if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder + if(ts < 0) BigInteger.ZERO.subTo(r,r); +} + +// (public) this mod a +function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a,null,r); + if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); + return r; +} + +// Modular reduction using "classic" algorithm +function Classic(m) { this.m = m; } +function cConvert(x) { + if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; +} +function cRevert(x) { return x; } +function cReduce(x) { x.divRemTo(this.m,null,x); } +function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } +function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +Classic.prototype.convert = cConvert; +Classic.prototype.revert = cRevert; +Classic.prototype.reduce = cReduce; +Classic.prototype.mulTo = cMulTo; +Classic.prototype.sqrTo = cSqrTo; + +// (protected) return "-1/this % 2^DB"; useful for Mont. reduction +// justification: +// xy == 1 (mod m) +// xy = 1+km +// xy(2-xy) = (1+km)(1-km) +// x[y(2-xy)] = 1-k^2m^2 +// x[y(2-xy)] == 1 (mod m^2) +// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 +// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. +// JS multiply "overflows" differently from C/C++, so care is needed here. +function bnpInvDigit() { + if(this.t < 1) return 0; + var x = this[0]; + if((x&1) == 0) return 0; + var y = x&3; // y == 1/x mod 2^2 + y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 + y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 + y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return (y>0)?this.DV-y:-y; +} + +// Montgomery reduction +function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp&0x7fff; + this.mph = this.mp>>15; + this.um = (1<<(m.DB-15))-1; + this.mt2 = 2*m.t; +} + +// xR mod m +function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t,r); + r.divRemTo(this.m,null,r); + if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); + return r; +} + +// x/R mod m +function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; +} + +// x = x/R mod m (HAC 14.32) +function montReduce(x) { + while(x.t <= this.mt2) // pad x so am has enough room later + x[x.t++] = 0; + for(var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x[i]*mp mod DV + var j = x[i]&0x7fff; + var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; + // use am to combine the multiply-shift-add into one call + j = i+this.m.t; + x[j] += this.m.am(0,u0,x,i,0,this.m.t); + // propagate carry + while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } + } + x.clamp(); + x.drShiftTo(this.m.t,x); + if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); +} + +// r = "x^2/R mod m"; x != r +function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +// r = "xy/R mod m"; x,y != r +function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } + +Montgomery.prototype.convert = montConvert; +Montgomery.prototype.revert = montRevert; +Montgomery.prototype.reduce = montReduce; +Montgomery.prototype.mulTo = montMulTo; +Montgomery.prototype.sqrTo = montSqrTo; + +// (protected) true iff this is even +function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } + +// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) +function bnpExp(e,z) { + if(e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; + g.copyTo(r); + while(--i >= 0) { + z.sqrTo(r,r2); + if((e&(1< 0) z.mulTo(r2,g,r); + else { var t = r; r = r2; r2 = t; } + } + return z.revert(r); +} + +// (public) this^e % m, 0 <= e < 2^32 +function bnModPowInt(e,m) { + var z; + if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); + return this.exp(e,z); +} + +// protected +BigInteger.prototype.copyTo = bnpCopyTo; +BigInteger.prototype.fromInt = bnpFromInt; +BigInteger.prototype.fromString = bnpFromString; +BigInteger.prototype.clamp = bnpClamp; +BigInteger.prototype.dlShiftTo = bnpDLShiftTo; +BigInteger.prototype.drShiftTo = bnpDRShiftTo; +BigInteger.prototype.lShiftTo = bnpLShiftTo; +BigInteger.prototype.rShiftTo = bnpRShiftTo; +BigInteger.prototype.subTo = bnpSubTo; +BigInteger.prototype.multiplyTo = bnpMultiplyTo; +BigInteger.prototype.squareTo = bnpSquareTo; +BigInteger.prototype.divRemTo = bnpDivRemTo; +BigInteger.prototype.invDigit = bnpInvDigit; +BigInteger.prototype.isEven = bnpIsEven; +BigInteger.prototype.exp = bnpExp; + +// public +BigInteger.prototype.toString = bnToString; +BigInteger.prototype.negate = bnNegate; +BigInteger.prototype.abs = bnAbs; +BigInteger.prototype.compareTo = bnCompareTo; +BigInteger.prototype.bitLength = bnBitLength; +BigInteger.prototype.mod = bnMod; +BigInteger.prototype.modPowInt = bnModPowInt; + +// "constants" +BigInteger.ZERO = nbv(0); +BigInteger.ONE = nbv(1); + +module.exports.BigInteger = BigInteger; diff --git a/jslib/jsbn2.js b/jslib/jsbn2.js new file mode 100644 index 0000000..5b2b725 --- /dev/null +++ b/jslib/jsbn2.js @@ -0,0 +1,656 @@ +// Copyright (c) 2005-2009 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Extended JavaScript BN functions, required for RSA private ops. + +// Version 1.1: new BigInteger("0", 10) returns "proper" zero +// Version 1.2: square() API, isProbablePrime fix + +// (public) +function bnClone() { var r = nbi(); this.copyTo(r); return r; } + +// (public) return value as integer +function bnIntValue() { + if(this.s < 0) { + if(this.t == 1) return this[0]-this.DV; + else if(this.t == 0) return -1; + } + else if(this.t == 1) return this[0]; + else if(this.t == 0) return 0; + // assumes 16 < DB < 32 + return ((this[1]&((1<<(32-this.DB))-1))<>24; } + +// (public) return value as short (assumes DB>=16) +function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } + +// (protected) return x s.t. r^x < DV +function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } + +// (public) 0 if this == 0, 1 if this > 0 +function bnSigNum() { + if(this.s < 0) return -1; + else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; + else return 1; +} + +// (protected) convert to radix string +function bnpToRadix(b) { + if(b == null) b = 10; + if(this.signum() == 0 || b < 2 || b > 36) return "0"; + var cs = this.chunkSize(b); + var a = Math.pow(b,cs); + var d = nbv(a), y = nbi(), z = nbi(), r = ""; + this.divRemTo(d,y,z); + while(y.signum() > 0) { + r = (a+z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d,y,z); + } + return z.intValue().toString(b) + r; +} + +// (protected) convert from radix string +function bnpFromRadix(s,b) { + this.fromInt(0); + if(b == null) b = 10; + var cs = this.chunkSize(b); + var d = Math.pow(b,cs), mi = false, j = 0, w = 0; + for(var i = 0; i < s.length; ++i) { + var x = intAt(s,i); + if(x < 0) { + if(s.charAt(i) == "-" && this.signum() == 0) mi = true; + continue; + } + w = b*w+x; + if(++j >= cs) { + this.dMultiply(d); + this.dAddOffset(w,0); + j = 0; + w = 0; + } + } + if(j > 0) { + this.dMultiply(Math.pow(b,j)); + this.dAddOffset(w,0); + } + if(mi) BigInteger.ZERO.subTo(this,this); +} + +// (protected) alternate constructor +function bnpFromNumber(a,b,c) { + if("number" == typeof b) { + // new BigInteger(int,int,RNG) + if(a < 2) this.fromInt(1); + else { + this.fromNumber(a,c); + if(!this.testBit(a-1)) // force MSB set + this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); + if(this.isEven()) this.dAddOffset(1,0); // force odd + while(!this.isProbablePrime(b)) { + this.dAddOffset(2,0); + if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); + } + } + } + else { + // new BigInteger(int,RNG) + var x = new Array(), t = a&7; + x.length = (a>>3)+1; + b.nextBytes(x); + if(t > 0) x[0] &= ((1< 0) { + if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) + r[k++] = d|(this.s<<(this.DB-p)); + while(i >= 0) { + if(p < 8) { + d = (this[i]&((1<>(p+=this.DB-8); + } + else { + d = (this[i]>>(p-=8))&0xff; + if(p <= 0) { p += this.DB; --i; } + } + if((d&0x80) != 0) d |= -256; + if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; + if(k > 0 || d != this.s) r[k++] = d; + } + } + return r; +} + +function bnEquals(a) { return(this.compareTo(a)==0); } +function bnMin(a) { return(this.compareTo(a)<0)?this:a; } +function bnMax(a) { return(this.compareTo(a)>0)?this:a; } + +// (protected) r = this op a (bitwise) +function bnpBitwiseTo(a,op,r) { + var i, f, m = Math.min(a.t,this.t); + for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); + if(a.t < this.t) { + f = a.s&this.DM; + for(i = m; i < this.t; ++i) r[i] = op(this[i],f); + r.t = this.t; + } + else { + f = this.s&this.DM; + for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); + r.t = a.t; + } + r.s = op(this.s,a.s); + r.clamp(); +} + +// (public) this & a +function op_and(x,y) { return x&y; } +function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } + +// (public) this | a +function op_or(x,y) { return x|y; } +function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } + +// (public) this ^ a +function op_xor(x,y) { return x^y; } +function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } + +// (public) this & ~a +function op_andnot(x,y) { return x&~y; } +function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } + +// (public) ~this +function bnNot() { + var r = nbi(); + for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; + r.t = this.t; + r.s = ~this.s; + return r; +} + +// (public) this << n +function bnShiftLeft(n) { + var r = nbi(); + if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); + return r; +} + +// (public) this >> n +function bnShiftRight(n) { + var r = nbi(); + if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); + return r; +} + +// return index of lowest 1-bit in x, x < 2^31 +function lbit(x) { + if(x == 0) return -1; + var r = 0; + if((x&0xffff) == 0) { x >>= 16; r += 16; } + if((x&0xff) == 0) { x >>= 8; r += 8; } + if((x&0xf) == 0) { x >>= 4; r += 4; } + if((x&3) == 0) { x >>= 2; r += 2; } + if((x&1) == 0) ++r; + return r; +} + +// (public) returns index of lowest 1-bit (or -1 if none) +function bnGetLowestSetBit() { + for(var i = 0; i < this.t; ++i) + if(this[i] != 0) return i*this.DB+lbit(this[i]); + if(this.s < 0) return this.t*this.DB; + return -1; +} + +// return number of 1 bits in x +function cbit(x) { + var r = 0; + while(x != 0) { x &= x-1; ++r; } + return r; +} + +// (public) return number of set bits +function bnBitCount() { + var r = 0, x = this.s&this.DM; + for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); + return r; +} + +// (public) true iff nth bit is set +function bnTestBit(n) { + var j = Math.floor(n/this.DB); + if(j >= this.t) return(this.s!=0); + return((this[j]&(1<<(n%this.DB)))!=0); +} + +// (protected) this op (1<>= this.DB; + } + if(a.t < this.t) { + c += a.s; + while(i < this.t) { + c += this[i]; + r[i++] = c&this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while(i < a.t) { + c += a[i]; + r[i++] = c&this.DM; + c >>= this.DB; + } + c += a.s; + } + r.s = (c<0)?-1:0; + if(c > 0) r[i++] = c; + else if(c < -1) r[i++] = this.DV+c; + r.t = i; + r.clamp(); +} + +// (public) this + a +function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } + +// (public) this - a +function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } + +// (public) this * a +function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } + +// (public) this^2 +function bnSquare() { var r = nbi(); this.squareTo(r); return r; } + +// (public) this / a +function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } + +// (public) this % a +function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } + +// (public) [this/a,this%a] +function bnDivideAndRemainder(a) { + var q = nbi(), r = nbi(); + this.divRemTo(a,q,r); + return new Array(q,r); +} + +// (protected) this *= n, this >= 0, 1 < n < DV +function bnpDMultiply(n) { + this[this.t] = this.am(0,n-1,this,0,0,this.t); + ++this.t; + this.clamp(); +} + +// (protected) this += n << w words, this >= 0 +function bnpDAddOffset(n,w) { + if(n == 0) return; + while(this.t <= w) this[this.t++] = 0; + this[w] += n; + while(this[w] >= this.DV) { + this[w] -= this.DV; + if(++w >= this.t) this[this.t++] = 0; + ++this[w]; + } +} + +// A "null" reducer +function NullExp() {} +function nNop(x) { return x; } +function nMulTo(x,y,r) { x.multiplyTo(y,r); } +function nSqrTo(x,r) { x.squareTo(r); } + +NullExp.prototype.convert = nNop; +NullExp.prototype.revert = nNop; +NullExp.prototype.mulTo = nMulTo; +NullExp.prototype.sqrTo = nSqrTo; + +// (public) this^e +function bnPow(e) { return this.exp(e,new NullExp()); } + +// (protected) r = lower n words of "this * a", a.t <= n +// "this" should be the larger one if appropriate. +function bnpMultiplyLowerTo(a,n,r) { + var i = Math.min(this.t+a.t,n); + r.s = 0; // assumes a,this >= 0 + r.t = i; + while(i > 0) r[--i] = 0; + var j; + for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); + for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); + r.clamp(); +} + +// (protected) r = "this * a" without lower n words, n > 0 +// "this" should be the larger one if appropriate. +function bnpMultiplyUpperTo(a,n,r) { + --n; + var i = r.t = this.t+a.t-n; + r.s = 0; // assumes a,this >= 0 + while(--i >= 0) r[i] = 0; + for(i = Math.max(n-this.t,0); i < a.t; ++i) + r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); + r.clamp(); + r.drShiftTo(1,r); +} + +// Barrett modular reduction +function Barrett(m) { + // setup Barrett + this.r2 = nbi(); + this.q3 = nbi(); + BigInteger.ONE.dlShiftTo(2*m.t,this.r2); + this.mu = this.r2.divide(m); + this.m = m; +} + +function barrettConvert(x) { + if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); + else if(x.compareTo(this.m) < 0) return x; + else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } +} + +function barrettRevert(x) { return x; } + +// x = x mod m (HAC 14.42) +function barrettReduce(x) { + x.drShiftTo(this.m.t-1,this.r2); + if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } + this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); + this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); + while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); + x.subTo(this.r2,x); + while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); +} + +// r = x^2 mod m; x != r +function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +// r = x*y mod m; x,y != r +function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } + +Barrett.prototype.convert = barrettConvert; +Barrett.prototype.revert = barrettRevert; +Barrett.prototype.reduce = barrettReduce; +Barrett.prototype.mulTo = barrettMulTo; +Barrett.prototype.sqrTo = barrettSqrTo; + +// (public) this^e % m (HAC 14.85) +function bnModPow(e,m) { + var i = e.bitLength(), k, r = nbv(1), z; + if(i <= 0) return r; + else if(i < 18) k = 1; + else if(i < 48) k = 3; + else if(i < 144) k = 4; + else if(i < 768) k = 5; + else k = 6; + if(i < 8) + z = new Classic(m); + else if(m.isEven()) + z = new Barrett(m); + else + z = new Montgomery(m); + + // precomputation + var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { + var g2 = nbi(); + z.sqrTo(g[1],g2); + while(n <= km) { + g[n] = nbi(); + z.mulTo(g2,g[n-2],g[n]); + n += 2; + } + } + + var j = e.t-1, w, is1 = true, r2 = nbi(), t; + i = nbits(e[j])-1; + while(j >= 0) { + if(i >= k1) w = (e[j]>>(i-k1))&km; + else { + w = (e[j]&((1<<(i+1))-1))<<(k1-i); + if(j > 0) w |= e[j-1]>>(this.DB+i-k1); + } + + n = k; + while((w&1) == 0) { w >>= 1; --n; } + if((i -= n) < 0) { i += this.DB; --j; } + if(is1) { // ret == 1, don't bother squaring or multiplying it + g[w].copyTo(r); + is1 = false; + } + else { + while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } + if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } + z.mulTo(r2,g[w],r); + } + + while(j >= 0 && (e[j]&(1< 0) { + x.rShiftTo(g,x); + y.rShiftTo(g,y); + } + while(x.signum() > 0) { + if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); + if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); + if(x.compareTo(y) >= 0) { + x.subTo(y,x); + x.rShiftTo(1,x); + } + else { + y.subTo(x,y); + y.rShiftTo(1,y); + } + } + if(g > 0) y.lShiftTo(g,y); + return y; +} + +// (protected) this % n, n < 2^26 +function bnpModInt(n) { + if(n <= 0) return 0; + var d = this.DV%n, r = (this.s<0)?n-1:0; + if(this.t > 0) + if(d == 0) r = this[0]%n; + else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; + return r; +} + +// (public) 1/this % m (HAC 14.61) +function bnModInverse(m) { + var ac = m.isEven(); + if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; + var u = m.clone(), v = this.clone(); + var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); + while(u.signum() != 0) { + while(u.isEven()) { + u.rShiftTo(1,u); + if(ac) { + if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } + a.rShiftTo(1,a); + } + else if(!b.isEven()) b.subTo(m,b); + b.rShiftTo(1,b); + } + while(v.isEven()) { + v.rShiftTo(1,v); + if(ac) { + if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } + c.rShiftTo(1,c); + } + else if(!d.isEven()) d.subTo(m,d); + d.rShiftTo(1,d); + } + if(u.compareTo(v) >= 0) { + u.subTo(v,u); + if(ac) a.subTo(c,a); + b.subTo(d,b); + } + else { + v.subTo(u,v); + if(ac) c.subTo(a,c); + d.subTo(b,d); + } + } + if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; + if(d.compareTo(m) >= 0) return d.subtract(m); + if(d.signum() < 0) d.addTo(m,d); else return d; + if(d.signum() < 0) return d.add(m); else return d; +} + +var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; +var lplim = (1<<26)/lowprimes[lowprimes.length-1]; + +// (public) test primality with certainty >= 1-.5^t +function bnIsProbablePrime(t) { + var i, x = this.abs(); + if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { + for(i = 0; i < lowprimes.length; ++i) + if(x[0] == lowprimes[i]) return true; + return false; + } + if(x.isEven()) return false; + i = 1; + while(i < lowprimes.length) { + var m = lowprimes[i], j = i+1; + while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; + m = x.modInt(m); + while(i < j) if(m%lowprimes[i++] == 0) return false; + } + return x.millerRabin(t); +} + +// (protected) true if probably prime (HAC 4.24, Miller-Rabin) +function bnpMillerRabin(t) { + var n1 = this.subtract(BigInteger.ONE); + var k = n1.getLowestSetBit(); + if(k <= 0) return false; + var r = n1.shiftRight(k); + t = (t+1)>>1; + if(t > lowprimes.length) t = lowprimes.length; + var a = nbi(); + for(var i = 0; i < t; ++i) { + //Pick bases at random, instead of starting at 2 + a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); + var y = a.modPow(r,this); + if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { + var j = 1; + while(j++ < k && y.compareTo(n1) != 0) { + y = y.modPowInt(2,this); + if(y.compareTo(BigInteger.ONE) == 0) return false; + } + if(y.compareTo(n1) != 0) return false; + } + } + return true; +} + +// protected +BigInteger.prototype.chunkSize = bnpChunkSize; +BigInteger.prototype.toRadix = bnpToRadix; +BigInteger.prototype.fromRadix = bnpFromRadix; +BigInteger.prototype.fromNumber = bnpFromNumber; +BigInteger.prototype.bitwiseTo = bnpBitwiseTo; +BigInteger.prototype.changeBit = bnpChangeBit; +BigInteger.prototype.addTo = bnpAddTo; +BigInteger.prototype.dMultiply = bnpDMultiply; +BigInteger.prototype.dAddOffset = bnpDAddOffset; +BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; +BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; +BigInteger.prototype.modInt = bnpModInt; +BigInteger.prototype.millerRabin = bnpMillerRabin; + +// public +BigInteger.prototype.clone = bnClone; +BigInteger.prototype.intValue = bnIntValue; +BigInteger.prototype.byteValue = bnByteValue; +BigInteger.prototype.shortValue = bnShortValue; +BigInteger.prototype.signum = bnSigNum; +BigInteger.prototype.toByteArray = bnToByteArray; +BigInteger.prototype.equals = bnEquals; +BigInteger.prototype.min = bnMin; +BigInteger.prototype.max = bnMax; +BigInteger.prototype.and = bnAnd; +BigInteger.prototype.or = bnOr; +BigInteger.prototype.xor = bnXor; +BigInteger.prototype.andNot = bnAndNot; +BigInteger.prototype.not = bnNot; +BigInteger.prototype.shiftLeft = bnShiftLeft; +BigInteger.prototype.shiftRight = bnShiftRight; +BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; +BigInteger.prototype.bitCount = bnBitCount; +BigInteger.prototype.testBit = bnTestBit; +BigInteger.prototype.setBit = bnSetBit; +BigInteger.prototype.clearBit = bnClearBit; +BigInteger.prototype.flipBit = bnFlipBit; +BigInteger.prototype.add = bnAdd; +BigInteger.prototype.subtract = bnSubtract; +BigInteger.prototype.multiply = bnMultiply; +BigInteger.prototype.divide = bnDivide; +BigInteger.prototype.remainder = bnRemainder; +BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; +BigInteger.prototype.modPow = bnModPow; +BigInteger.prototype.modInverse = bnModInverse; +BigInteger.prototype.pow = bnPow; +BigInteger.prototype.gcd = bnGCD; +BigInteger.prototype.isProbablePrime = bnIsProbablePrime; + +// JSBN-specific extension +BigInteger.prototype.square = bnSquare; + +// BigInteger interfaces not implemented in jsbn: + +// BigInteger(int signum, byte[] magnitude) +// double doubleValue() +// float floatValue() +// int hashCode() +// long longValue() +// static BigInteger valueOf(long val) diff --git a/jslib/prng4.js b/jslib/prng4.js new file mode 100644 index 0000000..43c4b49 --- /dev/null +++ b/jslib/prng4.js @@ -0,0 +1,49 @@ +// prng4.js - uses Arcfour as a PRNG + +function Arcfour() { + this.i = 0; + this.j = 0; + this.S = new Array(); +} + +// Initialize arcfour context from key, an array of ints, each from [0..255] +function ARC4init(key) { + var i, j, t; + for(i = 0; i < 256; ++i) + this.S[i] = i; + j = 0; + for(i = 0; i < 256; ++i) { + j = (j + this.S[i] + key[i % key.length]) & 255; + t = this.S[i]; + this.S[i] = this.S[j]; + this.S[j] = t; + } + this.i = 0; + this.j = 0; +} + +function ARC4next() { + var t; + this.i = (this.i + 1) & 255; + this.j = (this.j + this.S[this.i]) & 255; + t = this.S[this.i]; + this.S[this.i] = this.S[this.j]; + this.S[this.j] = t; + return this.S[(t + this.S[this.i]) & 255]; +} + +Arcfour.prototype.init = ARC4init; +Arcfour.prototype.next = ARC4next; + +// Plug in your RNG constructor here +function prng_newstate() { + return new Arcfour(); +} + +// Pool size must be a multiple of 4 and greater than 32. +// An array of bytes the size of the pool will be passed to init() +var rng_psize = 256; + +module.exports.Arcfour = Arcfour; +module.exports.rng_psize = rng_psize; +module.exports.prng_newstate = prng_newstate; diff --git a/jslib/public.js b/jslib/public.js new file mode 100644 index 0000000..df6a443 --- /dev/null +++ b/jslib/public.js @@ -0,0 +1,282 @@ +/* eslint-disable */ + +const {restCalls} = require("../src/utils/DefaultRestCalls"); +const parser = require('xml2json'); +const CryptoJS = require('crypto-js'); +const {RSAKey} = require('./rsa'); + + +var C = CryptoJS; +var C_lib = C.lib; +var WordArray = C_lib.WordArray; +var C_algo = C.algo; + +var SHA2 = C_algo.SHA256; +var HmacSHA2 = C.HmacSHA256; +var Base = C_lib.Base; + +var SCRAM = C_algo.SCRAM = Base.extend({ + cfg: Base.extend({ + keySize: 8, + hasher: SHA2, + hmac: HmacSHA2 + }), + + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + /** + * return client nonce + */ + nonce: function () { + lastNonce = WordArray.random(this.cfg.keySize * 4); + return lastNonce; + }, + /** + * pbkdf2 + */ + saltedPassword: function (password, salt, iterations) { + return CryptoJS.PBKDF2(password, salt, { + keySize: this.cfg.keySize, + iterations: iterations, + hasher: this.cfg.hasher + }); + }, + /** + * ClientKey = HMAC(saltPwd, "Client Key") + */ + clientKey: function (saltPwd) { + return this.cfg.hmac(saltPwd, "Client Key"); + }, + /** + * ServerKey = HMAC(saltPwd, "Server Key") + */ + serverKey: function (saltPwd) { + return this.cfg.hmac(saltPwd, "Server Key"); + }, + /** + * StoredKey = HASH(ClientKey) + */ + storedKey: function (clientKey) { + var hasher = this.cfg.hasher.create(); + hasher.update(clientKey); + + return hasher.finalize(); + }, + /** + * Signature = HMAC(StoredKey, AuthMessage) + */ + signature: function (storedKey, authMessage) { + return this.cfg.hmac(storedKey, authMessage); + }, + /** + * ClientProof = ClientKey ^ ClientSignature + */ + clientProof: function (password, salt, iterations, authMessage) { + var spwd = this.saltedPassword(password, salt, iterations); + var ckey = this.clientKey(spwd); + var skey = this.storedKey(ckey); + var csig = this.signature(skey, authMessage); + + for (var i = 0; i < ckey.sigBytes / 4; i += 1) { + ckey.words[i] = ckey.words[i] ^ csig.words[i] + } + return ckey.toString(); + }, + /** + * ServerProof = HMAC(ServerKey, AuthMessage) + */ + serverProof: function (password, salt, iterations, authMessage) { + var spwd = this.saltedPassword(password, salt, iterations); + var skey = this.serverKey(spwd); + var sig = this.signature(skey, authMessage); + return sig.toString(); + } +}); + +/** + * var scram = CryptoJS.SCRAM(); + */ +C.SCRAM = function (cfg) { + return SCRAM.create(cfg); +}; + + +async function getPublicKey(session){ + const resp = await restCalls.fetchData(`http://${session.url}/api/webserver/publickey`,'GET', { + 'cookie': `sessionId=${session.SesInfo}`, + __RequestVerificationToken: session.TokInfo + }); + const message = JSON.parse(parser.toJson(resp)); + return message.response; +} + +function utf8Encode(string) { + var stringTemp = string.replace(/\r\n/g, '\n'); + var utftext = ''; + for (var n = 0; n < stringTemp.length; n++) { + var charStr = stringTemp.charCodeAt(n); + if (charStr < 128) { + utftext += String.fromCharCode(charStr); + } else if ((charStr > 127) && (charStr < 2048)) { + utftext += String.fromCharCode((charStr >> 6) | 192); + utftext += String.fromCharCode((charStr & 63) | 128); + } else { + utftext += String.fromCharCode((charStr >> 12) | 224); + utftext += String.fromCharCode(((charStr >> 6) & 63) | 128); + utftext += String.fromCharCode((charStr & 63) | 128); + } + } + return utftext; +} + +function base64encode(str) { + var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var outputStr = ''; + var char1; + var char2; + var char3; + var encry1; + var encry2; + var encry3; + var encry4; + var i = 0; + var input = utf8Encode(str); + while (i < input.length) { + char1 = input.charCodeAt(i++); + char2 = input.charCodeAt(i++); + char3 = input.charCodeAt(i++); + encry1 = char1 >> 2; + encry2 = ((char1 & 3) << 4) | (char2 >> 4); + encry3 = ((char2 & 15) << 2) | (char3 >> 6); + encry4 = char3 & 63; + if (isNaN(char2)) { + encry3 = encry4 = 64; + } else if (isNaN(char3)) { + encry4 = 64; + } + outputStr += keyStr.charAt(encry1) + keyStr.charAt(encry2) + keyStr.charAt(encry3) + keyStr.charAt(encry4); + } + return outputStr; +} + +async function doRSAEncrypt(session,encstring) { + if (encstring === '') { + return ''; + } + let res; + const gEncPublickey = { e: '', n: '' }; + const pubkeyKeyInfo = await getPublicKey(session); + // eslint-disable-next-line prefer-destructuring + gEncPublickey.n = pubkeyKeyInfo.encpubkeyn; + // eslint-disable-next-line prefer-destructuring + gEncPublickey.e = pubkeyKeyInfo.encpubkeye; + var rsa = new RSAKey(); + rsa.setPublic(gEncPublickey.n, gEncPublickey.e); + var encStr = base64encode(encstring); + var num = encStr.length / 245; + // if (EMUI.LoginStateController.rsapadingtype === '1') { + num = encStr.length / 214; + // } + var restotal = ''; + var rsan = gEncPublickey.n; + for (var i = 0; i < num; i++) { + // if (EMUI.LoginStateController.rsapadingtype === '1') { + var encdata = encStr.substr(i * 214, 214); + res = rsa.encryptOAEP(encdata); + // } else { + // var encdata = encStr.substr(i * 245, 245); + // res = rsa.encrypt(encdata); + // } + if (res.length !== rsan.length) { + i--; + continue; + } + restotal += res; + } + return restotal; +} + +const scram = CryptoJS.SCRAM(); +const smsNonce = scram.nonce().toString(); +const smsSalt = scram.nonce().toString(); +const nonceStr = smsNonce + smsSalt; + + +function getDAesString(encrypted, keystr, ivstr) { + var key = CryptoJS.enc.Hex.parse(keystr); + var iv = CryptoJS.enc.Hex.parse(ivstr); + var decrypted = CryptoJS.AES.decrypt(encrypted, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }); + return decrypted.toString(CryptoJS.enc.Latin1); +} + +function utf8to16(str) { + var output, i, leng, unic; + var char1, char2; + output = ""; + leng = str.length; + i = 0; + while (i < leng) { + unic = str.charCodeAt(i++); + switch (unic >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + output += str.charAt(i - 1); + break; + case 12: + case 13: + char1 = str.charCodeAt(i++); + output += String.fromCharCode(((unic & 0x1F) << 6) | (char1 & 0x3F)); + break; + case 14: + char1 = str.charCodeAt(i++); + char2 = str.charCodeAt(i++); + output += String.fromCharCode(((unic & 0x0F) << 12) | + ((char1 & 0x3F) << 6) | + ((char2 & 0x3F) << 0)); + break; + } + } + return output; +} + +function dataDecrypt(scram,smsNonce,smsSalt,nonceStr,pwdret) { + if (!pwdret.response + || !pwdret.response.pwd + || !pwdret.response.hash + || !pwdret.response.iter) { + return; + } + var smsEncrypted = pwdret.response.pwd; + var salt = CryptoJS.enc.Hex.parse(smsSalt); + var iter = pwdret.response.iter; + var saltedStr = scram.saltedPassword(smsNonce, salt, iter); + saltedStr = saltedStr.toString(); + var aesKey = saltedStr.substring(0, 32); + var aesIV = saltedStr.substring(32, 48); + var hmacKey = saltedStr.substring(48, 64); + var hashData = scram.signature(CryptoJS.enc.Hex.parse(smsEncrypted), CryptoJS.enc.Hex.parse(hmacKey)); + hashData = hashData.toString(); + if (pwdret.response.hash !== hashData) { + throw new Error('UserPwd hash error'); + } + var encrypted = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(smsEncrypted)); + var decryptedData = getDAesString(encrypted, aesKey, aesIV); + decryptedData = utf8to16(decryptedData); + decryptedData = decryptedData.substring(decryptedData.indexOf('')); + return decryptedData ; +} + +module.exports.dataDecrypt = dataDecrypt; +module.exports.CryptoJS = CryptoJS; +module.exports.doRSAEncrypt = doRSAEncrypt; diff --git a/jslib/rng.js b/jslib/rng.js new file mode 100644 index 0000000..0be00cb --- /dev/null +++ b/jslib/rng.js @@ -0,0 +1,79 @@ +// Random number generator - requires a PRNG backend, e.g. prng4.js + +// For best results, put code like +// +// in your main HTML document. + +var getRandomValues = require('get-random-values'); +const prng4 = require('./prng4.js'); +var rng_state; +var rng_pool; +var rng_pptr; + +// Mix in a 32-bit integer into the pool +function rng_seed_int(x) { + rng_pool[rng_pptr++] ^= x & 255; + rng_pool[rng_pptr++] ^= (x >> 8) & 255; + rng_pool[rng_pptr++] ^= (x >> 16) & 255; + rng_pool[rng_pptr++] ^= (x >> 24) & 255; + if(rng_pptr >= prng4.rng_psize) rng_pptr -= prng4.rng_psize; +} + +// Mix in the current time (w/milliseconds) into the pool +function rng_seed_time() { + rng_seed_int(new Date().getTime()); +} + +// Initialize the pool with junk if needed. +if(rng_pool == null) { + rng_pool = new Array(); + rng_pptr = 0; + var t; + + // Use webcrypto if available + var ua = new Uint8Array(32); + getRandomValues(ua); + for(t = 0; t < 32; ++t) + rng_pool[rng_pptr++] = ua[t]; + + // if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { + // // Extract entropy (256 bits) from NS4 RNG if available + // var z = window.crypto.random(32); + // for(t = 0; t < z.length; ++t) + // rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; + // } + while(rng_pptr < prng4.rng_psize) { // extract some randomness from Math.random() + t = Math.floor(65536 * Math.random()); + rng_pool[rng_pptr++] = t >>> 8; + rng_pool[rng_pptr++] = t & 255; + } + rng_pptr = 0; + rng_seed_time(); + //rng_seed_int(window.screenX); + //rng_seed_int(window.screenY); +} + +function rng_get_byte() { + if(rng_state == null) { + rng_seed_time(); + rng_state = prng4.prng_newstate(); + rng_state.init(rng_pool); + for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) + rng_pool[rng_pptr] = 0; + rng_pptr = 0; + //rng_pool = null; + } + // TODO: allow reseeding after first request + return rng_state.next(); +} + +function rng_get_bytes(ba) { + var i; + for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); +} + +function SecureRandom() {} + +SecureRandom.prototype.nextBytes = rng_get_bytes; + +module.exports.SecureRandom = SecureRandom; diff --git a/jslib/rsa.js b/jslib/rsa.js new file mode 100644 index 0000000..b5c2198 --- /dev/null +++ b/jslib/rsa.js @@ -0,0 +1,228 @@ +// Depends on jsbn.js and rng.js + +// Version 1.1: support utf-8 encoding in pkcs1pad2 + +// convert a (hex) string to a bignum object + +const { BigInteger } = require('./jsbn'); +const { SecureRandom } = require('./rng'); +const {KJUR} = require('./crypto-1.1.js'); + +function parseBigInt(str, r) { + return new BigInteger(str, r); +} + +function linebrk(s, n) { + let ret = ''; + let i = 0; + while (i + n < s.length) { + ret += `${s.substring(i, i + n)}\n`; + i += n; + } + return ret + s.substring(i, s.length); +} + +function byte2Hex(b) { + if (b < 0x10) return `0${b.toString(16)}`; + return b.toString(16); +} + +/** + * convert a raw string including non printable characters to hexadecimal encoded string.
+ * @name rstrtohex + * @function + * @param {String} s raw string + * @return {String} hexadecimal encoded string + * @since 1.1.2 + * @example + * rstrtohex("a\x00a") → "610061" + */ +function rstrtohex(s) { + var result = ""; + for (var i = 0; i < s.length; i++) { + result += ("0" + s.charCodeAt(i).toString(16)).slice(-2); + } + return result; +} + +function hextorstr(sHex) { + var s = ""; + for (var i = 0; i < sHex.length - 1; i += 2) { + s += String.fromCharCode(parseInt(sHex.substr(i, 2), 16)); + } + return s; +} + +// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint +function pkcs1pad2(s, n) { + if (n < s.length + 11) { // TODO: fix for utf-8 + alert('Message too long for RSA'); + return null; + } + const ba = new Array(); + let i = s.length - 1; + while (i >= 0 && n > 0) { + const c = s.charCodeAt(i--); + if (c < 128) { // encode using utf-8 + ba[--n] = c; + } else if ((c > 127) && (c < 2048)) { + ba[--n] = (c & 63) | 128; + ba[--n] = (c >> 6) | 192; + } else { + ba[--n] = (c & 63) | 128; + ba[--n] = ((c >> 6) & 63) | 128; + ba[--n] = (c >> 12) | 224; + } + } + ba[--n] = 0; + const rng = new SecureRandom(); + const x = new Array(); + while (n > 2) { // random non-zero pad + x[0] = 0; + while (x[0] == 0) rng.nextBytes(x); + ba[--n] = x[0]; + } + ba[--n] = 2; + ba[--n] = 0; + return new BigInteger(ba); +} + + +// PKCS#1 (OAEP) mask generation function +function oaep_mgf1_arr(seed, len, hash) { + var mask = '', i = 0; + + while (mask.length < len) { + mask += hash(String.fromCharCode.apply(String, seed.concat([ + (i & 0xff000000) >> 24, + (i & 0x00ff0000) >> 16, + (i & 0x0000ff00) >> 8, + i & 0x000000ff]))); + i += 1; + } + + return mask; +} + +/** + * PKCS#1 (OAEP) pad input string s to n bytes, and return a bigint + * @name oaep_pad + * @param s raw string of message + * @param n key length of RSA key + * @param hash JavaScript function to calculate raw hash value from raw string or algorithm name (ex. "SHA1") + * @param hashLen byte length of resulted hash value (ex. 20 for SHA1) + * @return {BigInteger} BigInteger object of resulted PKCS#1 OAEP padded message + * @description + * This function calculates OAEP padded message from original message.
+ * NOTE: Since jsrsasign 6.2.0, 'hash' argument can accept an algorithm name such as "sha1". + * @example + * oaep_pad("aaa", 128) → big integer object // SHA-1 by default + * oaep_pad("aaa", 128, function(s) {...}, 20); + * oaep_pad("aaa", 128, "sha1"); + */ +function oaep_pad(s, n, hash, hashLen) { + var MD = KJUR.crypto.MessageDigest; + var Util = KJUR.crypto.Util; + var algName = null; + + if (!hash) hash = "sha1"; + + if (typeof hash === "string") { + algName = MD.getCanonicalAlgName(hash); + hashLen = MD.getHashLength(algName); + hash = function (s) { + return hextorstr(Util.hashHex(rstrtohex(s), algName)); + }; + } + + if (s.length + 2 * hashLen + 2 > n) { + throw "Message too long for RSA"; + } + + var PS = '', i; + + for (i = 0; i < n - s.length - 2 * hashLen - 2; i += 1) { + PS += '\x00'; + } + + var DB = hash('') + PS + '\x01' + s; + var seed = new Array(hashLen); + new SecureRandom().nextBytes(seed); + + var dbMask = oaep_mgf1_arr(seed, DB.length, hash); + var maskedDB = []; + + for (i = 0; i < DB.length; i += 1) { + maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i); + } + + var seedMask = oaep_mgf1_arr(maskedDB, seed.length, hash); + var maskedSeed = [0]; + + for (i = 0; i < seed.length; i += 1) { + maskedSeed[i + 1] = seed[i] ^ seedMask.charCodeAt(i); + } + + return new BigInteger(maskedSeed.concat(maskedDB)); +} + +// "empty" RSA key constructor +function RSAKey() { + this.n = null; + this.e = 0; + this.d = null; + this.p = null; + this.q = null; + this.dmp1 = null; + this.dmq1 = null; + this.coeff = null; +} + +// Set the public key fields N and e from hex strings +function RSASetPublic(N, E) { + if (N != null && E != null && N.length > 0 && E.length > 0) { + this.n = parseBigInt(N, 16); + this.e = parseInt(E, 16); + } else alert('Invalid RSA public key'); +} + +// Perform raw public operation on "x": return x^e (mod n) +function RSADoPublic(x) { + return x.modPowInt(this.e, this.n); +} + +// Return the PKCS#1 RSA encryption of "text" as an even-length hex string +function RSAEncrypt(text) { + const m = pkcs1pad2(text, (this.n.bitLength() + 7) >> 3); + if (m == null) return null; + const c = this.doPublic(m); + if (c == null) return null; + const h = c.toString(16); + if ((h.length & 1) == 0) return h; return `0${h}`; +} + +function RSAEncryptOAEP(text, hash, hashLen) { + var m = oaep_pad(text, (this.n.bitLength() + 7) >> 3, hash, hashLen); + if (m == null) return null; + var c = this.doPublic(m); + if (c == null) return null; + var h = c.toString(16); + if ((h.length & 1) == 0) return h; else return "0" + h; +} + +// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string +// function RSAEncryptB64(text) { +// var h = this.encrypt(text); +// if(h) return hex2b64(h); else return null; +// } + +// protected +RSAKey.prototype.doPublic = RSADoPublic; + +// public +RSAKey.prototype.setPublic = RSASetPublic; +RSAKey.prototype.encrypt = RSAEncrypt; +RSAKey.prototype.encryptOAEP = RSAEncryptOAEP; +// RSAKey.prototype.encrypt_b64 = RSAEncryptB64; + +module.exports.RSAKey = RSAKey; diff --git a/jslib/rsa2.js b/jslib/rsa2.js new file mode 100644 index 0000000..1dfdb70 --- /dev/null +++ b/jslib/rsa2.js @@ -0,0 +1,132 @@ +// Depends on rsa.js and jsbn2.js + +// Version 1.1: support utf-8 decoding in pkcs1unpad2 + +// Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext +function pkcs1unpad2(d,n) { + var b = d.toByteArray(); + var i = 0; + while(i < b.length && b[i] == 0) ++i; + if(b.length-i != n-1 || b[i] != 2) + return null; + ++i; + while(b[i] != 0) + if(++i >= b.length) return null; + var ret = ""; + while(++i < b.length) { + var c = b[i] & 255; + if(c < 128) { // utf-8 decode + ret += String.fromCharCode(c); + } + else if((c > 191) && (c < 224)) { + ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63)); + ++i; + } + else { + ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63)); + i += 2; + } + } + return ret; +} + +// Set the private key fields N, e, and d from hex strings +function RSASetPrivate(N,E,D) { + if(N != null && E != null && N.length > 0 && E.length > 0) { + this.n = parseBigInt(N,16); + this.e = parseInt(E,16); + this.d = parseBigInt(D,16); + } + else + alert("Invalid RSA private key"); +} + +// Set the private key fields N, e, d and CRT params from hex strings +function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) { + if(N != null && E != null && N.length > 0 && E.length > 0) { + this.n = parseBigInt(N,16); + this.e = parseInt(E,16); + this.d = parseBigInt(D,16); + this.p = parseBigInt(P,16); + this.q = parseBigInt(Q,16); + this.dmp1 = parseBigInt(DP,16); + this.dmq1 = parseBigInt(DQ,16); + this.coeff = parseBigInt(C,16); + } + else + alert("Invalid RSA private key"); +} + +// Generate a new random private key B bits long, using public expt E +function RSAGenerate(B,E) { + var rng = new SecureRandom(); + var qs = B>>1; + this.e = parseInt(E,16); + var ee = new BigInteger(E,16); + for(;;) { + for(;;) { + this.p = new BigInteger(B-qs,1,rng); + if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; + } + for(;;) { + this.q = new BigInteger(qs,1,rng); + if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; + } + if(this.p.compareTo(this.q) <= 0) { + var t = this.p; + this.p = this.q; + this.q = t; + } + var p1 = this.p.subtract(BigInteger.ONE); + var q1 = this.q.subtract(BigInteger.ONE); + var phi = p1.multiply(q1); + if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { + this.n = this.p.multiply(this.q); + this.d = ee.modInverse(phi); + this.dmp1 = this.d.mod(p1); + this.dmq1 = this.d.mod(q1); + this.coeff = this.q.modInverse(this.p); + break; + } + } +} + +// Perform raw private operation on "x": return x^d (mod n) +function RSADoPrivate(x) { + if(this.p == null || this.q == null) + return x.modPow(this.d, this.n); + + // TODO: re-calculate any missing CRT params + var xp = x.mod(this.p).modPow(this.dmp1, this.p); + var xq = x.mod(this.q).modPow(this.dmq1, this.q); + + while(xp.compareTo(xq) < 0) + xp = xp.add(this.p); + return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); +} + +// Return the PKCS#1 RSA decryption of "ctext". +// "ctext" is an even-length hex string and the output is a plain string. +function RSADecrypt(ctext) { + var c = parseBigInt(ctext, 16); + var m = this.doPrivate(c); + if(m == null) return null; + return pkcs1unpad2(m, (this.n.bitLength()+7)>>3); +} + +// Return the PKCS#1 RSA decryption of "ctext". +// "ctext" is a Base64-encoded string and the output is a plain string. +//function RSAB64Decrypt(ctext) { +// var h = b64tohex(ctext); +// if(h) return this.decrypt(h); else return null; +//} + +// protected +RSAKey.prototype.doPrivate = RSADoPrivate; + +// public +RSAKey.prototype.setPrivate = RSASetPrivate; +RSAKey.prototype.setPrivateEx = RSASetPrivateEx; +RSAKey.prototype.generate = RSAGenerate; +RSAKey.prototype.decrypt = RSADecrypt; +//RSAKey.prototype.b64_decrypt = RSAB64Decrypt; diff --git a/package.json b/package.json new file mode 100644 index 0000000..681ea23 --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "e3372h-320-cli", + "version": "1.0.0", + "description": "e3372h-320 client", + "main": "index.js", + "scripts": { + "sendSMS": "node index.js sendSMS --phone=+11111111111 --message=test123 --url=192.168.89.1 ", + "contacts": "node index.js contacts --url=192.168.89.1", + "getContactsCount": "node index.js contactPages --url=192.168.89.1 --exportFormat=json", + "sms": "node index.js sms --phone=+11111111111 --url=192.168.89.1 --page=1", + "pages": "node index.js pages --phone=+11111111111 --url=192.168.89.1 --exportFormat=json", + "enableData": "node index.js mobileData --mode=on --url=192.168.89.1", + "disableData": "node index.js mobileData --mode=off --url=192.168.89.1", + "reconnected": "node index.js mobileData --mode=reconnect --url=192.168.89.1", + "monitoring": "node index.js monitoring --url=192.168.89.1", + "lint": "eslint src --ext .ts src", + "build": "tsc --build", + "lint:fix": "eslint --fix src --ext .ts" + }, + "bin": { + "e3372h_320": "./bin/e3372h_320" + }, + "dependencies": { + "axios": "^0.24.0", + "crypto-js": "^4.1.1", + "get-random-values": "^1.2.2", + "xml2json": "^0.12.0", + "yargs": "^17.2.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vzakharchenko/E3372h-320-cli.git" + }, + "keywords": [ + "huawei", + "e3372h-320", + "client", + "rest-api", + "publicKey" + ], + "devDependencies": { + "@shopify/eslint-plugin": "^40.4.0", + "@types/xml2json": "^0.11.4", + "@types/yargs": "^17.0.4", + "@typescript-eslint/parser": "^5.2.0", + "eslint": "7.3.0", + "eslint-plugin-no-loops": "^0.3.0", + "parcel-bundler": "^1.12.5", + "typescript": "^4.4.4" + }, + "author": "vzakharchenko", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/vzakharchenko/E3372h-320-cli/issues" + }, + "homepage": "https://github.com/vzakharchenko/E3372h-320-cli#readme" +} diff --git a/src/ListSMS.ts b/src/ListSMS.ts new file mode 100644 index 0000000..94e06e4 --- /dev/null +++ b/src/ListSMS.ts @@ -0,0 +1,206 @@ +import fs from "fs"; + +import parser from 'xml2json'; + +import {SessionData} from './startSession'; +import {restCalls} from "./utils/DefaultRestCalls"; +import {ExportFormat} from "./utils/Constants"; + +const huawei = require('../jslib/public'); + +async function saveFile(filename: string, data: string) { + await fs.promises.writeFile(filename, data); +} + +export async function getSMSByUsers(sessionData: SessionData, + phone: string, + pageindex: number, + exportFile: string, + exportFormat: ExportFormat, + deleteAfter: boolean) { + // const count = await getContactSMSPages(sessionData, phone, '', 'hide'); + // if (count === 0) { + // console.log(`contact ${phone} does not have messages`); + // return; + // } + const scram = huawei.CryptoJS.SCRAM(); + const smsNonce = scram.nonce().toString(); + const smsSalt = scram.nonce().toString(); + const nonceStr = smsNonce + smsSalt; + const encrpt = await huawei.doRSAEncrypt(sessionData, nonceStr); + const data = await huawei.doRSAEncrypt(sessionData, `${phone}${pageindex}20${encrpt}`); + const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/sms-list-phone`, 'POST', data, { + __RequestVerificationToken: sessionData.TokInfo, + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + const pwdret = JSON.parse(parser.toJson(resp)); + const ret = huawei.dataDecrypt(scram, smsNonce, smsSalt, nonceStr, pwdret); + + if (exportFormat !== 'hide') { + if (exportFormat === 'xml') { + await saveFile(exportFile, ret); + console.info(`xml file ${exportFile} created`); + } else if (exportFormat === 'json') { + await saveFile(exportFile, parser.toJson(ret)); + console.info(`json file ${exportFile} created`); + } else { + const text = parser.toJson(ret); + const json = JSON.parse(text); + if (Array.isArray(json.response.messages.message)) { + json.response.messages.message.forEach((message: any) => { + console.log(`MessageId: ${message.index} Phone: ${message.phone} Message: ${JSON.stringify(message.content)}`); + }); + } else { + const message: any = json.response.messages.message; + console.log(`MessageId: ${message.index} Phone: ${message.phone} Message: ${JSON.stringify(message.content)}`); + } + } + } + + if (deleteAfter) { + const text = parser.toJson(ret); + const json = JSON.parse(text); + const messages = json.response.messages.message; + for (let i = 0; i < messages.length; i++) { + await deleteMessage(sessionData, messages[i].index); + } + } +} + +export async function getContactSMSPages(sessionData: SessionData, + phone: string, + exportFile: string, + exportFormat: ExportFormat) { + const data = await huawei.doRSAEncrypt(sessionData, `${phone}`); + const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/sms-count-contact`, 'POST', data, { + __RequestVerificationToken: sessionData.TokInfo, + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + + const text = parser.toJson(resp); + const json = JSON.parse(text); + let number = Math.floor(json.response.count / 21); + if (number > 0) { + number += 1; + } + if (exportFormat !== 'hide') { + if (exportFormat === 'xml') { + await saveFile(exportFile, resp); + console.info(`xml file ${exportFile} created`); + } else if (exportFormat === 'json') { + await saveFile(exportFile, parser.toJson(resp)); + console.info(`json file ${exportFile} created`); + } else { + console.info(`${number}`); + } + } + return number; +} + +export async function getSMSPages(sessionData: SessionData, + exportFile: string, + exportFormat: ExportFormat) { + const resp = await restCalls.fetchData(`http://${sessionData.url}/api/sms/sms-count`, 'GET'); + const text = parser.toJson(resp); + const json = JSON.parse(text); + let number = Math.floor((json.response.LocalInbox + json.response.LocalOutbox) / 21); + if (number > 0) { + number += 1; + } + if (exportFormat !== 'hide') { + if (exportFormat === 'xml') { + await saveFile(exportFile, resp); + console.info(`xml file ${exportFile} created`); + } else if (exportFormat === 'json') { + await saveFile(exportFile, parser.toJson(resp)); + console.info(`json file ${exportFile} created`); + } else { + console.info(`${number}`); + } + } + return number; +} + +export async function deleteMessage(sessionData: SessionData, + messageId: string) { + const data = await huawei.doRSAEncrypt(sessionData, `${messageId}`); + const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/delete-sms`, 'POST', data, { + __RequestVerificationToken: sessionData.TokInfo, + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + const text = parser.toJson(resp); + const json = JSON.parse(text); + if (json.response !== 'OK') { + throw new Error(`Delete message error: ${text}`); + } + console.info('Message or Contact deleted'); +} + +export async function sendMessage(sessionData: SessionData, + phones: string, + message: string) { + const scram = huawei.CryptoJS.SCRAM(); + const smsNonce = scram.nonce().toString(); + const smsSalt = scram.nonce().toString(); + const nonceStr = smsNonce + smsSalt; + const encrpt = await huawei.doRSAEncrypt(sessionData, nonceStr); + const data = await huawei.doRSAEncrypt(sessionData, `-1${(phones)}${message}${message.length}12021-10-27 00:12:24${encrpt}`); + const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/send-sms`, 'POST', data, { + __RequestVerificationToken: sessionData.TokInfo, + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + const text = parser.toJson(resp); + const json = JSON.parse(text); + if (json.response !== 'OK') { + throw new Error(`Delete message error: ${text}`); + } + console.info('Message sent'); +} + + +export async function getSMSContacts(sessionData: SessionData, + pageindex: number, + exportFile: string, + exportFormat: ExportFormat) { + const count = await getSMSPages(sessionData, '', 'hide'); + if (count === 0) { + console.log('huawei does not have contacts'); + return; + } + const scram = huawei.CryptoJS.SCRAM(); + const smsNonce = scram.nonce().toString(); + const smsSalt = scram.nonce().toString(); + const nonceStr = smsNonce + smsSalt; + const encrpt = await huawei.doRSAEncrypt(sessionData, nonceStr); + const data = await huawei.doRSAEncrypt(sessionData, `${pageindex}20${encrpt}`); + const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/sms-list-contact`, 'POST', + data, { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + __RequestVerificationToken: sessionData.TokInfo, + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + const pwdret = JSON.parse(parser.toJson(resp)); + const ret = huawei.dataDecrypt(scram, smsNonce, smsSalt, nonceStr, pwdret); + if (exportFormat === 'xml') { + await saveFile(exportFile, ret); + console.info(`xml file ${exportFile} created`); + } else if (exportFormat === 'json') { + await saveFile(exportFile, parser.toJson(ret)); + console.info(`json file ${exportFile} created`); + } else { + const text = parser.toJson(ret); + const json = JSON.parse(text); + if (Array.isArray(json.response.messages.message)) { + json.response.messages.message.forEach((message: any) => { + console.log(`MessageId: ${message.index} Phone: ${message.phone} lastMessage: ${JSON.stringify(message.content)}`); + }); + } else { + const message: any = json.response.messages.message; + console.log(`(MessageId: ${message.index}) Phone: ${message.phone} lastMessage: ${message.content}`); + } + } +} diff --git a/src/MobileData.ts b/src/MobileData.ts new file mode 100644 index 0000000..161ac57 --- /dev/null +++ b/src/MobileData.ts @@ -0,0 +1,66 @@ +import fs from "fs"; + +import parser from "xml2json"; + +import {SessionData} from "./startSession"; +import {restCalls} from "./utils/DefaultRestCalls"; +import {ExportFormat} from "./utils/Constants"; + + +type MobileStatus = 'on' | 'off'; + +async function saveFile(filename: string, data: string) { + await fs.promises.writeFile(filename, data); +} + +export async function controlMobileData(sessionData: SessionData, mobileStatus: MobileStatus) { + const data = `${mobileStatus === 'on' ? 1 : 0}`; + const resp = await restCalls.sendData(`http://${sessionData.url}/api/dialup/mobile-dataswitch`, 'POST', data, { + __RequestVerificationToken: sessionData.TokInfo, + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + const text = parser.toJson(resp); + const json = JSON.parse(text); + if (json.response !== 'OK') { + throw new Error(`Control Mobile Data error: ${text}`); + } + console.log(`Control Mobile Data changed to ${mobileStatus}`); +} + +export async function reconnect(sessionData: SessionData) { + const data = `1`; + const resp = await restCalls.sendData(`http://${sessionData.url}/api/net/reconnect`, 'POST', data, { + __RequestVerificationToken: sessionData.TokInfo, + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + const text = parser.toJson(resp); + const json = JSON.parse(text); + if (json.response !== 'OK') { + throw new Error(`Reconnecting error: ${text}`); + } + console.log('Reconnected'); +} + +export async function status(sessionData: SessionData, exportFile: string, + exportFormat: ExportFormat) { + const resp = await restCalls.fetchData(`http://${sessionData.url}/api/monitoring/status`, 'GET', { + __RequestVerificationToken: sessionData.TokInfo, + Cookie: `SessionId=${sessionData.SesInfo}`, + }); + if (exportFormat !== 'hide') { + if (exportFormat === 'xml') { + await saveFile(exportFile, resp); + console.info(`xml file ${exportFile} created`); + } else if (exportFormat === 'json') { + await saveFile(exportFile, parser.toJson(resp)); + console.info(`json file ${exportFile} created`); + } else { + const text = parser.toJson(resp); + const json = JSON.parse(text); + const response = json.response; + Object.keys(response).forEach((key) => { + console.info(`${key}=${response[key]}`); + }); + } + } +} diff --git a/src/startSession.ts b/src/startSession.ts new file mode 100644 index 0000000..b5f90e1 --- /dev/null +++ b/src/startSession.ts @@ -0,0 +1,57 @@ +import parser from 'xml2json'; + +import {restCalls} from "./utils/DefaultRestCalls"; + +export type SessionData = { + TokInfo: string, + SesInfo: string, + url: string +} + +type SessionData0 = { + TokInfo: string, + SesInfo: string +} + +// async function saveFile(data: SessionData) { +// await fs.promises.writeFile(os.tmpdir() + '/huawei.tmp', JSON.stringify(data)); +// } +// +// async function readFile(): Promise { +// try { +// return JSON.parse(await fs.promises.readFile(os.tmpdir() + '/huawei.tmp', 'utf-8')); +// } catch (e) { +// console.error("read session data error", e); +// await fs.promises.rm(os.tmpdir() + '/huawei.tmp'); +// throw e; +// } +// } +// +async function getSessionId(url: string): Promise { + const resp = await restCalls.fetchData(`http://${url}/api/webserver/SesTokInfo`, 'GET'); + const message = JSON.parse(parser.toJson(resp)); + return message.response; +} +// +// export async function getCurrentSession() { +// try { +// const session: SessionData = await readFile(); +// await startSession(session.url); +// return await readFile(); +// } catch (e) { +// console.error("Session file does not exist or not valid please start a new session", e); +// throw new Error("Session Does not exist") +// } +// +// } + +export async function startSession(url: string) { + const session0: SessionData0 = await getSessionId(url); + const sessionData: SessionData = { + ...session0, + url, + }; + return sessionData; +} + + diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts new file mode 100644 index 0000000..fc371c5 --- /dev/null +++ b/src/utils/Constants.ts @@ -0,0 +1,2 @@ + +export type ExportFormat = 'xml' | 'json' | 'none' | 'hide'; diff --git a/src/utils/DefaultRestCalls.ts b/src/utils/DefaultRestCalls.ts new file mode 100644 index 0000000..1465324 --- /dev/null +++ b/src/utils/DefaultRestCalls.ts @@ -0,0 +1,33 @@ +import fetch from 'axios'; + +import {HTTPMethod, RestCalls} from './restCalls'; + +class DefaultRestCalls implements RestCalls { + async fetchData(url: string, method: HTTPMethod, headers?: any): Promise { + const ret = await fetch({ + url, + method, + headers, + transformResponse: (req) => req, + withCredentials: true, + timeout: 29000, + }); + return ret.data; + } + + async sendData(url: string, method: HTTPMethod, data: string, headers?: any): Promise { + const ret = await fetch({ + url, + method, + data, + transformResponse: (req) => req, + headers, + withCredentials: true, + timeout: 29000, + }); + return ret.data; + } + +} + +export const restCalls = new DefaultRestCalls(); diff --git a/src/utils/restCalls.ts b/src/utils/restCalls.ts new file mode 100644 index 0000000..57cef61 --- /dev/null +++ b/src/utils/restCalls.ts @@ -0,0 +1,16 @@ +export type HTTPMethod = + | 'get' | 'GET' + | 'delete' | 'DELETE' + | 'head' | 'HEAD' + | 'options' | 'OPTIONS' + | 'post' | 'POST' + | 'put' | 'PUT' + | 'patch' | 'PATCH' + | 'purge' | 'PURGE' + | 'link' | 'LINK' + | 'unlink' | 'UNLINK' + +export interface RestCalls { + fetchData(url:string, method?:HTTPMethod, headers?:any):Promise; + sendData(url:string, method:HTTPMethod, data:string, headers?:any):Promise; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..86220c2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,73 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + "declaration": false, /* Generates corresponding '.d.ts' file. */ + "declarationMap": false, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + "types": ["node"], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "exclude": [ + ] +}