diff options
Diffstat (limited to 'lang/js')
46 files changed, 3252 insertions, 0 deletions
diff --git a/lang/js/.babelrc b/lang/js/.babelrc new file mode 100644 index 00000000..9d8d5165 --- /dev/null +++ b/lang/js/.babelrc @@ -0,0 +1 @@ +{ "presets": ["es2015"] } diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html new file mode 100644 index 00000000..c379ef53 --- /dev/null +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <link href="libs/mocha.css" rel="stylesheet" /> + </head> +<body> + <h3>Browsertest</h3> + <div id="mocha"></div> + <!-- load unit tests --> + <script src="libs/mocha.js"></script> + <script src="libs/chai.js"></script> + <script src="setup_testing.js"></script> + <script src="libs/gpgmejs.bundle.js"></script> + <script src="tests/inputvalues.js"></script> +<!-- insert tests here--> + <script src="tests/startup.js"></script> + <script src="tests/encryptTest.js"></script> + <script src="tests/encryptDecryptTest.js"></script> +<!-- run tests --> + <script src="runbrowsertest.js"></script> + </body> +</html> diff --git a/lang/js/BrowserTestExtension/index.html b/lang/js/BrowserTestExtension/index.html new file mode 100644 index 00000000..05d413ba --- /dev/null +++ b/lang/js/BrowserTestExtension/index.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <link href="libs/mocha.css" rel="stylesheet" /> + </head> +<body> + <h3>gpgmejs - Tests</h3> + <p> + The unittests rely on a separately packaged version of gpgmejs, + with the different classes and functions exposed. These tests and their + input values can be found in gpgme/lang/js/test. They do not test the + overall functionality, but the individual behaviour of the components. + <ul> + <li> + <a href="unittests.html"> + Unittests of the individual functions and classes. + </a> + </li> + </ul> + </p> + <p> + The functionality tests, to be found in + gpgme/lang/js/BrowserTestExtension, check the overall functionality of + the standard packaged version of gpgmejs. + <ul> + <li> + <a href="browsertest.html"> + Functionality tests using the bundled library. + </a> + </li> + <li> + <a href="longTests.html"> + Functionality tests with larger/longer running data sets. + </a> + </li> + </ul> + </p> + </body> +</html> diff --git a/lang/js/BrowserTestExtension/longTests.html b/lang/js/BrowserTestExtension/longTests.html new file mode 100644 index 00000000..8ff969b7 --- /dev/null +++ b/lang/js/BrowserTestExtension/longTests.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <link href="libs/mocha.css" rel="stylesheet" /> + </head> +<body> + <h3>Browsertest</h3> + <div id="mocha"></div> + <!-- load unit tests --> + <script src="libs/mocha.js"></script> + <script src="libs/chai.js"></script> + <script src="setup_testing.js"></script> + <script src="libs/gpgmejs.bundle.js"></script> + <script src="tests/inputvalues.js"></script> +<!-- insert tests here--> + <script src="tests/startup.js"></script> + <script src="tests/longRunningTests.js"></script> +<!-- run tests --> + <script src="runbrowsertest.js"></script> + </body> +</html> diff --git a/lang/js/BrowserTestExtension/manifest.json b/lang/js/BrowserTestExtension/manifest.json new file mode 100644 index 00000000..a9e605bc --- /dev/null +++ b/lang/js/BrowserTestExtension/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": 2, + + "name": "Browsertests for gpgmejs", + "description": "Run the browsertests.", + "version": "0.1", + "content_security_policy": "default-src 'self' filesystem:", + "browser_action": { + "default_icon": "testicon.png", + "default_popup": "popup.html" + }, + "permissions": ["nativeMessaging", "activeTab"] + } diff --git a/lang/js/BrowserTestExtension/popup.html b/lang/js/BrowserTestExtension/popup.html new file mode 100644 index 00000000..f17f262a --- /dev/null +++ b/lang/js/BrowserTestExtension/popup.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <script src="popup.js"></script> + </head> + <body> + </body> +</html>
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/popup.js b/lang/js/BrowserTestExtension/popup.js new file mode 100644 index 00000000..12beb1eb --- /dev/null +++ b/lang/js/BrowserTestExtension/popup.js @@ -0,0 +1,44 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +document.addEventListener('DOMContentLoaded', function() { + chrome.tabs.create({ + url: './index.html' + }); +}); diff --git a/lang/js/BrowserTestExtension/runbrowsertest.js b/lang/js/BrowserTestExtension/runbrowsertest.js new file mode 100644 index 00000000..39bc3fb9 --- /dev/null +++ b/lang/js/BrowserTestExtension/runbrowsertest.js @@ -0,0 +1,21 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +mocha.run(); diff --git a/lang/js/BrowserTestExtension/rununittests.js b/lang/js/BrowserTestExtension/rununittests.js new file mode 100644 index 00000000..f85ed8b5 --- /dev/null +++ b/lang/js/BrowserTestExtension/rununittests.js @@ -0,0 +1,21 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +Gpgmejs_test.unittests(); +mocha.run(); diff --git a/lang/js/BrowserTestExtension/setup_testing.js b/lang/js/BrowserTestExtension/setup_testing.js new file mode 100644 index 00000000..7f70d347 --- /dev/null +++ b/lang/js/BrowserTestExtension/setup_testing.js @@ -0,0 +1,22 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +mocha.setup('bdd'); +var expect = chai.expect; +chai.config.includeStack = true;
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/testicon.png b/lang/js/BrowserTestExtension/testicon.png Binary files differnew file mode 100644 index 00000000..12c3f5df --- /dev/null +++ b/lang/js/BrowserTestExtension/testicon.png diff --git a/lang/js/BrowserTestExtension/testkey.pub b/lang/js/BrowserTestExtension/testkey.pub new file mode 100644 index 00000000..cfc329f3 --- /dev/null +++ b/lang/js/BrowserTestExtension/testkey.pub @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf +PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE +BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c +PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870 ++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M +yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAG0EHRlc3RAZXhhbXBsZS5v +cmeJAVQEEwEIAD4WIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8Jn +AAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGw +Fq07W9N01HWULyhHKoMmcHL6rfZ64oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6 +kaBe+wF6Kqir6udFSBW9rPcFg6/VZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rN +GkYo1JCBR0XdRJYCSX3yB4TWv/eXnZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k7 +0DevQeBsv+UjVXjWpNTZmPbvDnd995uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6U +unOf9Rlp1oMzdxMool/d1MlCxg2h3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ +1J8Nsv87SZeEuQENBFrsKEkBCADjoEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6 +yE+x2hk5FoQCajxKa/d4AVxOnJpdwhAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0p +O7bgAiZxkA6RHxtNqhpPnPQoXvUzkzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/ +8i9Taz67pdZwuJjac8qBuJHjzAo1bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt +3RuqVGP316Fk+Sy2+60tC/HlX8jgMyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyL +UGKU+L8cB2g1PGGp2biBFWqZbudZoyRBet/0yH/zirBdQJw1ABEBAAGJATwEGAEI +ACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAKCRAjAWNe +7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ5F32NDJ9 +PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7Jq3e/4Iy +0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02Lkr+2Cc/Q +k6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe23nntMSD +A+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t9Mmd7bM1 ++UyPgbPEr0iWMeyctYsuOLeUyQKMscDT +=QyY6 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/lang/js/BrowserTestExtension/testkey.sec b/lang/js/BrowserTestExtension/testkey.sec new file mode 100644 index 00000000..ced8f3ec --- /dev/null +++ b/lang/js/BrowserTestExtension/testkey.sec @@ -0,0 +1,57 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf +PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE +BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c +PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870 ++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M +yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAEAB/wLJ0gyMjs2fFfT83wM +5Lzz2yQIwV4t3bblBAujdHTqeN5Zmsm/oakFyjSokULK96Kv0R4ej9eoIgMFvxFk +HRkrggxTrbsNJ7I6QcKYHTPeIIj318ykNL6fj0WJUcdPIENukXl5jbqNyk3/4D2y +TTDySyq6jHTgvMH4K4KJUSpglvSJPntTk9RhuFGHAF+sNR9atygDYctAaERMRtSg +LCoSt/AoX5GRMlQjXT9oqQjwSQoZyF4s8HMC8wdTFIE/E0L4IVdHVp8sz2UszNtT +W/evmCA+KVruKjRH/Fhrq4hHkEamW28+j4L6uAyagONP7BONs+S5Oo2zTT9+tV2R +ILTZBADdgLuAgF6C5Lu9jCF6DfFgaT/uafMyQNkEGNlxOHMWHTgLHe475V2eG9gA +amd4yXKyEFKU1PWnvlGuicQSGdzVcwmq61msvXgYD0FK3LP3yWzKnE4X1tzrC9Vp +/uHJxKjewCuyt1f5in919v+T8TbUxBYKC0zX/qWtX+10cTx77QQA6leqhToJ95Yc +u4UBrKMEO+y2v8Svb3LG7yI5oY8tkw0EkJ/kpZ8xTAfZYCe6fXdvVE3PHg2lrxyc +Wv/EU3QY/qA3G82mbXYeJ2jNZaTNYo4MylMrt4Mx25x4ke7JlsE8SVrQ+4CrHkqp +OjSIa7fppLrQ78uW980AtN8NNQGrlTsD/A9aoA60Igxy1Q3K2uSyDCyjLknv57ym +ZSBD3/t7m0l6Q6gbdfhNGosT+Hd4y3actqEqzXZHW2VG4dKZ/wRNkxtSm9adU9vs +EHyzxjb6mKIH32zAG5TaFT20hC+NK6lsyHr9UE2ZrS6ma2sLxGW2O40hqNsdD+5m +NrqeBc2I/js1PMK0EHRlc3RAZXhhbXBsZS5vcmeJAVQEEwEIAD4WIQTUFzW5Ejb9 +uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIe +AQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGwFq07W9N01HWULyhHKoMmcHL6rfZ6 +4oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6kaBe+wF6Kqir6udFSBW9rPcFg6/V +ZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rNGkYo1JCBR0XdRJYCSX3yB4TWv/eX +nZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k70DevQeBsv+UjVXjWpNTZmPbvDnd9 +95uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6UunOf9Rlp1oMzdxMool/d1MlCxg2h +3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ1J8Nsv87SZeEnQOYBFrsKEkBCADj +oEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6yE+x2hk5FoQCajxKa/d4AVxOnJpd +whAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0pO7bgAiZxkA6RHxtNqhpPnPQoXvUz +kzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/8i9Taz67pdZwuJjac8qBuJHjzAo1 +bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt3RuqVGP316Fk+Sy2+60tC/HlX8jg +MyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyLUGKU+L8cB2g1PGGp2biBFWqZbudZ +oyRBet/0yH/zirBdQJw1ABEBAAEAB/4lN3gXOI4OuoOcsvHak4pebx61Mt0YP9cT +qZASIBqxok5x8E28pFh/tYfkYdqRCtdNYZOnxcEoUWh5j6nfwZkEnJ9P/T8GPNk7 +pMKnKXmExi05b5uGHD8nU1rSbf/YkvAF0vpbxd4/RDxbbtQhbUwGzusSI+pBLM0w +5TreEB+vRGBc2gOvXXOtKLNEa7M9rH2EwbAkP3jOGGwgk6adxbQdBcRxq4merqhL +YrVz73bCj8TDc0fsNJyIaZZJ++ejfBFYavsF1pvx9z7FNFi8rSXoiB3SBtaWGfhr +bwNaMZrDc7TRIq/fgGaL6g//bzcWrr1YaHXZ10Bgx6UymDOlYkCpBADm0Hv46sPw +07SO8+IACcaQliOto1pndOPwTimCeo58/7rf8I2a5uuJloGrnPwAX65bKDnUALp6 +X3lnXRNMhnB3Uewx4i00LQmjsxhJfQiGLpMv0j58tn64s7GqQzGVV1JKcQm992RV +jFOydyjZ+K4LGWEOITG/bZrMEVNGCM+OnQQA/Haz8xN0NFSlq7tyfFc0pkx/TiCX +xGfBqbO0wU2b5GMnZbY/06HENpidIzpa231VQaw5/nPTvfhlLKW1iGAkc148cX1q +lL9w2ksXuaHR3LXud2VcfVTIdxU/7h7u1dD/85+c0+7jlGObD9cXKxlM6OjpIJz1 +l5/1h3C5S0TuxHkEAL/3BGihkhNfv1Xx0rWu0/732usX/nE/A9C26hGu41FUf3fp +0ilonKpKZUEwWt5hWSEFCSrznNVekiO0rxvuu3RVegvzThPNU4Pf4JZtJpRVhvUQ +d9ulxJw7V9rs75uNBatTNC0kXuGoXhehw4Bn93xa67gYGd3LfrH+oT0GCDpTSHCJ +ATwEGAEIACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAK +CRAjAWNe7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ +5F32NDJ9PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7 +Jq3e/4Iy0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02L +kr+2Cc/Qk6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe +23nntMSDA+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t +9Mmd7bM1+UyPgbPEr0iWMeyctYsuOLeUyQKMscDT +=hkUm +-----END PGP PRIVATE KEY BLOCK----- diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js new file mode 100644 index 00000000..2fe955e6 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -0,0 +1,225 @@ + +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +describe('Encryption and Decryption', function () { + it('Successful encrypt and decrypt simple string', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + inputvalues.encrypt.good.data, + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + context.decrypt(answer.data).then(function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(inputvalues.encrypt.good.data); + context.connection.disconnect(); + done(); + }); + }); + }); + }); + + it('Decrypt simple non-ascii', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = encryptedData; + context.decrypt(data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal( + '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); + done(); + }); + }); + }).timeout(3000); + + it('Roundtrip does not destroy trailing whitespace', + function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = 'Keks. \rKeks \n Keks \r\n'; + context.encrypt(data, + inputvalues.encrypt.good.fingerprint).then( + function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + + }); + }); + }); + }).timeout(5000); + + for (let j = 0; j < inputvalues.encrypt.good.data_nonascii_32.length; j++){ + it('Roundtrip with >1MB non-ascii input meeting default chunksize (' + (j + 1) + '/' + inputvalues.encrypt.good.data_nonascii_32.length + ')', + function (done) { + let input = inputvalues.encrypt.good.data_nonascii_32[j]; + expect(input).to.have.length(32); + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = ''; + for (let i=0; i < 34 * 1024; i++){ + data += input; + } + context.encrypt(data, + inputvalues.encrypt.good.fingerprint).then( + function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(3000); + }; + + it('Random data, as string', function (done) { + let data = bigString(1000); + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt(data, + inputvalues.encrypt.good.fingerprint).then( + function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(3000); + + it('Data, input as base64', function (done) { + let data = inputvalues.encrypt.good.data; + let b64data = btoa(data); + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt(b64data, + inputvalues.encrypt.good.fingerprint,).then( + function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(data).to.equal(data); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(3000); + + it('Random data, input as base64', function (done) { + //TODO fails. The result is + let data = bigBoringString(0.001); + let b64data = btoa(data); + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt(b64data, + inputvalues.encrypt.good.fingerprint, true).then( + function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(3000); + + it('Random data, input and output as base64', function (done) { + let data = bigBoringString(0.0001); + let b64data = btoa(data); + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt(b64data, + inputvalues.encrypt.good.fingerprint).then( + function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data, true).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(b64data); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(3000); + + +}); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js new file mode 100644 index 00000000..521ed276 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -0,0 +1,156 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +describe('Encryption', function () { + it('Successful encrypt', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + inputvalues.encrypt.good.data, + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); + done(); + }); + }); + }); + + it('Successful encrypt 5 MB', function (done) { + let prm = Gpgmejs.init(); + let data = fixedLengthString(5); + prm.then(function (context) { + context.encrypt( + data, + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); + done(); + }); + }); + }).timeout(10000); + + it('Successful encrypt 20 MB', function (done) { + let prm = Gpgmejs.init(); + let data = fixedLengthString(20); + prm.then(function (context) { + context.encrypt( + data, + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); + done(); + }); + }); + }).timeout(20000); + + it('Successful encrypt 50 MB', function (done) { + let prm = Gpgmejs.init(); + let data = fixedLengthString(50); + prm.then(function (context) { + context.encrypt( + data, + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); + done(); + }); + }); + }).timeout(20000); + + it('Sending encryption without keys fails', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + inputvalues.encrypt.good.data, + null).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.equal('MSG_INCOMPLETE'); + context.connection.disconnect(); + done(); + }); + }); + }); + + it('Sending encryption without data fails', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + null, inputvalues.encrypt.good.keyid).then(function (answer) { + expect(answer).to.be.undefined; + }, function (error) { + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('MSG_INCOMPLETE'); + context.connection.disconnect(); + done(); + }); + }); + }); + + it('Sending encryption with non existing keys fails', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + inputvalues.encrypt.good.data, + inputvalues.encrypt.bad.fingerprint).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.not.be.undefined; + expect(error.code).to.equal('GNUPG_ERROR'); + context.connection.disconnect(); + done(); + }); + }); + }).timeout(5000);; + + it('Overly large message ( > 65MB) is rejected', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + fixedLengthString(65), + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an.instanceof(Error); + // expect(error.code).to.equal('GNUPG_ERROR'); + // TODO: there is a 64 MB hard limit at least in chrome at: + // chromium//extensions/renderer/messaging_util.cc: + // kMaxMessageLength + context.connection.disconnect(); + done(); + }); + }); + }).timeout(8000); + + // TODO check different valid parameter +}); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js new file mode 100644 index 00000000..52e3a7b0 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -0,0 +1,143 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +var inputvalues = { + encrypt: { + good:{ + data : 'Hello World.', + // Fingerprint of a key that has been imported to gnupg (i.e. see testkey.pub; testkey.sec) + fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05', + data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день', + + // used for checking encoding consistency in > 2MB messages. + data_nonascii_32: [ + 'K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€', + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€', + '€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€', + '²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³', + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€A€µ€µ€µ€µ€', + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µAµ€µ€µ€µ€', + 'üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü', + 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', + 'µAAAAµAAAAAAAAAAAAAAAAAAAAAAAAA€', + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAµ€', + 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA°', + '€AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', + 'µ||||||||||||||||||||||||||||||€', + 'æſæſ³¼„¬“³³¬“¬½”æſæſ³¼„¬“³³¬“¬½”' + ] + }, + bad: { + // valid Hex value, but not usable (not imported to gnupg, or bogus fingerprint) + fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' + } + }, + init: { + // some parameters + invalid_startups: [ + {all_passwords: true}, + 'openpgpmode', + {api_style:"frankenstein"} + ] + } +}; + +// (Pseudo-)Random String covering all of utf8. +function bigString(length){ + var uint = ''; + let arr = []; + for (let i= 0; i < length; i++){ + arr.push(String.fromCharCode( + Math.floor(Math.random() * 10174) + 1) + ); + } + return arr.join(''); +} + +function fixedLengthString(megabytes){ + let maxlength = 1024 * 1024 * megabytes / 2; + let uint = new Uint8Array(maxlength); + for (let i = 0; i < maxlength; i++){ + uint[i] = Math.floor(Math.random()* 256); + } + let td = new TextDecoder('ascii'); + let result = td.decode(uint); + return result; +} + +// (Pseudo-)Random Uint8Array, given size in Megabytes +function bigUint8(megabytes){ + let maxlength = 1024 * 1024 * megabytes; + let uint = new Uint8Array(maxlength); + for (let i= 0; i < maxlength; i++){ + uint[i] = Math.random() * Math.floor(256); + } + return uint; +} + +// (Pseudo-)Random string with very limited charset (ascii only, no control chars) +function bigBoringString(megabytes){ + let maxlength = 1024 * 1024 * megabytes; + let string = []; + let chars = ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + for (let i= 0; i < maxlength; i++){ + string.push(chars[Math.floor(Math.random() * chars.length)]); + } + return string.join(''); +} + +// Some String with simple chars, with different characteristics, but still +// expected to occur in an averag message +function slightlyLessBoringString(megabytes, set){ + let maxlength = 1024 * 1024 * megabytes; + let string = []; + let chars = ''; + if (set ===1 ) { + chars = '\n\"\r \''; + } else if (set === 2 ) { + chars = '()=?`#+-{}[]'; + } else if (set === 3){ + chars = '^°/'; + } else if (set ===4) { + chars = 'äüßµüþÖ~ɁÑ||@'; + } else { + chars = '*<>\n\"\r§$%&/()=?`#+-{}[] \''; + } + for (let i= 0; i < maxlength; i++){ + string.push(chars[Math.floor(Math.random() * chars.length)]); + } + return string.join(''); +} + +// Data encrypted with testKey +var encryptedData = + '-----BEGIN PGP MESSAGE-----\n' + + '\n' + + 'hQEMA6B8jfIUScGEAQgAlANd3uyhmhYLzVcfz4LEqA8tgUC3n719YH0iuKEzG/dv\n' + + 'B8fsIK2HoeQh2T3/Cc2LBMjgn4K33ksG3k2MqrbIvxWGUQlOAuggc259hquWtX9B\n' + + 'EcEoOAeh5DuZT/b8CM5seJKNEpPzNxbEDiGikp9DV9gfIQTTUnrDjAu5YtgCN9vA\n' + + '3PJxihioH8ODoQw2jlYSkqgXpBVP2Fbx7qgTuxGNu5w36E0/P93//4hDXcKou7ez\n' + + 'o0+NEGSkbaY+OPk1k7k9n+vBSC3F440dxsTNs5WmRvx9XZEotJkUBweE+8XaoLCn\n' + + '3RrtyD/lj63qi3dbyI5XFLuPU1baFskJ4UAmI4wNhdJ+ASailpnFBnNgiFBh3ZfB\n' + + 'G5Rmd3ocSL7l6lq1bVK9advXb7vcne502W1ldAfHgTdQgc2CueIDFUYAaXP2OvhP\n' + + 'isGL7jOlDCBKwep67ted0cTRPLWkk3NSuLIlvD5xs6L4z3rPu92gXYgbZoMMdP0N\n' + + 'kSAQYOHplfA7YJWkrlRm\n' + + '=zap6\n' + + '-----END PGP MESSAGE-----\n'; diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js new file mode 100644 index 00000000..4e55fd26 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -0,0 +1,40 @@ +describe('Long running Encryption/Decryption', function () { + for (let i=0; i < 100; i++) { + it('Successful encrypt/decrypt completely random data ' + (i+1) + '/100', function (done) { + let prm = Gpgmejs.init(); + let data = bigString(2*1024*1024); + prm.then(function (context) { + context.encrypt(data, + inputvalues.encrypt.good.fingerprint).then( + function (answer){ + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include( + 'BEGIN PGP MESSAGE'); + expect(answer.data).to.include( + 'END PGP MESSAGE'); + context.decrypt(answer.data).then( + function(result){ + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + if (result.data.length !== data.length) { + console.log('diff: ' + (result.data.length - data.length)); + for (let i=0; i < result.data.length; i++){ + if (result.data[i] !== data[i]){ + console.log('position: ' + i); + console.log('result : '+ result.data.charCodeAt(i) + result.data[i-2] + result.data[i-1] + result.data[i] + result.data[i+1] + result.data[i+2]); + console.log('original: ' + data.charCodeAt(i) + data[i-2] + data[i-1] + data[i] + data[i+1] + data[i+2]); + break; + } + } + } + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(8000); + }; + +}); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js new file mode 100644 index 00000000..5de70a6b --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -0,0 +1,54 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + + describe('GPGME context', function(){ + it('Starting a GpgME instance', function(done){ + let prm = Gpgmejs.init(); + prm.then( + function(context){ + expect(context.connection).to.not.be.undefined; + expect(context).to.be.an('object'); + expect(context.connection).to.be.an('object'); + expect(context.Keyring).to.be.undefined; + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }, function(errorr){ + expect(error).to.be.undefined; + done(); + }); + }); +}); + +describe('GPGME does not start with invalid parameters', function(){ + for (let i=0; i < inputvalues.init.invalid_startups.length; i++){ + it('Parameter '+ i, function(done){ + let prm = Gpgmejs.init(inputvalues.init.invalid_startups[i]); + prm.then(function(context){ + expect(context).to.be.undefined; + done(); + }, function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('PARAM_WRONG'); + done(); + }); + }) + } +});
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/unittests.html b/lang/js/BrowserTestExtension/unittests.html new file mode 100644 index 00000000..6f7da3f1 --- /dev/null +++ b/lang/js/BrowserTestExtension/unittests.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <link href="libs/mocha.css" rel="stylesheet" /> + + </head> +<body> + <h3>Unit tests</h3> + <div id="mocha"></div> + <script src="libs/mocha.js"></script> + <script src="libs/chai.js"></script> + <script src="setup_testing.js"></script> + <script src="libs/gpgmejs_unittests.bundle.js"></script> + <script src="rununittests.js"></script> + </body> +</html> diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST new file mode 100644 index 00000000..278f39dd --- /dev/null +++ b/lang/js/CHECKLIST @@ -0,0 +1,25 @@ +NativeConnection: + + [X] nativeConnection: successfully sending an encrypt request, +receiving an answer + [X] nativeConnection successfull on Chromium, chrome and firefox + [*] nativeConnection successfull on Windows, macOS, Linux + [X] nativeConnection with delayed, multipart (> 1MB) answer + + [*] Message handling (encrypt, decrypt verify, sign) + [x] encrypt, decrypt + [ ] verify + [ ] sign + [*] Key handling (import/export, modifying, status queries) + [*] Configuration handling + [ ] check for completeness + +Communication with other implementations + + [ ] option to export SECRET Key into localstore used by e.g. mailvelope + +Management: + [*] Define the gpgme interface + [x] check Permissions (e.g. csp) for the different envs + [X] agree on license + [*] tests diff --git a/lang/js/CHECKLIST_build b/lang/js/CHECKLIST_build new file mode 100644 index 00000000..a7c8d08d --- /dev/null +++ b/lang/js/CHECKLIST_build @@ -0,0 +1,3 @@ +- Checklist for build/install: + +browsers' manifests (see README) need allowedextension added, and the path set diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js new file mode 100644 index 00000000..62583421 --- /dev/null +++ b/lang/js/DemoExtension/entry.js @@ -0,0 +1,25 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + * + */ +document.addEventListener('DOMContentLoaded', function() { + chrome.tabs.create({ + url: './mainui.html' + }); +}); diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js new file mode 100644 index 00000000..b2cb4c23 --- /dev/null +++ b/lang/js/DemoExtension/maindemo.js @@ -0,0 +1,55 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + * + */ + +document.addEventListener('DOMContentLoaded', function() { + Gpgmejs.init().then(function(gpgmejs){ + document.getElementById("buttonencrypt").addEventListener("click", + function(){ + let data = document.getElementById('cleartext').value; + let keyId = document.getElementById('pubkey').value; + gpgmejs.encrypt(data, keyId).then( + function(answer){ + console.log(answer); + if (answer.data){ + console.log(answer.data); + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.code + ' ' + errormsg.msg); + }); + }); + + document.getElementById("buttondecrypt").addEventListener("click", + function(){ + let data = document.getElementById("ciphertext").value; + gpgmejs.decrypt(data).then( + function(answer){ + console.log(answer); + if (answer.data){ + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.code + ' ' + errormsg.msg); + }); + }); + }, + function(error){console.log(error)}); +}); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html new file mode 100644 index 00000000..76b8a221 --- /dev/null +++ b/lang/js/DemoExtension/mainui.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <link rel="stylesheet" href="ui.css"/> + <script src="libs/gpgmejs.bundle.js"></script> + <script src="maindemo.js"></script> + </head> + <body> + <ul> + <li> + <span class="label">Text: </span> + <input type="text" id='cleartext' /> + </li> + <li> + <span class="label">Public key ID: </span> + <input type="text" id="pubkey" value="" /> + </li> + </ul> + <button id="buttonencrypt">Encrypt</button><br> + <hr> + <ul> + <li> + <span class="label">Encrypted armored Text: </span> + <textarea rows="5" cols="65" id="ciphertext" wrap="hard"></textarea> + </li> + </ul> + <button id="buttondecrypt">Decrypt</button><br> + <hr> + <h3>Result data:</h3> + <textarea id="answer" rows="5" cols="65" wrap="hard"></textarea> + </body> +</html> diff --git a/lang/js/DemoExtension/manifest.json b/lang/js/DemoExtension/manifest.json new file mode 100644 index 00000000..9e057b35 --- /dev/null +++ b/lang/js/DemoExtension/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 2, + + "name": "gpgme-json with native Messaging", + "description": "A simple demo application", + "version": "0.1", + "content_security_policy": "default-src 'self' filesystem:", + "browser_action": { + "default_icon": "testicon.png", + "default_title": "gpgme.js", + "default_popup": "popup.html" + }, + "permissions": ["nativeMessaging", "activeTab"] +} diff --git a/lang/js/DemoExtension/popup.html b/lang/js/DemoExtension/popup.html new file mode 100644 index 00000000..50070311 --- /dev/null +++ b/lang/js/DemoExtension/popup.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <script src="entry.js"></script> + </head> + <body> + </body> +</html>
\ No newline at end of file diff --git a/lang/js/DemoExtension/testicon.png b/lang/js/DemoExtension/testicon.png Binary files differnew file mode 100644 index 00000000..12c3f5df --- /dev/null +++ b/lang/js/DemoExtension/testicon.png diff --git a/lang/js/DemoExtension/ui.css b/lang/js/DemoExtension/ui.css new file mode 100644 index 00000000..9c88698b --- /dev/null +++ b/lang/js/DemoExtension/ui.css @@ -0,0 +1,10 @@ +ul { + list-style-type: none; + padding-left: 0px; +} + +ul li span { + float: left; + width: 120px; + margin-top: 6px; +} diff --git a/lang/js/README b/lang/js/README new file mode 100644 index 00000000..b597adb2 --- /dev/null +++ b/lang/js/README @@ -0,0 +1,52 @@ +gpgmejs, as contained in this directory, is a javascript library for direct use +of gnupg in browsers, with the help of nativeMessaging. + +Installation +------------- +gpgmejs uses webpack, and thus depends on nodejs for building. Webpack can be +installed by running +`npm install webpack webpack-cli --save-dev`. + +To create a current version of the package, the command is +`npx webpack --config webpack.conf.js`. +If you want a more debuggable (i.e. not minified) build, just change the mode +in webpack.conf.js. + +Demo WebExtension: +As soon as a bundled webpack is in dist/ +the gpgmejs folder can just be included in the extensions tab of the browser in +questions (extension debug mode needs to be active). For chrome, selecting the +folder is sufficient, for firefox, the manifest.json needs to be selected. +Please note that it is just for demonstration/debug purposes! + +In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json' +is needed, with the following content: + +(The path to the native app gpgme-json may need adaption) + +Chromium: +~/.config/chromium/NativeMessagingHosts/gpgmejson.json + +{ + "name": "gpgmejson", + "description": "This is a test application for gpgmejs", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_origins": ["chrome-extension://ExtensionIdentifier/"] +} +The ExtensionIdentifier can be seen on the chrome://extensions page, and +changes on each reinstallation. Note the slashes in allowed_origins. + + +Firefox: +~/.mozilla/native-messaging-hosts/gpgmejson.json +{ + "name": "gpgmejson", + "description": "This is a test application for gpgmejs", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_extensions": ["ExtensionIdentifier@temporary-addon"] +} +The ExtensionIdentifier can be seen as Extension ID on the about:addons page if +addon-debugging is active. In firefox, the temporary addon is removed once +firefox exits, and the identifier will need to be changed more often. diff --git a/lang/js/README_testing b/lang/js/README_testing new file mode 100644 index 00000000..b61ca1a6 --- /dev/null +++ b/lang/js/README_testing @@ -0,0 +1,14 @@ +Test extension: + +The test extension contains tests with mocha and chai. It will be packed as an +extra extension (see build_extension.sh). + +Tests from BrowserTestExtension/tests will be run against the gpgmejs.bundle.js +itself. They aim to test the outward facing functionality and API. + +Unittests as defined in ./unittests.js will be bundled in +gpgmejs_unittests.bundle.js, and test the separate components of gpgmejs, +which mostly are not exported. + +The BrowserExtension can be installed the same way as the DemoExtension +(see README).
\ No newline at end of file diff --git a/lang/js/build_extensions.sh b/lang/js/build_extensions.sh new file mode 100755 index 00000000..91d5479b --- /dev/null +++ b/lang/js/build_extensions.sh @@ -0,0 +1,17 @@ +#/!bin/bash + +npx webpack --config webpack.conf.js +npx webpack --config webpack.conf_unittests.js +mkdir -p BrowserTestExtension/libs +cp node_modules/chai/chai.js \ + node_modules/mocha/mocha.css \ + node_modules/mocha/mocha.js \ + build/gpgmejs.bundle.js \ + build/gpgmejs_unittests.bundle.js BrowserTestExtension/libs +rm -rf build/extensions +mkdir -p build/extensions +zip -r build/extensions/browsertest.zip BrowserTestExtension + +mkdir -p DemoExtension/libs +cp build/gpgmejs.bundle.js DemoExtension/libs +zip -r build/extensions/demoextension.zip DemoExtension diff --git a/lang/js/package.json b/lang/js/package.json new file mode 100644 index 00000000..be52a554 --- /dev/null +++ b/lang/js/package.json @@ -0,0 +1,16 @@ +{ + "name": "gpgmejs", + "version": "0.0.1", + "description": "javascript part of a nativeMessaging gnupg integration", + "main": "src/index.js", + "private": true, + "keywords": [], + "author": "", + "license": "", + "devDependencies": { + "webpack": "^4.5.0", + "webpack-cli": "^2.0.14", + "chai": "^4.1.2", + "mocha": "^5.1.1" + } +} diff --git a/lang/js/src/Config.js b/lang/js/src/Config.js new file mode 100644 index 00000000..e85bbb82 --- /dev/null +++ b/lang/js/src/Config.js @@ -0,0 +1,31 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +export const availableConf = { + null_expire_is_never: [true, false], + // cachedKeys: Some Key info will not be queried on each invocation, + // manual refresh by Key.refresh() + cachedKeys: [true, false] +}; + +export const defaultConf = { + null_expire_is_never: false, + cachedKeys: false +};
\ No newline at end of file diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js new file mode 100644 index 00000000..9c2a6428 --- /dev/null +++ b/lang/js/src/Connection.js @@ -0,0 +1,241 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +/** + * A connection port will be opened for each communication between gpgmejs and + * gnupg. It should be alive as long as there are additional messages to be + * expected. + */ +import { permittedOperations } from './permittedOperations' +import { gpgme_error } from "./Errors" +import { GPGME_Message } from "./Message"; + +/** + * A Connection handles the nativeMessaging interaction. + */ +export class Connection{ + + constructor(){ + this.connect(); + let me = this; + } + + /** + * (Simple) Connection check. + * @returns {Boolean} true if the onDisconnect event has not been fired. + * Please note that the event listener of the port takes some time + * (5 ms seems enough) to react after the port is created. Then this will + * return undefined + */ + get isConnected(){ + return this._isConnected; + } + + /** + * Immediately closes the open port. + */ + disconnect() { + if (this._connection){ + this._connection.disconnect(); + } + } + + /** + * Opens a nativeMessaging port. + */ + connect(){ + if (this._isConnected === true){ + gpgme_error('CONN_ALREADY_CONNECTED'); + } else { + this._isConnected = true; + this._connection = chrome.runtime.connectNative('gpgmejson'); + let me = this; + this._connection.onDisconnect.addListener( + function(){ + me._isConnected = false; + } + ); + } + } + + /** + * Sends a message and resolves with the answer. + * @param {GPGME_Message} message + * @returns {Promise<Object>} the gnupg answer, or rejection with error + * information. + */ + post(message){ + if (!this.isConnected){ + return Promise.reject(gpgme_error('CONN_DISCONNECTED')); + } + if (!message || !message instanceof GPGME_Message){ + return Promise.reject(gpgme_error('PARAM_WRONG'), message); + } + if (message.isComplete !== true){ + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + let me = this; + return new Promise(function(resolve, reject){ + let answer = new Answer(message); + let listener = function(msg) { + if (!msg){ + me._connection.onMessage.removeListener(listener) + reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); + } else if (msg.type === "error"){ + me._connection.onMessage.removeListener(listener); + reject(gpgme_error('GNUPG_ERROR', msg.msg)); + } else { + let answer_result = answer.add(msg); + if (answer_result !== true){ + me._connection.onMessage.removeListener(listener); + reject(answer_result); + } + if (msg.more === true){ + me._connection.postMessage({'op': 'getmore'}); + } else { + me._connection.onMessage.removeListener(listener) + resolve(answer.message); + } + } + }; + + me._connection.onMessage.addListener(listener); + if (permittedOperations[message.operation].pinentry){ + return me._connection.postMessage(message.message); + } else { + return Promise.race([ + me._connection.postMessage(message.message), + function(resolve, reject){ + setTimeout(function(){ + reject(gpgme_error('CONN_TIMEOUT')); + }, 5000); + }]).then(function(result){ + return result; + }, function(reject){ + if(!reject instanceof Error) { + return gpgme_error('GNUPG_ERROR', reject); + } else { + return reject; + } + }); + } + }); + } +}; + +/** + * A class for answer objects, checking and processing the return messages of + * the nativeMessaging communication. + * @param {String} operation The operation, to look up validity of returning messages + */ +class Answer{ + + constructor(message){ + this.operation = message.operation; + this.expected = message.expected; + } + + /** + * Add the information to the answer + * @param {Object} msg The message as received with nativeMessaging + * returns true if successfull, gpgme_error otherwise + */ + add(msg){ + if (this._response === undefined){ + this._response = {}; + } + let messageKeys = Object.keys(msg); + let poa = permittedOperations[this.operation].answer; + if (messageKeys.length === 0){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + for (let i= 0; i < messageKeys.length; i++){ + let key = messageKeys[i]; + switch (key) { + case 'type': + if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + break; + case 'more': + break; + default: + //data should be concatenated + if (poa.data.indexOf(key) >= 0){ + if (!this._response.hasOwnProperty(key)){ + this._response[key] = ''; + } + this._response[key] += msg[key]; + } + //params should not change through the message + else if (poa.params.indexOf(key) >= 0){ + if (!this._response.hasOwnProperty(key)){ + this._response[key] = msg[key]; + } + else if (this._response[key] !== msg[key]){ + return gpgme_error('CONN_UNEXPECTED_ANSWER',msg[key]); + } + } + //infos may be json objects etc. Not yet defined. + // Pushing them into arrays for now + else if (poa.infos.indexOf(key) >= 0){ + if (!this._response.hasOwnProperty(key)){ + this._response[key] = []; + } + this._response.push(msg[key]); + } + else { + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + break; + } + } + return true; + } + + /** + * @returns {Object} the assembled message, original data assumed to be + * (javascript-) strings + */ + get message(){ + let keys = Object.keys(this._response); + let msg = {}; + let poa = permittedOperations[this.operation].answer; + for (let i=0; i < keys.length; i++) { + if (poa.data.indexOf(keys[i]) >= 0 + && this._response.base64 === true + ) { + msg[keys[i]] = atob(this._response[keys[i]]); + if (this.expected === 'base64'){ + msg[keys[i]] = this._response[keys[i]]; + } else { + msg[keys[i]] = decodeURIComponent( + atob(this._response[keys[i]]).split('').map(function(c) { + return '%' + + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + } + } else { + msg[keys[i]] = this._response[keys[i]]; + } + } + return msg; + } +} diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js new file mode 100644 index 00000000..bfe3a2f4 --- /dev/null +++ b/lang/js/src/Errors.js @@ -0,0 +1,129 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +const err_list = { + // Connection + 'CONN_NO_CONNECT': { + msg:'Connection with the nativeMessaging host could not be' + + ' established.', + type: 'error' + }, + 'CONN_DISCONNECTED': { + msg:'Connection with the nativeMessaging host was lost.', + type: 'error' + }, + 'CONN_EMPTY_GPG_ANSWER':{ + msg: 'The nativeMessaging answer was empty.', + type: 'error' + }, + 'CONN_TIMEOUT': { + msg: 'A connection timeout was exceeded.', + type: 'error' + }, + 'CONN_UNEXPECTED_ANSWER': { + msg: 'The answer from gnupg was not as expected.', + type: 'error' + }, + 'CONN_ALREADY_CONNECTED':{ + msg: 'A connection was already established.', + type: 'warning' + }, + // Message/Data + 'MSG_INCOMPLETE': { + msg: 'The Message did not match the minimum requirements for' + + ' the interaction.', + type: 'error' + }, + 'MSG_EMPTY' : { + msg: 'The Message is empty.', + type: 'error' + }, + 'MSG_WRONG_OP': { + msg: 'The operation requested could not be found', + type: 'error' + }, + 'MSG_NO_KEYS' : { + msg: 'There were no valid keys provided.', + type: 'warning' + }, + 'MSG_NOT_A_FPR': { + msg: 'The String is not an accepted fingerprint', + type: 'warning' + }, + 'KEY_INVALID': { + msg:'Key object is invalid', + type: 'error' + }, + // generic + 'PARAM_WRONG':{ + msg: 'Invalid parameter was found', + type: 'error' + }, + 'PARAM_IGNORED': { + msg: 'An parameter was set that has no effect in gpgmejs', + type: 'warning' + }, + 'GENERIC_ERROR': { + msg: 'Unspecified error', + type: 'error' + } +}; + +/** + * Checks the given error code and returns an error object with some + * information about meaning and origin + * @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR' + * @param {*} info Error message passed through if code is 'GNUPG_ERROR' + */ +export function gpgme_error(code = 'GENERIC_ERROR', info){ + if (err_list.hasOwnProperty(code)){ + if (err_list[code].type === 'error'){ + return new GPGME_Error(code); + } + if (err_list[code].type === 'warning'){ + console.warn(code + ': ' + err_list[code].msg); + } + return null; + } else if (code === 'GNUPG_ERROR'){ + return new GPGME_Error(code, info); + } + else { + return new GPGME_Error('GENERIC_ERROR'); + } +} + +class GPGME_Error extends Error{ + constructor(code, msg=''){ + if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ + super(msg); + } else if (err_list.hasOwnProperty(code)){ + super(err_list[code].msg); + } else { + super(err_list['GENERIC_ERROR'].msg); + } + this.code = code || 'GENERIC_ERROR'; + } + set code(value){ + this._code = value; + } + get code(){ + return this._code; + } +}
\ No newline at end of file diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js new file mode 100644 index 00000000..fd0e7200 --- /dev/null +++ b/lang/js/src/Helpers.js @@ -0,0 +1,103 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +import { gpgme_error } from "./Errors"; +import { GPGME_Key } from "./Key"; + +/** + * Tries to return an array of fingerprints, either from input fingerprints or + * from Key objects + * @param {Key |Array<Key>| GPGME_Key | Array<GPGME_Key>|String|Array<String>} input + * @returns {Array<String>} Array of fingerprints. + */ + +export function toKeyIdArray(input){ + if (!input){ + gpgme_error('MSG_NO_KEYS'); + return []; + } + if (!Array.isArray(input)){ + input = [input]; + } + let result = []; + for (let i=0; i < input.length; i++){ + if (typeof(input[i]) === 'string'){ + if (isFingerprint(input[i]) === true){ + result.push(input[i]); + } else { + gpgme_error('MSG_NOT_A_FPR'); + } + } else if (typeof(input[i]) === 'object'){ + let fpr = ''; + if (input[i] instanceof GPGME_Key){ + fpr = input[i].fingerprint; + } else if (input[i].hasOwnProperty('primaryKey') && + input[i].primaryKey.hasOwnProperty('getFingerprint')){ + fpr = input[i].primaryKey.getFingerprint(); + } + if (isFingerprint(fpr) === true){ + result.push(fpr); + } else { + gpgme_error('MSG_NOT_A_FPR'); + } + } else { + return gpgme_error('PARAM_WRONG'); + } + } + if (result.length === 0){ + gpgme_error('MSG_NO_KEYS'); + return []; + } else { + return result; + } +}; + +/** + * check if values are valid hexadecimal values of a specified length + * @param {*} key input value. + * @param {int} len the expected length of the value + */ +function hextest(key, len){ + if (!key || typeof(key) !== "string"){ + return false; + } + if (key.length !== len){ + return false; + } + let regexp= /^[0-9a-fA-F]*$/i; + return regexp.test(key); +}; + +/** + * check if the input is a valid Hex string with a length of 40 + */ +export function isFingerprint(string){ + return hextest(string, 40); +}; +/** + * TODO no usage; check if the input is a valid Hex string with a length of 16 + */ +function isLongId(string){ + return hextest(string, 16); +}; + +// TODO still not needed anywhere +function isShortId(string){ + return hextest(string, 8); +}; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js new file mode 100644 index 00000000..075a190e --- /dev/null +++ b/lang/js/src/Key.js @@ -0,0 +1,244 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +/** + * The key class allows to query the information defined in gpgme Key Objects + * (see https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) + * + * This is a stub, as the gpgme-json side is not yet implemented + * + */ + +import { isFingerprint } from './Helpers' +import { gpgme_error } from './Errors' +import { createMessage } from './Message'; +import { permittedOperations } from './permittedOperations'; +import { Connection } from './Connection'; + + +export function createKey(fingerprint, parent){ + if (!isFingerprint(fingerprint)){ + return gpgme_error('PARAM_WRONG'); + } + if ( parent instanceof Connection){ + return new GPGME_Key(fingerprint, parent); + } else if ( parent.hasOwnProperty('connection') && + parent.connection instanceof Connection){ + return new GPGME_Key(fingerprint, parent.connection); + } else { + return gpgme_error('PARAM_WRONG'); + } +} + +export class GPGME_Key { + + constructor(fingerprint, connection){ + this.fingerprint = fingerprint; + this.connection = connection; + } + + set connection(conn){ + if (this._connection instanceof Connection) { + gpgme_error('CONN_ALREADY_CONNECTED'); + } else if (conn instanceof Connection ) { + this._connection = conn; + } + } + + get connection(){ + if (!this._fingerprint){ + return gpgme_error('KEY_INVALID'); + } + if (!this._connection instanceof Connection){ + return gpgme_error('CONN_NO_CONNECT'); + } else { + return this._connection; + } + } + + set fingerprint(fpr){ + if (isFingerprint(fpr) === true && !this._fingerprint){ + this._fingerprint = fpr; + } + } + + get fingerprint(){ + if (!this._fingerprint){ + return gpgme_error('KEY_INVALID'); + } + return this._fingerprint; + } + + /** + * hasSecret returns true if a secret subkey is included in this Key + */ + get hasSecret(){ + return this.checkKey('secret'); + } + + get isRevoked(){ + return this.checkKey('revoked'); + } + + get isExpired(){ + return this.checkKey('expired'); + } + + get isDisabled(){ + return this.checkKey('disabled'); + } + + get isInvalid(){ + return this.checkKey('invalid'); + } + + get canEncrypt(){ + return this.checkKey('can_encrypt'); + } + + get canSign(){ + return this.checkKey('can_sign'); + } + + get canCertify(){ + return this.checkKey('can_certify'); + } + + get canAuthenticate(){ + return this.checkKey('can_authenticate'); + } + + get isQualified(){ + return this.checkKey('is_qualified'); + } + + get armored(){ + let msg = createMessage ('export_key'); + msg.setParameter('armor', true); + if (msg instanceof Error){ + return gpgme_error('KEY_INVALID'); + } + this.connection.post(msg).then(function(result){ + return result.data; + }); + // TODO return value not yet checked. Should result in an armored block + // in correct encoding + } + + /** + * TODO returns true if this is the default key used to sign + */ + get isDefault(){ + throw('NOT_YET_IMPLEMENTED'); + } + + /** + * get the Key's subkeys as GPGME_Key objects + * @returns {Array<GPGME_Key>} + */ + get subkeys(){ + return this.checkKey('subkeys').then(function(result){ + // TBD expecting a list of fingerprints + if (!Array.isArray(result)){ + result = [result]; + } + let resultset = []; + for (let i=0; i < result.length; i++){ + let subkey = new GPGME_Key(result[i], this.connection); + if (subkey instanceof GPGME_Key){ + resultset.push(subkey); + } + } + return Promise.resolve(resultset); + }, function(error){ + //TODO this.checkKey fails + }); + } + + /** + * creation time stamp of the key + * @returns {Date|null} TBD + */ + get timestamp(){ + return this.checkKey('timestamp'); + //TODO GPGME: -1 if the timestamp is invalid, and 0 if it is not available. + } + + /** + * The expiration timestamp of this key TBD + * @returns {Date|null} TBD + */ + get expires(){ + return this.checkKey('expires'); + // TODO convert to Date; check for 0 + } + + /** + * getter name TBD + * @returns {String|Array<String>} The user ids associated with this key + */ + get userIds(){ + return this.checkKey('uids'); + } + + /** + * @returns {String} The public key algorithm supported by this subkey + */ + get pubkey_algo(){ + return this.checkKey('pubkey_algo'); + } + + /** + * generic function to query gnupg information on a key. + * @param {*} property The gpgme-json property to check. + * TODO: check if Promise.then(return) + */ + checkKey(property){ + if (!this._fingerprint){ + return gpgme_error('KEY_INVALID'); + } + return gpgme_error('NOT_YET_IMPLEMENTED'); + // TODO: async is not what is to be ecpected from Key information :( + if (!property || typeof(property) !== 'string' || + !permittedOperations['keyinfo'].hasOwnProperty(property)){ + return gpgme_error('PARAM_WRONG'); + } + let msg = createMessage ('keyinfo'); + if (msg instanceof Error){ + return gpgme_error('PARAM_WRONG'); + } + msg.setParameter('fingerprint', this.fingerprint); + this.connection.post(msg).then(function(result, error){ + if (error){ + return gpgme_error('GNUPG_ERROR',error.msg); + } else if (result.hasOwnProperty(property)){ + return result[property]; + } + else if (property == 'secret'){ + // TBD property undefined means "not true" in case of secret? + return false; + } else { + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + }, function(error){ + return gpgme_error('GENERIC_ERROR'); + }); + } +};
\ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js new file mode 100644 index 00000000..4596035a --- /dev/null +++ b/lang/js/src/Keyring.js @@ -0,0 +1,162 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +import {createMessage} from './Message' +import {GPGME_Key} from './Key' +import { isFingerprint } from './Helpers'; +import { gpgme_error } from './Errors'; +import { Connection } from './Connection'; + +export class GPGME_Keyring { + constructor(connection){ + this.connection = connection; + } + + set connection(connection){ + if (!this._connection && connection instanceof Connection){ + this._connection = connection; + } + } + get connection(){ + if (this._connection instanceof Connection){ + if (this._connection.isConnected){ + return this._connection; + } + return gpgme_error('CONN_DISCONNECTED'); + } + return gpgme_error('CONN_NO_CONNECT'); + } + + /** + * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds + * @param {Boolean} (optional) Include listing of secret keys + * @returns {Promise.<Array<GPGME_Key>>} + * + */ + getKeys(pattern, include_secret){ + let msg = createMessage('listkeys'); + if (msg instanceof Error){ + return Promise.reject(msg); + } + if (pattern && typeof(pattern) === 'string'){ + msg.setParameter('pattern', pattern); + } + if (include_secret){ + msg.setParameter('with-secret', true); + } + let me = this; + + this.connection.post(msg).then(function(result){ + let fpr_list = []; + let resultset = []; + if (!Array.isArray(result.keys)){ + //TODO check assumption keys = Array<String fingerprints> + fpr_list = [result.keys]; + } else { + fpr_list = result.keys; + } + for (let i=0; i < fpr_list.length; i++){ + let newKey = new GPGME_Key(fpr_list[i], me._connection); + if (newKey instanceof GPGME_Key){ + resultset.push(newKey); + } + } + return Promise.resolve(resultset); + }, function(error){ + //TODO error handling + }); + } + + /** + * @param {Object} flags subset filter expecting at least one of the + * filters described below. True will filter on the condition, False will + * reverse the filter, if not present or undefined, the filter will not be + * considered. Please note that some combination may not make sense + * @param {Boolean} flags.secret Only Keys containing a secret part. + * @param {Boolean} flags.revoked revoked Keys only + * @param {Boolean} flags.expired Expired Keys only + * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds + * @returns {Promise Array<GPGME_Key>} + * + */ + getSubset(flags, pattern){ + if (flags === undefined) { + throw('ERR_WRONG_PARAM'); + }; + let secretflag = false; + if (flags.hasOwnProperty(secret) && flags.secret){ + secretflag = true; + } + this.getKeys(pattern, secretflag).then(function(queryset){ + let resultset = []; + for (let i=0; i < queryset.length; i++ ){ + let conditions = []; + let anticonditions = []; + if (secretflag === true){ + conditions.push('hasSecret'); + } else if (secretflag === false){ + anticonditions.push('hasSecret'); + } + /** + if (flags.defaultKey === true){ + conditions.push('isDefault'); + } else if (flags.defaultKey === false){ + anticonditions.push('isDefault'); + } + */ + /** + * if (flags.valid === true){ + anticonditions.push('isInvalid'); + } else if (flags.valid === false){ + conditions.push('isInvalid'); + } + */ + if (flags.revoked === true){ + conditions.push('isRevoked'); + } else if (flags.revoked === false){ + anticonditions.push('isRevoked'); + } + if (flags.expired === true){ + conditions.push('isExpired'); + } else if (flags.expired === false){ + anticonditions.push('isExpired'); + } + let decision = undefined; + for (let con = 0; con < conditions.length; con ++){ + if (queryset[i][conditions[con]] !== true){ + decision = false; + } + } + for (let acon = 0; acon < anticonditions.length; acon ++){ + if (queryset[i][anticonditions[acon]] === true){ + decision = false; + } + } + if (decision !== false){ + resultset.push(queryset[i]); + } + } + return Promise.resolve(resultset); + }, function(error){ + //TODO error handling + }); + } + +}; diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js new file mode 100644 index 00000000..932212a6 --- /dev/null +++ b/lang/js/src/Message.js @@ -0,0 +1,196 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +import { permittedOperations } from './permittedOperations' +import { gpgme_error } from './Errors' + +export function createMessage(operation){ + if (typeof(operation) !== 'string'){ + return gpgme_error('PARAM_WRONG'); + } + if (permittedOperations.hasOwnProperty(operation)){ + return new GPGME_Message(operation); + } else { + return gpgme_error('MSG_WRONG_OP'); + } +} + +/** + * Prepares a communication request. It checks operations and parameters in + * ./permittedOperations. + * @param {String} operation + */ +export class GPGME_Message { + //TODO getter + + constructor(operation){ + this.operation = operation; + this._expected = 'string'; + } + + set operation (op){ + if (typeof(op) === "string"){ + if (!this._msg){ + this._msg = {}; + } + if (!this._msg.op & permittedOperations.hasOwnProperty(op)){ + this._msg.op = op; + } + } + } + + get operation(){ + return this._msg.op; + } + + set expected(string){ + if (string === 'base64'){ + this._expected = 'base64'; + } + } + + get expected() { + if (this._expected === "base64"){ + return this._expected; + } + return "string"; + } + + /** + * Sets a parameter for the message. Note that the operation has to be set + * first, to be able to check if the parameter is permittted + * @param {String} param Parameter to set + * @param {any} value Value to set //TODO: Some type checking + * @returns {Boolean} If the parameter was set successfully + */ + setParameter(param,value){ + if (!param || typeof(param) !== 'string'){ + return gpgme_error('PARAM_WRONG'); + } + let po = permittedOperations[this._msg.op]; + if (!po){ + return gpgme_error('MSG_WRONG_OP'); + } + let poparam = null; + if (po.required.hasOwnProperty(param)){ + poparam = po.required[param]; + } else if (po.optional.hasOwnProperty(param)){ + poparam = po.optional[param]; + } else { + return gpgme_error('PARAM_WRONG'); + } + let checktype = function(val){ + switch(typeof(val)){ + case 'string': + if (poparam.allowed.indexOf(typeof(val)) >= 0 + && val.length > 0) { + return true; + } + return gpgme_error('PARAM_WRONG'); + break; + case 'number': + if ( + poparam.allowed.indexOf('number') >= 0 + && isNaN(value) === false){ + return true; + } + return gpgme_error('PARAM_WRONG'); + break; + case 'boolean': + if (poparam.allowed.indexOf('boolean') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + break; + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); + } + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + if (val.length > 0) { + return true; + } + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + } else { + return gpgme_error('PARAM_WRONG'); + } + break; + default: + return gpgme_error('PARAM_WRONG'); + } + }; + let typechecked = checktype(value); + if (typechecked !== true){ + return typechecked; + } + if (poparam.hasOwnProperty('allowed_data')){ + if (poparam.allowed_data.indexOf(value) < 0){ + return gpgme_error('PARAM_WRONG'); + } + } + this._msg[param] = value; + return true; + } + + /** + * Check if the message has the minimum requirements to be sent, according + * to the definitions in permittedOperations + * @returns {Boolean} + */ + get isComplete(){ + if (!this._msg.op){ + return false; + } + let reqParams = Object.keys( + permittedOperations[this._msg.op].required); + let msg_params = Object.keys(this._msg); + for (let i=0; i < reqParams.length; i++){ + if (msg_params.indexOf(reqParams[i]) < 0){ + console.log(reqParams[i] + ' missing'); + return false; + } + } + return true; + } + + /** + * Returns the prepared message with parameters and completeness checked + * @returns {Object|null} Object to be posted to gnupg, or null if + * incomplete + */ + get message(){ + if (this.isComplete === true){ + return this._msg; + } + else { + return null; + } + + } +} diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js new file mode 100644 index 00000000..3aa5957a --- /dev/null +++ b/lang/js/src/gpgmejs.js @@ -0,0 +1,192 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +import {Connection} from "./Connection" +import {GPGME_Message, createMessage} from './Message' +import {toKeyIdArray} from "./Helpers" +import { gpgme_error } from "./Errors" +import { GPGME_Keyring } from "./Keyring"; + +export class GpgME { + /** + * initializes GpgME by opening a nativeMessaging port + * TODO: add configuration + */ + constructor(connection){ + this.connection = connection; + } + + set connection(conn){ + if (this._connection instanceof Connection){ + gpgme_error('CONN_ALREADY_CONNECTED'); + } else if (conn instanceof Connection){ + this._connection = conn; + } else { + gpgme_error('PARAM_WRONG'); + } + } + + get connection(){ + if (this._connection){ + if (this._connection.isConnected === true){ + return this._connection; + } + return undefined; + } + return undefined; + } + + set Keyring(keyring){ + if (keyring && keyring instanceof GPGME_Keyring){ + this._Keyring = keyring; + } + } + + get Keyring(){ + return this._Keyring; + } + + /** + * @param {String} data text/data to be encrypted as String + * @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} publicKeys Keys used to encrypt the message + * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message + */ + encrypt(data, publicKeys, base64=false, wildcard=false){ + + let msg = createMessage('encrypt'); + if (msg instanceof Error){ + return Promise.reject(msg) + } + // TODO temporary + msg.setParameter('armor', true); + msg.setParameter('always-trust', true); + if (base64 === true) { + msg.setParameter('base64', true); + } + let pubkeys = toKeyIdArray(publicKeys); + msg.setParameter('keys', pubkeys); + putData(msg, data); + if (wildcard === true){ + msg.setParameter('throw-keyids', true); + }; + if (msg.isComplete === true){ + return this.connection.post(msg); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + } + + /** + * @param {String} data TODO base64? Message with the encrypted data + * @param {Boolean} base64 (optional) Response should stay base64 + * @returns {Promise<Object>} decrypted message: + data: The decrypted data. This may be base64 encoded. + base64: Boolean indicating whether data is base64 encoded. + mime: A Boolean indicating whether the data is a MIME object. + info: An optional object with extra information. + * @async + */ + + decrypt(data, base64=false){ + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let msg = createMessage('decrypt'); + if (base64 === true){ + msg.expected = 'base64'; + } + if (msg instanceof Error){ + return Promise.reject(msg); + } + putData(msg, data); + return this.connection.post(msg); + + } + + deleteKey(key, delete_secret = false, no_confirm = false){ + return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); + let msg = createMessage('deletekey'); + if (msg instanceof Error){ + return Promise.reject(msg); + } + let key_arr = toKeyIdArray(key); + if (key_arr.length !== 1){ + return Promise.reject( + gpgme_error('GENERIC_ERROR')); + // TBD should always be ONE key? + } + msg.setParameter('key', key_arr[0]); + if (delete_secret === true){ + msg.setParameter('allow_secret', true); + // TBD + } + if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? + msg.setParameter('delete_force', true); + // TBD + } + if (msg.isComplete === true){ + this.connection.post(msg).then(function(success){ + // TODO: it seems that there is always errors coming back: + }, function(error){ + switch (error.msg){ + case 'ERR_NO_ERROR': + return Promise.resolve('okay'); //TBD + default: + return Promise.reject(gpgme_error('TODO') ); // + // INV_VALUE, + // GPG_ERR_NO_PUBKEY, + // GPG_ERR_AMBIGUOUS_NAME, + // GPG_ERR_CONFLICT + } + }); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + } +} + +/** + * Sets the data of the message + * @param {GPGME_Message} message The message where this data will be set + * @param {*} data The data to enter + */ +function putData(message, data){ + if (!message || !message instanceof GPGME_Message ) { + return gpgme_error('PARAM_WRONG'); + } + if (!data){ + return gpgme_error('PARAM_WRONG'); + } else if (typeof(data) === 'string') { + message.setParameter('data', data); + } else if ( + typeof(data) === 'object' && + typeof(data.getText) === 'function' + ){ + let txt = data.getText(); + if (typeof(txt) === 'string'){ + message.setParameter('data', txt); + } else { + return gpgme_error('PARAM_WRONG'); + } + + } else { + return gpgme_error('PARAM_WRONG'); + } +} diff --git a/lang/js/src/index.js b/lang/js/src/index.js new file mode 100644 index 00000000..8527b3f3 --- /dev/null +++ b/lang/js/src/index.js @@ -0,0 +1,86 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + +import { GpgME } from "./gpgmejs"; +import { gpgme_error } from "./Errors"; +import { Connection } from "./Connection"; +import { defaultConf, availableConf } from "./Config"; + +/** + * Initializes a nativeMessaging Connection and returns a GPGMEjs object + * @param {Object} config Configuration. See Config.js for available parameters. Still TODO + */ +function init(config){ + let _conf = parseconfiguration(config); + if (_conf instanceof Error){ + return Promise.reject(_conf); + } + return new Promise(function(resolve, reject){ + let connection = new Connection; + // TODO: Delayed reaction is ugly. We need to listen to the port's + // event listener in isConnected, but in some cases this takes some + // time (<5ms) to disconnect if there is no successfull connection. + let delayedreaction = function(){ + if (connection === undefined) { + reject(gpgme_error('CONN_NO_CONNECT')); + } + if (connection.isConnected === true){ + resolve(new GpgME(connection, _conf)); + } else { + reject(gpgme_error('CONN_NO_CONNECT')); + } + }; + setTimeout(delayedreaction, 5); + }); +} + +function parseconfiguration(rawconfig = {}){ + if ( typeof(rawconfig) !== 'object'){ + return gpgme_error('PARAM_WRONG'); + }; + let result_config = {}; + let conf_keys = Object.keys(rawconfig); + + for (let i=0; i < conf_keys.length; i++){ + + if (availableConf.hasOwnProperty(conf_keys[i])){ + let value = rawconfig[conf_keys[i]]; + if (availableConf[conf_keys[i]].indexOf(value) < 0){ + return gpgme_error('PARAM_WRONG'); + } else { + result_config[conf_keys[i]] = value; + } + } + else { + return gpgme_error('PARAM_WRONG'); + } + } + let default_keys = Object.keys(defaultConf); + for (let j=0; j < default_keys.length; j++){ + if (!result_config.hasOwnProperty(default_keys[j])){ + result_config[default_keys[j]] = defaultConf[default_keys[j]]; + } + } + return result_config; +}; + +export default { + init: init +}
\ No newline at end of file diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js new file mode 100644 index 00000000..da46a1fd --- /dev/null +++ b/lang/js/src/permittedOperations.js @@ -0,0 +1,217 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ + + /** + * Definition of the possible interactions with gpgme-json. + * operation: <Object> + required: Array<Object> + <String> name The name of the property + allowed: Array of allowed types. Currently accepted values: + ['number', 'string', 'boolean', 'Uint8Array'] + array_allowed: Boolean. If the value can be an array of the above + allowed_data: <Array> If present, restricts to the given value + optional: Array<Object> + see 'required', with these parameters not being mandatory for a + complete message + pinentry: boolean If a pinentry dialog is expected, and a timeout of + 5000 ms would be too short + answer: <Object> + type: <String< The content type of answer expected + data: Array<String> The payload property of the answer. May be + partial and in need of concatenation + params: Array<String> Information that do not change throughout + the message + infos: Array<*> arbitrary information that may result in a list + } + } + */ + +export const permittedOperations = { + encrypt: { + required: { + 'keys': { + allowed: ['string'], + array_allowed: true + }, + 'data': { + allowed: ['string'] + } + }, + optional: { + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'chunksize': { + allowed: ['number'] + }, + 'base64': { + allowed: ['boolean'] + }, + 'mime': { + allowed: ['boolean'] + }, + 'armor': { + allowed: ['boolean'] + }, + 'always-trust': { + allowed: ['boolean'] + }, + 'no-encrypt-to': { + allowed: ['string'], + array_allowed: true + }, + 'no-compress': { + allowed: ['boolean'] + }, + 'throw-keyids': { + allowed: ['boolean'] + }, + 'want-address': { + allowed: ['boolean'] + }, + 'wrap': { + allowed: ['boolean'] + }, + }, + answer: { + type: ['ciphertext'], + data: ['data'], + params: ['base64'], + infos: [] + } + }, + + decrypt: { + pinentry: true, + required: { + 'data': { + allowed: ['string'] + } + }, + optional: { + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'chunksize': { + allowed: ['number'], + }, + 'base64': { + allowed: ['boolean'] + } + }, + answer: { + type: ['plaintext'], + data: ['data'], + params: ['base64', 'mime'], + infos: [] // TODO pending. Info about signatures and validity + //{ + //signatures: [{ + //Key : <String>Fingerprint, + //valid: <Boolean> + // }] + } + }, + /** TBD: querying the Key's information (keyinfo) + TBD name: { + required: { + 'fingerprint': { + allowed: ['string'] + }, + }, + answer: { + type: ['TBD'], + data: [], + params: ['hasSecret','isRevoked','isExpired','armored', + 'timestamp','expires','pubkey_algo'], + infos: ['subkeys', 'userIds'] + // {'hasSecret': <Boolean>, + // 'isRevoked': <Boolean>, + // 'isExpired': <Boolean>, + // 'armored': <String>, // armored public Key block + // 'timestamp': <Number>, // + // 'expires': <Number>, + // 'pubkey_algo': TBD // TBD (optional?), + // 'userIds': Array<String>, + // 'subkeys': Array<String> Fingerprints of Subkeys + // } + }*/ + + /** + listkeys:{ + required: {}; + optional: { + 'with-secret':{ + allowed: ['boolean'] + },{ + 'pattern': { + allowed: ['string'] + } + }, + answer: { + type: ['TBD'], + infos: ['TBD'] + // keys: Array<String> Fingerprints representing the results + }, + */ + + /** + importkey: { + required: { + 'keyarmored': { + allowed: ['string'] + } + }, + answer: { + type: ['TBD'], + infos: ['TBD'], + // for each key if import was a success, + // and if it was an update of preexisting key + } + }, + */ + + /** + deletekey: { + pinentry: true, + required: { + 'fingerprint': { + allowed: ['string'], + // array_allowed: TBD Allow several Keys to be deleted at once? + }, + optional: { + 'TBD' //Flag to delete secret Key ? + } + answer: { + type ['TBD'], + infos: [''] + // TBD (optional) Some kind of 'ok' if delete was successful. + } + } + */ + + /** + *TBD get armored secret different treatment from keyinfo! + * TBD key modification? + * encryptsign: TBD + * verify: TBD + */ +} diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js new file mode 100644 index 00000000..3450afd2 --- /dev/null +++ b/lang/js/unittest_inputvalues.js @@ -0,0 +1,45 @@ +import {Connection} from "./src/Connection"; +import {createKey} from "./src/Key"; + +let conn = new Connection; + +export const helper_params = { + validLongId: '0A0A0A0A0A0A0A0A', + validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', + createKey('ADDBC303B6D31026F5EB4591A27EABDF283121BB', conn), + 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], + validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], + invalidLongId: '9A9A7A7A8A9A9A7A7A8A', + invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)], + invalidKeyArray: {curiosity:'uncat'}, + invalidKeyArray_OneBad: [ + createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn), + 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', + '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], + invalidErrorCode: 'Please type in all your passwords.', + validGPGME_Key: createKey('ADDBC303B6D31026F5EB4591A27EABDF283121BB', conn), + valid_openpgplike: { primaryKey: { + getFingerprint: function(){ + return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';} + } + } +} + +export const message_params = { + invalid_op_action : 'dance', + invalid_op_type : [234, 34, '<>'], + valid_encrypt_data: "مرحبا بالعالم", + invalid_param_test: { + valid_op: 'encrypt', + invalid_param_names: [22,'dance', {}], + validparam_name_0: 'mime', + invalid_values_0: [2134, 'All your passwords', + createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn), null] + } +} + +export const whatever_params = { + four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'] +} diff --git a/lang/js/unittests.js b/lang/js/unittests.js new file mode 100644 index 00000000..c437d599 --- /dev/null +++ b/lang/js/unittests.js @@ -0,0 +1,326 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + */ +import "./node_modules/mocha/mocha"; +import "./node_modules/chai/chai"; +import { helper_params as hp } from "./unittest_inputvalues"; +import { message_params as mp } from "./unittest_inputvalues"; +import { whatever_params as wp } from "./unittest_inputvalues"; +import { Connection } from "./src/Connection"; +import { gpgme_error } from "./src/Errors"; +import { toKeyIdArray , isFingerprint } from "./src/Helpers"; +import { GPGME_Key , createKey } from "./src/Key"; +import { GPGME_Keyring } from "./src/Keyring"; +import {GPGME_Message, createMessage} from "./src/Message"; +import { setTimeout } from "timers"; + +mocha.setup('bdd'); +var expect = chai.expect; +chai.config.includeStack = true; + +function unittests (){ + describe('Connection testing', function(){ + + it('Connecting', function(done) { + let conn0 = new Connection; + let delayed = function(){ + expect(conn0.isConnected).to.be.true; + expect(conn0.connect).to.be.a('function'); + expect(conn0.disconnect).to.be.a('function'); + expect(conn0.post).to.be.a('function'); + done(); + }; + setTimeout(delayed, 5); + + }); + + it('Disconnecting', function(done) { + let conn0 = new Connection; + let delayed = function(){ + conn0.disconnect(); // TODO fails! + expect(conn0.isConnected).to.be.false; + done(); + }; + setTimeout(delayed, 5); + }); + + // broken + // it('Connect info still only available after a delay', function(done){ + // // if false, all delayed connections can be refactored + // let conn0 = new Connection; + // expect(conn0.isConnected).to.be.undefined; + // // + // }) + }); + + describe('Error Object handling', function(){ + + it('check the Timeout error', function(){ + let test0 = gpgme_error('CONN_TIMEOUT'); + + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('CONN_TIMEOUT'); + }); + + it('Error Object returns generic code if code is not listed', function(){ + let test0 = gpgme_error(hp.invalidErrorCode); + + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('GENERIC_ERROR'); + }); + + it('Warnings like PARAM_IGNORED should not return errors', function(){ + let test0 = gpgme_error('PARAM_IGNORED'); + + expect(test0).to.be.null; + }); + }); + + describe('Fingerprint checking', function(){ + + it('isFingerprint(): valid Fingerprint', function(){ + let test0 = isFingerprint(hp.validFingerprint); + + expect(test0).to.be.true; + }); + + it('isFingerprint(): invalid Fingerprints', function(){ + for (let i=0; i < hp.invalidFingerprints.length; i++){ + let test0 = isFingerprint(hp.invalidFingerprints[i]); + + expect(test0).to.be.false; + } + }); + }); + + describe('toKeyIdArray() (converting input to fingerprint)', function(){ + + it('Correct fingerprint string', function(){ + let test0 = toKeyIdArray(hp.validFingerprint); + + expect(test0).to.be.an('array'); + expect(test0).to.include(hp.validFingerprint); + }); + + it('correct GPGME_Key', function(){ + expect(hp.validGPGME_Key).to.be.an.instanceof(GPGME_Key); + let test0 = toKeyIdArray(hp.validGPGME_Key); + + expect(test0).to.be.an('array'); + expect(test0).to.include(hp.validGPGME_Key.fingerprint); + }); + + it('openpgpjs-like object', function(){ + let test0 = toKeyIdArray(hp.valid_openpgplike); + + expect(test0).to.be.an('array').with.lengthOf(1); + expect(test0).to.include( + hp.valid_openpgplike.primaryKey.getFingerprint()); + }); + + it('Array of valid inputs', function(){ + let test0 = toKeyIdArray(hp.validKeys); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf(hp.validKeys.length); + }); + + it('Incorrect inputs', function(){ + + it('valid Long ID', function(){ + let test0 = toKeyIdArray(hp.validLongId); + + expect(test0).to.be.empty; + }); + + it('invalidFingerprint', function(){ + let test0 = toKeyIdArray(hp.invalidFingerprint); + + expect(test0).to.be.empty; + }); + + it('invalidKeyArray', function(){ + let test0 = toKeyIdArray(hp.invalidKeyArray); + + expect(test0).to.be.empty; + }); + + it('Partially invalid array', function(){ + let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad); + + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf( + hp.invalidKeyArray_OneBad.length - 1); + }); + }); + }); + + describe('GPGME_Key', function(){ + + it('correct Key initialization', function(){ + let conn = new Connection; + let key = createKey(hp.validFingerprint, conn); + + expect(key).to.be.an.instanceof(GPGME_Key); + expect(key.connection).to.be.an.instanceof(Connection); + // TODO not implemented yet: Further Key functionality + }); + + it('Key can use the connection', function(){ + let conn = new Connection; + let key = createKey(hp.validFingerprint, conn); + + expect(key.connection.isConnected).to.be.true; + + key.connection.disconnect(); + expect(key.connection.isConnected).to.be.false; + }); + + it('createKey returns error if parameters are wrong', function(){ + let conn = new Connection; + for (let i=0; i< 4; i++){ + let key0 = createKey(wp.four_invalid_params[i], conn); + + expect(key0).to.be.an.instanceof(Error); + expect(key0.code).to.equal('PARAM_WRONG'); + } + for (let i=0; i< 4; i++){ + let key0 = createKey( + hp.validFingerprint, wp.four_invalid_params[i]); + + expect(key0).to.be.an.instanceof(Error); + expect(key0.code).to.equal('PARAM_WRONG'); + } + }); + it('bad GPGME_Key returns Error if used', function(){ + let conn = new Connection; + for (let i=0; i < 4; i++){ + let key = new GPGME_Key(wp.four_invalid_params[i], conn); + + expect(key.connection).to.be.an.instanceof(Error); + expect(key.connection.code).to.equal('KEY_INVALID'); + } + }); + }); + + describe('GPGME_Keyring', function(){ + + it('correct initialization', function(){ + let conn = new Connection; + let keyring = new GPGME_Keyring(conn); + + expect(keyring).to.be.an.instanceof(GPGME_Keyring); + expect(keyring.connection).to.be.an.instanceof(Connection); + expect(keyring.getKeys).to.be.a('function'); + expect(keyring.getSubset).to.be.a('function'); + }); + + it('Keyring should return errors if not connected', function(){ + let keyring = new GPGME_Keyring; + + expect(keyring).to.be.an.instanceof(GPGME_Keyring); + expect(keyring.connection).to.be.an.instanceof(Error); + expect(keyring.connection.code).to.equal('CONN_NO_CONNECT'); + expect(keyring.getKeys).to.be.an.instanceof(Error); + expect(keyring.getkeys.code).to.equal('CONN_NO_CONNECT'); + }); + //TODO not yet implemented: + // getKeys(pattern, include_secret) //note: pattern can be null + // getSubset(flags, pattern) + // available Boolean flags: secret revoked expired + }); + + describe('GPGME_Message', function(){ + + it('creating encrypt Message', function(){ + let test0 = createMessage('encrypt'); + + expect(test0).to.be.an.instanceof(GPGME_Message); + expect(test0.isComplete).to.be.false; + }); + + it('Message is complete after setting mandatory data', function(){ + let test0 = createMessage('encrypt'); + test0.setParameter('data', mp.valid_encrypt_data); + test0.setParameter('keys', hp.validFingerprints); + + expect(test0.isComplete).to.be.true; + }); + + it('Message is not complete after mandatory data is empty', function(){ + let test0 = createMessage('encrypt'); + test0.setParameter('data', ''); + test0.setParameter('keys', hp.validFingerprints); + expect(test0.isComplete).to.be.false; + }); + + it('Complete Message contains the data that was set', function(){ + let test0 = createMessage('encrypt'); + test0.setParameter('data', mp.valid_encrypt_data); + test0.setParameter('keys', hp.validFingerprints); + + expect(test0.message).to.not.be.null; + expect(test0.message).to.have.keys('op', 'data', 'keys'); + expect(test0.message.op).to.equal('encrypt'); + expect(test0.message.data).to.equal( + mp.valid_encrypt_data); + }); + + it ('Not accepting non-allowed operation', function(){ + let test0 = createMessage(mp.invalid_op_action); + + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('MSG_WRONG_OP'); + }); + it('Not accepting wrong parameter type', function(){ + let test0 = createMessage(mp.invalid_op_type); + + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('PARAM_WRONG'); + }); + + it('Not accepting wrong parameter name', function(){ + let test0 = createMessage(mp.invalid_param_test.valid_op); + for (let i=0; + i < mp.invalid_param_test.invalid_param_names.length; i++){ + let ret = test0.setParameter( + mp.invalid_param_test.invalid_param_names[i], + 'Somevalue'); + + expect(ret).to.be.an.instanceof(Error); + expect(ret.code).to.equal('PARAM_WRONG'); + } + }); + + it('Not accepting wrong parameter value', function(){ + let test0 = createMessage(mp.invalid_param_test.valid_op); + for (let j=0; + j < mp.invalid_param_test.invalid_values_0.length; j++){ + let ret = test0.setParameter( + mp.invalid_param_test.validparam_name_0, + mp.invalid_param_test.invalid_values_0[j]); + + expect(ret).to.be.an.instanceof(Error); + expect(ret.code).to.equal('PARAM_WRONG'); + } + }); + }); + +} + +export default {unittests};
\ No newline at end of file diff --git a/lang/js/webpack.conf.js b/lang/js/webpack.conf.js new file mode 100644 index 00000000..b2ad9098 --- /dev/null +++ b/lang/js/webpack.conf.js @@ -0,0 +1,35 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + * + * This is the configuration file for building the gpgmejs-Library with webpack + */ +const path = require('path'); + +module.exports = { + entry: './src/index.js', + // mode: 'development', + mode: 'production', + output: { + path: path.resolve(__dirname, 'build'), + filename: 'gpgmejs.bundle.js', + libraryTarget: 'var', + libraryExport: 'default', + library: 'Gpgmejs' + } +}; diff --git a/lang/js/webpack.conf_unittests.js b/lang/js/webpack.conf_unittests.js new file mode 100644 index 00000000..4b903be6 --- /dev/null +++ b/lang/js/webpack.conf_unittests.js @@ -0,0 +1,34 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see <http://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1+ + * + * This is the configuration file for building the gpgmejs-Library with webpack + */ +const path = require('path'); + +module.exports = { + entry: './unittests.js', + mode: 'production', + output: { + path: path.resolve(__dirname, 'build'), + filename: 'gpgmejs_unittests.bundle.js', + libraryTarget: 'var', + libraryExport: 'default', + library: 'Gpgmejs_test' + } +}; |