From 961271f16cacdaa401c078c815f208b608f32415 Mon Sep 17 00:00:00 2001 From: vzakharchenko Date: Thu, 28 Oct 2021 23:25:01 +0300 Subject: [PATCH] initial commit --- .eslintignore | 2 + .eslintrc | 37 + .gitignore | 12 + README.md | 515 ++++++++++- bin/e3372h_320 | 3 + docs/4889330.jpg | Bin 0 -> 23423 bytes docs/4889332.jpg | Bin 0 -> 15106 bytes docs/info.png | Bin 0 -> 19148 bytes docs/info2.png | Bin 0 -> 40150 bytes docs/smsConversation1.png | Bin 0 -> 56489 bytes docs/smsConversation2.png | Bin 0 -> 21885 bytes index.ts | 249 ++++++ jslib/base64.js | 71 ++ jslib/crypto-1.1.js | 1524 +++++++++++++++++++++++++++++++++ jslib/jsbn.js | 561 ++++++++++++ jslib/jsbn2.js | 656 ++++++++++++++ jslib/prng4.js | 49 ++ jslib/public.js | 282 ++++++ jslib/rng.js | 79 ++ jslib/rsa.js | 228 +++++ jslib/rsa2.js | 132 +++ package.json | 57 ++ src/ListSMS.ts | 206 +++++ src/MobileData.ts | 66 ++ src/startSession.ts | 57 ++ src/utils/Constants.ts | 2 + src/utils/DefaultRestCalls.ts | 33 + src/utils/restCalls.ts | 16 + tsconfig.json | 73 ++ 29 files changed, 4909 insertions(+), 1 deletion(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100755 bin/e3372h_320 create mode 100644 docs/4889330.jpg create mode 100644 docs/4889332.jpg create mode 100644 docs/info.png create mode 100644 docs/info2.png create mode 100644 docs/smsConversation1.png create mode 100644 docs/smsConversation2.png create mode 100644 index.ts create mode 100644 jslib/base64.js create mode 100644 jslib/crypto-1.1.js create mode 100644 jslib/jsbn.js create mode 100644 jslib/jsbn2.js create mode 100644 jslib/prng4.js create mode 100644 jslib/public.js create mode 100644 jslib/rng.js create mode 100644 jslib/rsa.js create mode 100644 jslib/rsa2.js create mode 100644 package.json create mode 100644 src/ListSMS.ts create mode 100644 src/MobileData.ts create mode 100644 src/startSession.ts create mode 100644 src/utils/Constants.ts create mode 100644 src/utils/DefaultRestCalls.ts create mode 100644 src/utils/restCalls.ts create mode 100644 tsconfig.json 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 0000000000000000000000000000000000000000..5d8535558a866c7f8055b9a15bf2197974a936bb GIT binary patch literal 23423 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 literal 0 HcmV?d00001 diff --git a/docs/4889332.jpg b/docs/4889332.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8d223a7f01b4ec7fb409376b044d72fbabf9da45 GIT binary patch literal 15106 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 literal 0 HcmV?d00001 diff --git a/docs/info.png b/docs/info.png new file mode 100644 index 0000000000000000000000000000000000000000..3488f8082b46feff828c93328829ccd997c0d72a GIT binary patch literal 19148 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 literal 0 HcmV?d00001 diff --git a/docs/smsConversation1.png b/docs/smsConversation1.png new file mode 100644 index 0000000000000000000000000000000000000000..8ba8a59d769b9c31cde7dfe1790d71a1ccc12ddb GIT binary patch literal 56489 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 literal 0 HcmV?d00001 diff --git a/docs/smsConversation2.png b/docs/smsConversation2.png new file mode 100644 index 0000000000000000000000000000000000000000..df61a75df2b4689af4ad43a38b6d0588f344bd7f GIT binary patch literal 21885 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 { + 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. + *

    + *
  • {@link KJUR.crypto.MessageDigest} - Java JCE(cryptograhic extension) style MessageDigest class
  • + *
  • {@link KJUR.crypto.Signature} - Java JCE(cryptograhic extension) style Signature class
  • + *
  • {@link KJUR.crypto.Cipher} - class for encrypting and decrypting data
  • + *
  • {@link KJUR.crypto.Util} - cryptographic utility functions and properties
  • + *
+ * 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: + *
    + *
  • md5 - cryptojs
  • + *
  • sha1 - cryptojs
  • + *
  • sha224 - cryptojs
  • + *
  • sha256 - cryptojs
  • + *
  • sha384 - cryptojs
  • + *
  • sha512 - cryptojs
  • + *
  • ripemd160 - cryptojs
  • + *
  • sha256 - sjcl (NEW from crypto.js 1.0.4)
  • + *
+ * @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: + *
    + *
  • MD5
  • + *
  • SHA1
  • + *
  • SHA224
  • + *
  • SHA256
  • + *
  • SHA384
  • + *
  • SHA512
  • + *
  • RIPEMD160
  • + *
+ * 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: + *
    + *
  • hmacmd5 - cryptojs
  • + *
  • hmacsha1 - cryptojs
  • + *
  • hmacsha224 - cryptojs
  • + *
  • hmacsha256 - cryptojs
  • + *
  • hmacsha384 - cryptojs
  • + *
  • hmacsha512 - cryptojs
  • + *
+ * 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: + *
    + *
  • even length string of 0..9, a..f or A-F: implicitly specified as hexadecimal string
  • + *
  • not above string: implicitly specified as raw string
  • + *
  • {rstr: "\x65\x70"}: explicitly specified as raw string
  • + *
  • {hex: "6570"}: explicitly specified as hexacedimal string
  • + *
  • {utf8: "秘密"}: explicitly specified as UTF8 string
  • + *
  • {b64: "Mi78..=="}: explicitly specified as Base64 string
  • + *
  • {b64u: "Mi7-_"}: explicitly specified as Base64URL string
  • + *
+ * 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: + *
    + *
  • alg - signature algorithm name (ex. {MD5,SHA1,SHA224,SHA256,SHA384,SHA512,RIPEMD160}with{RSA,ECDSA,DSA})
  • + *
  • provider - currently 'cryptojs/jsrsa' only
  • + *
+ *

SUPPORTED ALGORITHMS AND PROVIDERS

+ * This Signature class supports following signature algorithm and provider names: + *
    + *
  • 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
  • + *
  • MD5withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHAwithRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHA1withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHA224withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHA256withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHA384withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHA512withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • RIPEMD160withRSAandMGF1 - cryptojs/jsrsa
  • + *
  • SHA1withDSA - cryptojs/jsrsa
  • + *
  • SHA224withDSA - cryptojs/jsrsa
  • + *
  • SHA256withDSA - cryptojs/jsrsa
  • + *
+ * 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: + *
    + *
  • secp256k1
  • + *
  • secp256r1, NIST P-256, P-256, prime256v1
  • + *
  • secp384r1, NIST P-384, P-384
  • + *
+ * 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
+ *
    + *
  • PEM formatted PKCS#8 encrypted RSA/ECDSA private key concluding "BEGIN ENCRYPTED PRIVATE KEY"
  • + *
  • PEM formatted PKCS#5 encrypted RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" and ",ENCRYPTED"
  • + *
  • PEM formatted PKCS#8 plain RSA/ECDSA private key concluding "BEGIN PRIVATE KEY"
  • + *
  • PEM formatted PKCS#5 plain RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" without ",ENCRYPTED"
  • + *
  • RSAKey object of private key
  • + *
  • KJUR.crypto.ECDSA object of private key
  • + *
  • KJUR.crypto.DSA object of private key
  • + *
+ *
verification
+ *
    + *
  • PEM formatted PKCS#8 RSA/EC/DSA public key concluding "BEGIN PUBLIC KEY"
  • + *
  • PEM formatted X.509 certificate with RSA/EC/DSA public key concluding + * "BEGIN CERTIFICATE", "BEGIN X509 CERTIFICATE" or "BEGIN TRUSTED CERTIFICATE".
  • + *
  • RSAKey object of public key
  • + *
  • KJUR.crypto.ECDSA object of public key
  • + *
  • KJUR.crypto.DSA object of public key
  • + *
+ * @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: + *
    + *
  • RSA - RSA/ECB/PKCS1Padding (default for RSAKey)
  • + *
  • RSAOAEP - RSA/ECB/OAEPWithSHA-1AndMGF1Padding
  • + *
  • RSAOAEP224 - RSA/ECB/OAEPWithSHA-224AndMGF1Padding(*)
  • + *
  • RSAOAEP256 - RSA/ECB/OAEPWithSHA-256AndMGF1Padding
  • + *
  • RSAOAEP384 - RSA/ECB/OAEPWithSHA-384AndMGF1Padding(*)
  • + *
  • RSAOAEP512 - RSA/ECB/OAEPWithSHA-512AndMGF1Padding(*)
  • + *
+ * 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: + *
    + *
  • RSA - RSA/ECB/PKCS1Padding (default for RSAKey)
  • + *
  • RSAOAEP - RSA/ECB/OAEPWithSHA-1AndMGF1Padding
  • + *
  • RSAOAEP224 - RSA/ECB/OAEPWithSHA-224AndMGF1Padding(*)
  • + *
  • RSAOAEP256 - RSA/ECB/OAEPWithSHA-256AndMGF1Padding
  • + *
  • RSAOAEP384 - RSA/ECB/OAEPWithSHA-384AndMGF1Padding(*)
  • + *
  • RSAOAEP512 - RSA/ECB/OAEPWithSHA-512AndMGF1Padding(*)
  • + *
+ * 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": [ + ] +}