diff options
Diffstat (limited to 'lang/js')
55 files changed, 5771 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/.eslintrc.json b/lang/js/.eslintrc.json new file mode 100644 index 00000000..dc3be2e4 --- /dev/null +++ b/lang/js/.eslintrc.json @@ -0,0 +1,49 @@ +{ +    "env": { +        "browser": true, +        "es6": true +    }, +    "extends": "eslint:recommended", +    "parserOptions": { +        "sourceType": "module" +    }, +    "rules": { +        "indent": [ +            "warn", +            4 +        ], +        "linebreak-style": [ +            "error", +            "unix" +        ], +        "quotes": [ +            "error", +            "single" +        ], +        "semi": [ +            "error", +            "always" +        ], +        "no-var": [ +            "warn" +        ], +        "max-len": 1, +        "default-case": 2, +        "no-invalid-this": 2, +        "no-lone-blocks": 1, +        "no-self-compare": 2, +        "radix": 2, +        "no-use-before-define": ["error", { +            "functions": false, +            "classes": false, +            "variables": true +        }], +        "no-useless-constructor": 1, +        "space-before-function-paren": ["error", "always"], +        "keyword-spacing": 2, +        "spaced-comment": 1, +        "space-unary-ops": 2, +        "object-curly-spacing": ["error", "always"], +        "array-bracket-spacing": ["error", "never"] +    } +}
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/Makefile.am b/lang/js/BrowserTestExtension/Makefile.am new file mode 100644 index 00000000..8f0a4f93 --- /dev/null +++ b/lang/js/BrowserTestExtension/Makefile.am @@ -0,0 +1,45 @@ +# Makefile.am for gpgme.js. +# Copyright (C) 2018 Intevation GmbH +# +# This file is part of GPGME. +# +# gpgme.js is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# gpgme.js 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +EXTRA_DIST = browsertest.html \ +             index.html \ +             longTests.html \ +             Makefile.am \ +             manifest.json \ +             popup.html \ +             popup.js \ +             runbrowsertest.js \ +             rununittests.js \ +             setup_testing.js \ +             testicon.png \ +             testkey2.pub \ +             testkey.pub \ +             testkey.sec \ +             tests/decryptTest.js \ +             tests/encryptDecryptTest.js \ +             tests/encryptTest.js \ +             tests/inputvalues.js \ +             tests/KeyImportExport.js \ +             tests/KeyInfos.js \ +             tests/longRunningTests.js \ +             tests/signTest.js \ +             tests/startup.js \ +             tests/verifyTest.js \ +             unittests.html diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html new file mode 100644 index 00000000..0d3e2936 --- /dev/null +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -0,0 +1,28 @@ +<!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/KeyInfos.js"></script> +    <script src="tests/encryptTest.js"></script> +    <script src="tests/encryptDecryptTest.js"></script> +    <script src="tests/signTest.js"></script> +    <script src="tests/verifyTest.js"></script> +    <script src="tests/decryptTest.js"></script> +    <script src="tests/KeyImportExport.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..7f8d97de --- /dev/null +++ b/lang/js/BrowserTestExtension/index.html @@ -0,0 +1,113 @@ +<!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. +    </p> +    <p> +        Most tests rely on a test gpg key to be available in gpg, which can be +        found at the bottom of this page, or as "testkey.sec" in the +        BrowserTestExtension's directory. Please import this key to your tested +        gpg installation, or adapt the input defined in tests/inputvalues.js +        if you want to use different values. +    </p> +    <p> +        <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> +        <hr /> +        <p> + +            <textarea rows="5" cols="65" wrap="hard" readonly> +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +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----- +            </textarea> + +        </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..794620b6 --- /dev/null +++ b/lang/js/BrowserTestExtension/popup.js @@ -0,0 +1,30 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global chrome */ + +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..c46eb120 --- /dev/null +++ b/lang/js/BrowserTestExtension/runbrowsertest.js @@ -0,0 +1,26 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global mocha */ + +mocha.run(); diff --git a/lang/js/BrowserTestExtension/rununittests.js b/lang/js/BrowserTestExtension/rununittests.js new file mode 100644 index 00000000..df31589e --- /dev/null +++ b/lang/js/BrowserTestExtension/rununittests.js @@ -0,0 +1,27 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global Gpgmejs_test, mocha*/ + +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..52aeac58 --- /dev/null +++ b/lang/js/BrowserTestExtension/setup_testing.js @@ -0,0 +1,28 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global mocha, chai */ + +mocha.setup('bdd'); +const expect = chai.expect; //eslint-disable-line no-unused-vars +chai.config.includeStack = true;
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/testicon.png b/lang/js/BrowserTestExtension/testicon.pngBinary files differ new file mode 100644 index 00000000..a98463de --- /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----- + +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 +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/testkey2.pub b/lang/js/BrowserTestExtension/testkey2.pub new file mode 100644 index 00000000..557bd5be --- /dev/null +++ b/lang/js/BrowserTestExtension/testkey2.pub @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFsMHecBCACqdJgqa+CeNYwPCK+MpOwAV6uFVjDyO2LmOs6+XfDWRBU/Zjtz +8zdYNKSbLjkWN4ujV5aiyA7MtEofszzYLEoKUt1wiDScHMpW8qmEFDvl9g26MeAV +rTno9D5KodHvEIs8wnrqBs8ix0WLbh6J1Dtt8HQgIbN+v3gaRQrgBFe6z2ZYpHHx +ZfOu3iFKlm2WE/NekRkvvFIo3ApGvRhGIYw6JMmugBlo7s5xosJK0I9dkPGlEEtt +aF1RkcMj8sWG9vHAXcjlGgFfXSN9YLppydXpkuZGm4+gjLB2a3rbQCZVFnxCyG4O +ybjkP8Jw6Udm89bK2ucYFfjdrmYn/nJqRxeNABEBAAG0I1Rlc3QgTm9Qcml2S2V5 +IDxub2JvZHlAZXhhbXBsZS5vcmc+iQFOBBMBCAA4FiEE4Fmh4IZtMa4TEXCITZou +EzBBU9EFAlsMHecCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQTZouEzBB +U9F+qwf/SHj4uRnTWgyJ71FBxQDYCBq3jbi6e7hMkRPbJyJdnPIMAb2p0PJjBgjW +0pp4+kDPZans3UDHbma1u/SFI4/y6isJiK94Bk5xp5YliLGnUceTjgDFe6lBhfQ1 +zVWZC/NF3tPgbziIxXQTNt34nS+9dbV/QFDLW0POcN7C0jR/hgkBjMEH2PezWhSj +mL/yLfLfUYAoxVpXjfC5aPJKqw0tR7m5ibznjCphE+FUMRg8EOmJcg6soeJ5QspU +k2dPN3+Y0zCTNRgAHEI+yIQbM6pio6v2c+UCtT1QhW4xSI38/kcEG8QiM55r1TUy +FcWAY5n5t1nNZtMxxse3LqEon3rKiLkBDQRbDB3nAQgAqfAjSjcngERtM+ZYOwN0 +QF2v2FuEuMe8mhju7Met7SN2zGv1LnjhTNshEa9IABEfjZirE2Tqx4xCWDwDedK4 +u1ToFvcnuAMnq2O47Sh+eTypsf6WPFtPBWf6ctKY31hFXjgoyDBULBvl43XU/D9C +Mt7nsKDPYHVrrnge/qWPYVcb+cO0sSwNImMcwQSdTQ3VBq7MeNS9ZeBcXi+XCjhN +kjNum2AQqpkHHDQV7871yQ8RIILvZSSfkLb0/SNDU+bGaw2G3lcyKdIfZi2EWWZT +oCbH38I/+LV7nAEe4zFpHwW8X0Dkx2aLgxe6UszDH9L3eGhTLpJhOSiaanG+zZKm ++QARAQABiQE2BBgBCAAgFiEE4Fmh4IZtMa4TEXCITZouEzBBU9EFAlsMHecCGwwA +CgkQTZouEzBBU9H5TQgAolWvIsez/WW8N2tmZEnX0LOFNB+1S4L4X983njwNdoVI +w19pbj+8RIHF/H9kcPGi7jK96gvlykQn3uez/95D2AiRFW5KYdOouFisKgHpv8Ay +BrhclHv11yK+X/0iTD0scYaG7np5162xLkaxSO9hsz2fGv20RKaXCWkI69fWw0BR +XlI5pZh2YFei2ZhH/tIMIW65h3w0gtgaZBBdpZTOOW4zvghyN+0MSObqkI1BvUJu +caDFI4d6ZTmp5SY+pZyktZ4bg/vMH5VFxdIKgbLx9uVeTvOupvbAW0TNulYGUBQE +nm+S0zr3W18t64e4sS3oHse8zCqo1iiImpba6F1Oaw== +=y6DD +-----END PGP PUBLIC KEY BLOCK----- diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js new file mode 100644 index 00000000..f52b790a --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -0,0 +1,149 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + *     Raimund Renkert <[email protected]> + */ + +/* global describe, it, expect, before, afterEach, Gpgmejs*/ +/* global ImportablePublicKey, inputvalues */ + +describe('Key importing', function () { +    const fpr = ImportablePublicKey.fingerprint; +    const pubKey = ImportablePublicKey.key; +    const changedKey = ImportablePublicKey.keyChangedUserId; + +    let context = null; +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            context.Keyring.getKeys(fpr).then( +                function (result){ +                    if (result.length === 1) { +                        result[0].delete().then(function (){ +                            done(); +                        },function (){ +                            done(); +                        }); +                    } else { +                        done(); +                    } +                }); +        }); +    }); + +    afterEach(function (done){ +        // delete the test key if still present +        context.Keyring.getKeys(fpr).then( +            function (result){ +                if (result.length === 1) { +                    result[0].delete().then(function (){ +                        done(); +                    },function (){ +                        done(); +                    }); +                } else { +                    done(); +                } +            }); +    }); + +    it('Importing Key', function (done) { +        context.Keyring.getKeys(fpr).then(function (result){ +            expect(result).to.be.an('array'); +            expect(result.length).to.equal(0); +            context.Keyring.importKey(pubKey).then(function (result){ +                expect(result.Keys).to.be.an('array'); +                expect(result.Keys[0]).to.not.be.undefined; +                expect(result.Keys[0].key).to.be.an('object'); +                expect(result.Keys[0].key.fingerprint).to.equal(fpr); +                expect(result.Keys[0].status).to.equal('newkey'); +                expect(result.summary.considered).to.equal(1); +                expect(result.summary.imported).to.equal(1); +                done(); +            }); +        }); +    }); + +    it('Updating Key', function (done){ +        context.Keyring.importKey(pubKey) +            .then(function (result){ +                expect(result.Keys[0].key).to.not.be.undefined; +                expect(result.Keys[0].status).to.equal('newkey'); +                context.Keyring.importKey(changedKey).then(function (res){ +                    expect(res.Keys[0].key).to.be.an('object'); +                    expect(res.Keys[0].key.fingerprint).to.equal(fpr); +                    expect(res.Keys[0].status).to.equal('change'); +                    expect(res.Keys[0].changes.userId).to.be.true; +                    expect(res.Keys[0].changes.subkey).to.be.false; +                    expect(res.Keys[0].changes.signature).to.be.true; +                    expect(res.summary.considered).to.equal(1); +                    done(); +                }); +            }); +    }); + +    it('Deleting Key', function (done) { +        context.Keyring.importKey(pubKey).then(function (result){ +            expect(result.Keys[0].key).to.be.an('object'); +            expect(result.Keys[0].key.fingerprint).to.equal(fpr); +            result.Keys[0].key.delete().then(function (result){ +                expect(result).to.be.true; +                done(); +            }); +        }); +    }); + +    it('Import result feedback', function (done){ +        context.Keyring.importKey(pubKey, true).then(function (result){ +            expect(result).to.be.an('object'); +            expect(result.Keys[0]).to.be.an('object'); +            expect(result.Keys[0].key.fingerprint).to.equal(fpr); +            expect(result.Keys[0].status).to.equal('newkey'); +            result.Keys[0].key.getArmor().then(function (armor){ +                expect(armor).to.be.a('string'); +                done(); +            }); +        }); +    }); + +    it('exporting armored Key with getKeysArmored', function (done) { +        context.Keyring.importKey(pubKey).then(function (){ +            context.Keyring.getKeysArmored(fpr).then(function (result){ +                expect(result).to.be.an('object'); +                expect(result.armored).to.be.a('string'); +                expect(result.secret_fprs).to.be.undefined; +                done(); +            }); +        }); +    }); + +    it('Exporting Key (including secret fingerprints)', function (done) { +        const key_secret = inputvalues.encrypt.good.fingerprint; +        context.Keyring.getKeysArmored(key_secret, true).then(function (result){ +            expect(result).to.be.an('object'); +            expect(result.armored).to.be.a('string'); +            expect(result.secret_fprs).to.be.an('array'); +            expect(result.secret_fprs[0]).to.equal(key_secret); +            done(); +        }); +    }); +});
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/KeyInfos.js b/lang/js/BrowserTestExtension/tests/KeyInfos.js new file mode 100644 index 00000000..e1caabe1 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/KeyInfos.js @@ -0,0 +1,57 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, expect, before, Gpgmejs */ +/* global inputvalues*/ + +describe('Key information', function () { +    let context = null; +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); + +    it('A fingerprint is consistently returned upper case hex', function (done){ +        const mixedCase = inputvalues.encrypt.good.fingerprint_mixedcase; +        context.Keyring.getKeys(mixedCase).then(function (result){ +            expect(result).to.be.an('array'); +            expect(result.length).to.equal(1); +            expect(result[0].fingerprint).to.equal(mixedCase.toUpperCase()); +            done(); +        }); +    }); + +    it('A userId keeps their encoding', function (done){ +        context.Keyring.importKey(inputvalues.publicKeyNonAscii.key, true) +            .then(function (result){ +                expect(result.Keys[0]).to.be.an('object'); +                const user = result.Keys[0].key.get('userids')[0]; +                expect(user.get('name')).to.equal( +                    inputvalues.publicKeyNonAscii.userid); +                done(); +            }); +    }); +});
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/decryptTest.js b/lang/js/BrowserTestExtension/tests/decryptTest.js new file mode 100644 index 00000000..ea887491 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/decryptTest.js @@ -0,0 +1,78 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, before, expect, Gpgmejs */ +/* global bigString, inputvalues, sabotageMsg*/ + +describe('Decryption', function () { +    let context = null; +    const good_fpr = inputvalues.encrypt.good.fingerprint; + +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); + +    it('Decryption of random string fails', function (done) { +        let data = bigString(20 * 1024); +        context.decrypt(data).then( +            function (){}, +            function (error){ +                expect(error).to.be.an('error'); +                expect(error.code).to.equal('GNUPG_ERROR'); +                done(); +            }); +    }); + +    it('Decryption of slightly corrupted message fails', function (done) { +        const data = bigString(10000); +        context.encrypt(data, good_fpr).then(function (enc){ +            context.decrypt(sabotageMsg(enc.data)).then( +                function (){}, +                function (error){ +                    expect(error).to.be.an('error'); +                    expect(error.code).to.equal('GNUPG_ERROR'); +                    done(); +                }); +        }); +    }).timeout(5000); + + +    it('decrypt/verify operations return proper information', function (done){ +        const data = inputvalues.encryptSignedMessage; +        context.decrypt(data).then(function (result){ +            expect(result).to.be.an('object'); +            expect(result.signatures).to.be.an('object'); +            expect(result.signatures.all_valid).to.be.true; +            expect(result.signatures.count).to.equal(1); +            expect(result.signatures.signatures.good).to.be.an('array'); +            expect( +                result.signatures.signatures.good[0].fingerprint).to.equal( +                good_fpr); +            done(); +        }); +    }); +});
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js new file mode 100644 index 00000000..28c98d98 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -0,0 +1,170 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, expect, before, Gpgmejs */ +/* global inputvalues, encryptedData, bigString, bigBoringString */ + +describe('Encryption and Decryption', function (){ +    let context = null; +    let good_fpr = inputvalues.encrypt.good.fingerprint; + +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); + +    it('Successful encrypt and decrypt simple string', function (done) { +        let data = inputvalues.encrypt.good.data; +        context.encrypt(data, good_fpr).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); +                done(); +            }); +        }); +    }); + +    it('Decrypt simple non-ascii', function (done) { +        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('Trailing whitespace and different line endings', function (done) { +        const data = 'Keks. \rKeks \n Keks \r\n'; +        context.encrypt(data, good_fpr).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); +                done(); +            }); +        }); +    }).timeout(5000); + +    it('Random data, as string', function (done) { +        let data = bigString(1000); +        context.encrypt(data, good_fpr).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); +                done(); +            }); +        }); +    }).timeout(3000); + +    it('Data, input as base64', function (done) { +        let data = inputvalues.encrypt.good.data; +        let b64data = btoa(data); +        context.encrypt(b64data, good_fpr, 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(data).to.equal(data); +                    done(); +                }); +        }); +    }).timeout(3000); + +    it('Random data, input as base64', function (done) { +        let data = bigBoringString(0.001); +        let b64data = btoa(data); +        context.encrypt(b64data, good_fpr, 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(b64data); +                    done(); +                }); +        }); +    }).timeout(3000); + +    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 data = ''; +            for (let i=0; i < 34 * 1024; i++){ +                data += input; +            } +            context.encrypt(data,good_fpr).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); +                    done(); +                }); +            }); +        }).timeout(5000); +    } +}); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js new file mode 100644 index 00000000..a242af5f --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -0,0 +1,113 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, expect, before, Gpgmejs */ +/* global inputvalues, fixedLengthString */ + +describe('Encryption', function () { +    let context = null; +    const good_fpr = inputvalues.encrypt.good.fingerprint; +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); + +    it('Successful encrypt', function (done) { +        const data = inputvalues.encrypt.good.data; +        context.encrypt(data, good_fpr).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'); +            done(); +        }); +    }); + +    const sizes = [5,20,50]; +    for (let i=0; i < sizes.length; i++) { +        it('Successful encrypt a ' + sizes[i] + 'MB message', function (done) { +            const data = fixedLengthString(sizes[i]); +            context.encrypt(data, good_fpr).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'); +                done(); +            }); +        }).timeout(20000); +    } + +    it('Sending encryption without keys fails', function (done) { +        const data = inputvalues.encrypt.good.data; +        context.encrypt(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'); +            done(); +        }); +    }); + +    it('Sending encryption without data fails', function (done) { +        context.encrypt(null, good_fpr).then(function (answer) { +            expect(answer).to.be.undefined; +        }, function (error) { +            expect(error).to.be.an.instanceof(Error); +            expect(error.code).to.equal('MSG_INCOMPLETE'); +            done(); +        }); +    }); + +    it('Sending encryption with non existing keys fails', function (done) { +        const data = inputvalues.encrypt.good.data; +        const bad_fpr = inputvalues.encrypt.bad.fingerprint; +        context.encrypt(data, bad_fpr).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'); +            done(); +        }); +    }).timeout(5000); + +    it('Overly large message ( > 64MB) is rejected', function (done) { +        const data = fixedLengthString(65); +        context.encrypt(data, good_fpr).then(function (answer) { +            expect(answer).to.be.undefined; +        }, function (error){ +            expect(error).to.be.an.instanceof(Error); +            // TODO: there is a 64 MB hard limit at least in chrome at: +            // chromium//extensions/renderer/messaging_util.cc: +            // kMaxMessageLength +            // The error will be a browser error, not from gnupg or from +            // this library +            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..5c2abf36 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -0,0 +1,338 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +const inputvalues = {// eslint-disable-line no-unused-vars +    encrypt: { +        good:{ +            data : 'Hello World.', +            // Fingerprint of a key that has been imported to gnupg +            // (i.e. see testkey.pub; testkey.sec) +            fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05', +            fingerprint_mixedcase: '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' +        } +    }, + +    signedMessage: { +        good: '-----BEGIN PGP SIGNED MESSAGE-----\n' + +        'Hash: SHA256\n' + +        '\n' + +        'Matschige Münsteraner Marshmallows\n' + +        '-----BEGIN PGP SIGNATURE-----\n' + +        '\n' + +        'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' + +        'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' + +        'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' + +        'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' + +        'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' + +        'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + +        'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + +        '=ioB6\n' + +        '-----END PGP SIGNATURE-----\n', +        bad: '-----BEGIN PGP SIGNED MESSAGE-----\n' + +        'Hash: SHA256\n' + +        '\n' + +        'Matschige Münchener Marshmallows\n' + +        '-----BEGIN PGP SIGNATURE-----\n' + +        '\n' + +        'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' + +        'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' + +        'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' + +        'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' + +        'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' + +        'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + +        'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + +        '=ioB6\n' + +        '-----END PGP SIGNATURE-----\n' +    }, +    encryptSignedMessage: '-----BEGIN PGP MESSAGE-----\n'+ +        '\n'+ +        'hQEMA6B8jfIUScGEAQf/bmQ+xNMGTjPvQCktkxR4Svt2dVNVdSzKsCmvSv24QOQF\n'+ +        'yBMK5w51S/6DTdiZI12IYD7hjvkr9NqxXXupjrVKwqEVpg4Pkwckac0OcElJIBsL\n'+ +        '3htr4iYsr8dhSgSS4BO0azcu4wZQTXy5v2P7yYPECMEagNEXnW+tE7sHLCq8Ysqz\n'+ +        'LVxG0R0IUijKeEd3xQC2Tt20e1Z1j5tnqaPhE/9Smqf5OjSUDqpXxvRnSNRk/zEs\n'+ +        'cGVgCF+cv68nUJM9lwEAbBQChplwL6ebnhunC6DsRCxnjLHVyKm127hmhSiMGC0e\n'+ +        'Ns31mGeP1dxpDv6Gi2/oKmq67vG3i4fKeckj7bt30tLA1wH0Qn5Mn6Tzxzve0W0q\n'+ +        'Ghqn9PY9qNK8EkrkzqaFk9dzu5tfSbaJBLS/uIhX2Wj70EMEBbFSkN0qlgOfLgGw\n'+ +        '5mwRvCgj4nvV1ByFhnx7uwgQixvOwLH4JLKvwCQpJm+O2R0eC7M6CzR/b9iL/oaO\n'+ +        'JTkoD9hcLhxF7j+3ZYg7rbNwofuHST097vFjzItsucb0jHOzjlkCqbhdczICILTa\n'+ +        'H76Q6YGdMLyG9a3s4yZUMruaeQyWGeXlryzLDvdEoSgoD5YrolsFOM+Z2apbzVs2\n'+ +        'k5CltwtanjjWGnpAqSyr49C6CSU8G1QHpNygx5frtAS8bojR2ovB9OJp2wUklDvC\n'+ +        'LtU7dLpTY/BIvfB1vzwcW/aNgmPadNHX8mAzlqTQJjeLoo69Wp804t+u36sgfd/J\n'+ +        'ser7vdJJUm+86Q9csefItvFmHhqjMg5XXHoa8WZWJOHIQMxZkaIwKAzcEt/oEOdJ\n'+ +        'rbVNVabhTdbmS5I1ok16wg5jMF07ZDM7nXWMcQNjwT646XKP+pp2N6YQROVidNXj\n'+ +        'COyRyiXE/csr\n'+ +        '=Ik7G\n'+ +        '-----END PGP MESSAGE-----\n', +    someInputParameter: 'bad string', + +    publicKeyNonAscii: { +        userid: 'Müller €uro', +        key: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + '\n' + +          'mQENBFt2/VIBCADIWBIMxExZlHda4XIVnM9nsIfUYLebJSC/krEriyWgzytU8/fQ\n' + +          'S05cfnYx7RXvOOq4k8aa7mu80ovg3q77idXauLreAUwng4Njw0nMxWq/vtoMiZ60\n' + +          '9f8EmfthZophhkQF2HIPHyqXMDZzMLWv4oTr2UJ9BKudL1XtbK51y9TbiyfQygBl\n' + +          '8bl+zrOo70/dN6aunvuo6Hlu5cEzkj2QrzZlqTdfG5qv6KVEMut1eAbxZAmvSnna\n' + +          'R4wqiRCT3/eRXGJbDL/8GaCEYkwi9FBrimjOTV0MpcLNwAU4aGfDxMUsxML9xJ+/\n' + +          '/6GFxzYf7Lmk5UhvoewR58uQkHkTVPjZ9hXZABEBAAG0KE3DvGxsZXIg4oKsdXJv\n' + +          'IDxtdWVsbGVyZXVyb0BleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQQVNixp3XT/DuGT\n' + +          'F4MFmkL4L5UZdAUCW3b9UgIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIX\n' + +          'gAAKCRAFmkL4L5UZdAhiCACowW1aC8DYGtJyAaBO2MqWhyw1wVCbQN9uFsQZPydY\n' + +          'v3BEbCDrRc0HyfV1PVoRQsgkiNMes1S2tz2IMJoEOTMaz3WjPM8yK0dDbo5sfx/o\n' + +          '/XaXeKhyYNqRkz2dPzorg1sHyHe0ki/HoQiANEJ8mByMtlwnPWlhnINAX+27eL17\n' + +          'JC8juhBYUchqoIBAl+ajYKSThdLzrUkcL7QfJjZb3pPytJSTTdFc0rD6ERDbfXXc\n' + +          '/vnE2SDYme+XXn7H5tNe67tPM8M96vbp+uM+n2t/z96C+Pqb6GJFMBa35PM+/qQO\n' + +          'yr0I2oaQnTecx2AfBXGZvd81wMYikAJ9rAOWyMQZHJWouQENBFt2/VIBCADXCvKD\n' + +          '3wRWCOzRWtLTs7hpAjCDxp6niPkwxKuUf9r/sUPmn0pWdZHYlbPDev9psN9bnJ+C\n' + +          '+wzzPZ1zgSYKIAN0IMoh0L7BRAoau7VWQ3Q7hP6HIbdzOTEGyklSoh9pIh6IlwZZ\n' + +          'XfPlFlnn7FeH1UeA711E174SUpDRKYSfT+mFObQUuQewGi9QC3gBsz5MPLQQLzML\n' + +          'yimIOT+8i64fHHSKChw5ZDckBffej31/YHPQ7+JsWFV+G/6xDfbwnaFZFAUwo+1L\n' + +          '4w9UiMyCNkIWCkulzJ2Hbz66xzFMi/8zMYxr08Af+PpsXaWTQHAa5V4GNJSInDEB\n' + +          '7gy/CGLcY90EozoDABEBAAGJATwEGAEIACYWIQQVNixp3XT/DuGTF4MFmkL4L5UZ\n' + +          'dAUCW3b9UgIbDAUJA8JnAAAKCRAFmkL4L5UZdPqoB/9kpqxqa82k7JMcq7UiwQY7\n' + +          'CdqCUPKF88ciOWKBpZmpl8V7zgM7kEXwmM6ocHcznXi8xM7eOfDIJcBeqFVIE4OT\n' + +          '63OCMuvZICM9Kiu48wLNAw5W/YGAOBH7ySQzZM2XrtvwfFtJ3lR00t5f4FVtriA5\n' + +          '47BjYYG5tTdJc8HwEHs045S99xKCWqwuDgO9qskIi6iPePUkuhpaVBLuEj2Goku6\n' + +          'i8aql/vKYQS67L7UHJiEbjLe+wP9k3FvWUFTx39lAubsDzb4Abhe+qRqs2TKD7Go\n' + +          'k35ZriRIYllmx4c9KyWL7Mvzcp+84Sq9LeMfsN4JstBDJ7jn6g19SjO5dmtxSuP0\n' + +          '=zZSJ\n' + +          '-----END PGP PUBLIC KEY BLOCK-----\n' +    } +}; + +// (Pseudo-)Random String covering all of utf8. +function bigString (length){// eslint-disable-line no-unused-vars +    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){// eslint-disable-line no-unused-vars +    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){// eslint-disable-line no-unused-vars +    let maxlength = 1024 * 1024 * megabytes; +    let uint = new Uint8Array(maxlength); +    for (let i= 0; i < maxlength; i++){ +        uint[i] = Math.floor(Math.random() * 256); +    } +    return uint; +} + +// (Pseudo-)Random string with very limited charset +// (ascii only, no control chars) +function bigBoringString (megabytes){// eslint-disable-line no-unused-vars +    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 +// eslint-disable-next-line no-unused-vars +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 +const encryptedData =// eslint-disable-line no-unused-vars +    '-----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'; + +const ImportablePublicKey = {// eslint-disable-line no-unused-vars +    fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A', +    key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + +    '\n' + +    'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' + +    'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' + +    'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' + +    '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' + +    'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' + +    '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' + +    'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' + +    'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' + +    'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' + +    'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' + +    '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' + +    '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' + +    'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' + +    'uzEXPGW+sq0WRp3hynn7kVP6QQYvuQENBFsPvK0BCACwvBcmbnGJk8XhEBRu2QN3\n' + +    'jKgVs3CG5nE2Xh20JipZwAuGHugDLv6/jlizzz5jtj3SAHVtJB8lJW8I0cNSEIX8\n' + +    'bRYH4C7lP2DTb9CgMcGErQIyK480+HIsbsZhJSNHdjUUl6IPEEVfSQzWaufmuswe\n' + +    'e+giqHiTsaiW20ytXilwVGpjlHBaxn/bpskZ0YRasgnPqKgJD3d5kunNqWoyCpMc\n' + +    'FYgDERvPbhhceFbvFE9G/u3gbcuV15mx53dDX0ImvPcvJnDOyJS9yr7ApdOV312p\n' + +    'A1MLbxfPnbnVu+dGXn7D/VCDd5aBYVPm+5ANrk6z9lYKH9aO5wgXpLAdJvutCOL5\n' + +    'ABEBAAGJATwEGAEIACYWIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUCWw+8rQIbDAUJ\n' + +    'A8JnAAAKCRD2N5DBHmAnigMVB/484G2+3R0cAaj3V/z4gW3MRSMhcYqEMyJ/ACdo\n' + +    '7y8eoreYW843JWWVDRY6/YcYYGuBBP47WO4JuP2wIlVn17XOCSgnNjmmjsIYiAzk\n' + +    'op772TB27o0VeiFX5iWcawy0EI7JCb23xpI+QP31ksL2yyRYFXCtXSUfcOrLpCY8\n' + +    'aEQMQbAGtkag1wHTo/Tf/Vip8q0ZEQ4xOKTR2/ll6+inP8kzGyzadElUnH1Q1OUX\n' + +    'd2Lj/7BpBHE2++hAjBQRgnyaONF7mpUNEuw64iBNs0Ce6Ki4RV2+EBLnFubnFNRx\n' + +    'fFJcYXcijhuf3YCdWzqYmPpU/CtF4TgDlfSsdxHxVOmnZkY3\n' + +    '=qP6s\n' + +    '-----END PGP PUBLIC KEY BLOCK-----\n', + +    keyChangedUserId: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + +    '\n' + +    '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\n' + +    '=9WZ7\n' + +    '-----END PGP PUBLIC KEY BLOCK-----\n' +}; + +/** + * Changes base64 encoded gpg messages + * @param {String} msg input message + * @param {Number} rate of changes as percentage of message length. + * @param {[Number, Number]} p begin and end of the message left untouched (to + * preserve) header/footer + */ +// eslint-disable-next-line no-unused-vars +function sabotageMsg (msg, rate = 0.01, p= [35,35]){ +    const iterations = Math.floor(Math.random() * msg.length * rate) + 1; +    const base64_set = +        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/'; +    for (let i=0; i < iterations; i++){ +        let str0, str1, str2; +        const chosePosition = function (){ +            let position = +                Math.floor( Math.random() * (msg.length - p[0] + p[1])) +                + p[0]; +            str1 = msg.substring(position,position+1); +            if (str1 === '\n'){ +                chosePosition(); +            } else { +                str0 = msg.substring(0,position); +                str2 = msg.substring(position +1); +            } +        }; +        chosePosition(); +        let new1 = function (){ +            let n = base64_set[Math.floor(Math.random() * 64)]; +            return (n === str1) ? new1() : n; +        }; +        msg = str0.concat(new1()).concat(str2); +    } +    return msg; +} diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js new file mode 100644 index 00000000..240a6b93 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -0,0 +1,56 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ +/* global describe, it, before, expect, Gpgmejs */ +/* global bigString, inputvalues */ + +describe('Long running Encryption/Decryption', function () { +    let context = null; +    const good_fpr = inputvalues.encrypt.good.fingerprint; +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); + +    for (let i=0; i < 101; i++) { +        it('Successful encrypt/decrypt completely random data ' +            + (i+1) + '/100', function (done) { +            const data = bigString(2*1024*1024); +            context.encrypt(data,good_fpr).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); +                    done(); +                }); +            }); +        }).timeout(15000); +    } + +}); diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js new file mode 100644 index 00000000..f5bd9c1d --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -0,0 +1,63 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, expect, before, Gpgmejs */ +/* global bigString, inputvalues */ + +describe('Signing', function () { +    let context = null; +    const good_fpr = inputvalues.encrypt.good.fingerprint; + +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); + +    it('Sign a message', function (done) { +        const data = bigString(100); +        context.sign(data, good_fpr).then(function (answer) { +            expect(answer).to.not.be.empty; +            expect(answer.data).to.be.a('string'); +            expect(answer.data).to.include('BEGIN PGP SIGNATURE'); +            expect(answer.data).to.include('END PGP SIGNATURE'); +            expect(answer.data).to.include(data); +            done(); +        }); +    }); + +    it('Detached sign a message', function (done) { +        const data = bigString(100); +        context.sign(data,good_fpr, 'detached').then(function (answer) { +            expect(answer).to.not.be.empty; +            expect(answer.data).to.be.a('string'); +            expect(answer.data).to.include(data); +            expect(answer.signature).to.be.a('string'); +            expect(answer.signature).to.be.a('string'); +            done(); +        }); +    }); + +}); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js new file mode 100644 index 00000000..cf5b0999 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -0,0 +1,47 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, expect, Gpgmejs, inputvalues */ + +describe('GPGME context', function (){ +    it('Starting a GpgME instance', function (done){ +        let prm = Gpgmejs.init(); +        const input = inputvalues.someInputParameter; +        prm.then( +            function (context){ +                expect(context).to.be.an('object'); +                expect(context.encrypt).to.be.a('function'); +                expect(context.decrypt).to.be.a('function'); +                expect(context.sign).to.be.a('function'); +                expect(context.verify).to.be.a('function'); +                context.Keyring = input; +                expect(context.Keyring).to.be.an('object'); +                expect(context.Keyring).to.not.equal(input); +                expect(context.Keyring.getKeys).to.be.a('function'); +                expect(context.Keyring.getDefaultKey).to.be.a('function'); +                expect(context.Keyring.importKey).to.be.a('function'); +                expect(context.Keyring.generateKey).to.be.a('function'); +                done(); +            }); +    }); +});
\ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js new file mode 100644 index 00000000..5788ed51 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/verifyTest.js @@ -0,0 +1,90 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global describe, it, expect, before, bigString, inputvalues, Gpgmejs */ + + + +describe('Verifying data', function () { +    let context = null; +    before(function (done){ +        const prm = Gpgmejs.init(); +        prm.then(function (gpgmejs){ +            context = gpgmejs; +            done(); +        }); +    }); +    it('Successful verify message', function (done) { +        const message = inputvalues.signedMessage.good; +        context.verify(message).then(function (result){ +            expect(result.data).to.be.a('string'); +            expect(result.signatures.all_valid).to.be.true; +            expect(result.signatures.count).to.equal(1); +            expect(result.signatures.signatures.good).to.be.an('array'); +            expect(result.signatures.signatures.good.length).to.equal(1); +            expect(result.signatures.signatures.good[0].fingerprint).to.be.a('string'); +            expect(result.signatures.signatures.good[0].valid).to.be.true; +            done(); +        }); +    }); + +    it('Successfully recognize changed cleartext', function (done) { +        const message = inputvalues.signedMessage.bad; +        context.verify(message).then(function (result){ +            expect(result.data).to.be.a('string'); +            expect(result.signatures.all_valid).to.be.false; +            expect(result.signatures.count).to.equal(1); +            expect(result.signatures.signatures.bad).to.be.an('array'); +            expect(result.signatures.signatures.bad.length).to.equal(1); +            expect(result.signatures.signatures.bad[0].fingerprint) +                .to.be.a('string'); +            expect(result.signatures.signatures.bad[0].valid) +                .to.be.false; +            done(); +        }); +    }); + +    it('Encrypt-Sign-Verify random message', function (done) { +        const message = bigString(2000); +        let fpr = inputvalues.encrypt.good.fingerprint; +        context.encrypt(message, fpr).then(function (message_enc){ +            context.sign(message_enc.data, fpr).then(function (message_encsign){ +                context.verify(message_encsign.data).then(function (result){ +                    expect(result.data).to.equal(message_enc.data); +                    expect(result.data).to.be.a('string'); +                    expect(result.signatures.all_valid).to.be.true; +                    expect(result.signatures.count).to.equal(1); +                    expect(result.signatures.signatures.good) +                        .to.be.an('array'); +                    expect(result.signatures.signatures.good.length) +                        .to.equal(1); +                    expect(result.signatures.signatures.good[0].fingerprint) +                        .to.equal(fpr); +                    expect(result.signatures.signatures.good[0].valid) +                        .to.be.true; +                    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/DemoExtension/Makefile.am b/lang/js/DemoExtension/Makefile.am new file mode 100644 index 00000000..d6e87fde --- /dev/null +++ b/lang/js/DemoExtension/Makefile.am @@ -0,0 +1,27 @@ +# Makefile.am for gpgme.js. +# Copyright (C) 2018 Intevation GmbH +# +# This file is part of gpgme.js. +# +# gpgme.js is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# gpgme.js 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +EXTRA_DIST = manifest.json \ +             popup.html \ +             entry.js \ +             maindemo.js \ +             mainui.html \ +             testicon.png \ +             ui.css diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js new file mode 100644 index 00000000..fd261a0b --- /dev/null +++ b/lang/js/DemoExtension/entry.js @@ -0,0 +1,30 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global chrome */ + +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..8d190852 --- /dev/null +++ b/lang/js/DemoExtension/maindemo.js @@ -0,0 +1,119 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global document, Gpgmejs */ + +document.addEventListener('DOMContentLoaded', function () { +    Gpgmejs.init().then(function (gpgmejs){ +        document.getElementById('buttonencrypt').addEventListener('click', +            function (){ +                let data = document.getElementById('inputtext').value; +                let keyId = document.getElementById('pubkey').value; +                gpgmejs.encrypt(data, keyId).then( +                    function (answer){ +                        if (answer.data){ +                            document.getElementById( +                                'answer').value = answer.data; +                        } +                    }, function (errormsg){ +                        alert( errormsg.message); +                    }); +            }); + +        document.getElementById('buttondecrypt').addEventListener('click', +            function (){ +                let data = document.getElementById('inputtext').value; +                gpgmejs.decrypt(data).then( +                    function (answer){ +                        if (answer.data){ +                            document.getElementById( +                                'answer').value = answer.data; +                        } +                    }, function (errormsg){ +                        alert(errormsg.message); +                    }); +            }); + +        document.getElementById('getdefaultkey').addEventListener('click', +            function (){ +                gpgmejs.Keyring.getDefaultKey().then(function (answer){ +                    document.getElementById('pubkey').value = +                        answer.fingerprint; +                }, function (errormsg){ +                    alert(errormsg.message); +                }); +            }); + +        document.getElementById('signtext').addEventListener('click', +            function (){ +                let data = document.getElementById('inputtext').value; +                let keyId = document.getElementById('pubkey').value; +                gpgmejs.sign(data, keyId).then( +                    function (answer){ +                        if (answer.data){ +                            document.getElementById( +                                'answer').value = answer.data; +                        } +                    }, function (errormsg){ +                        alert( errormsg.message); +                    }); +            }); + +        document.getElementById('verifytext').addEventListener('click', +            function (){ +                let data = document.getElementById('inputtext').value; +                gpgmejs.verify(data).then( +                    function (answer){ +                        let vals = ''; +                        if (answer.all_valid === true){ +                            vals = 'Success! '; +                        } else { +                            vals = 'Failure! '; +                        } +                        vals = vals + (answer.count - answer.failures) + 'of ' +                            + answer.count + ' signature(s) were successfully ' +                            + 'verified.\n\n' + answer.data; +                        document.getElementById('answer').value = vals; +                    }, function (errormsg){ +                        alert( errormsg.message); +                    }); +            }); +        document.getElementById('searchkey').addEventListener('click', +            function (){ +                let data = document.getElementById('inputtext').value; +                gpgmejs.Keyring.getKeys(data, true, true).then(function (keys){ +                    if (keys.length === 1){ +                        document.getElementById( +                            'pubkey').value = keys[0].fingerprint; +                    } else if (keys.length > 1) { +                        alert('The pattern was not unambigious enough for a Key. ' +                        + keys.length + ' Keys were found'); +                    } else { +                        alert('No keys found'); +                    } +                }, function (errormsg){ +                    alert( errormsg.message); +                }); +            }); +    }); +}); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html new file mode 100644 index 00000000..c773c9b9 --- /dev/null +++ b/lang/js/DemoExtension/mainui.html @@ -0,0 +1,47 @@ +<!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> +    <div> + +        <div class="left"> +            <ul> +                <li> +                    <span class="label">Input</span> +                    <textarea rows="5" cols="65" id="inputtext" wrap="hard"></textarea> +                </li> +                <li> +                    <span class="label">Fingerprint of Key to use: </span> +                    <input type="text" id="pubkey" value="" /> +                        <button id="getdefaultkey"> +                            Set to default signing key +                        </button>  +                        <button id="searchkey"> +                            Look up Key +                        </button> +                </li> +            </ul> +        </div> +        <div class="right"> +            <ul> +                    <li> +                        <span class="label">Result</span> +                        <textarea id="answer" rows="5" cols="65" wrap="hard"></textarea> +                    </li> +                </ul> +        </div> +    </div> +    <div class="center"> +            <button id="buttonencrypt">Encrypt input text</button><br> +            <button id="buttondecrypt">Decrypt input text</button><br> +            <button id="signtext">Sign input text</button> <br> +            <button id="verifytext">Verify input text</button><br> + +        </div> +</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.pngBinary files differ new file mode 100644 index 00000000..84284e0b --- /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..16dfb5ae --- /dev/null +++ b/lang/js/DemoExtension/ui.css @@ -0,0 +1,33 @@ +ul { +    list-style-type: none; +    padding-left: 0px; +} + +ul li span { +    float: left; +    width: 120px; +    margin-top: 6px; +} + +div .left { +    float: left; +    align-items: stretch; +    width: 40%; +} +div .center { +    width: 50%; +    align-content: space-between; +} + +div .center button { +    align-self: stretch; +} +div .right { +    float: right; +    align-items: stretch; +    width: 40%; +} + +div .bottom { +    clear:both; +}
\ No newline at end of file diff --git a/lang/js/Makefile.am b/lang/js/Makefile.am new file mode 100644 index 00000000..4464730f --- /dev/null +++ b/lang/js/Makefile.am @@ -0,0 +1,31 @@ +# Makefile.am for gpgme.js. +# Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik +# +# This file is part of gpgme.js. +# +# gpgme.js is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# gpgme.js 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +SUBDIRS = src BrowserTestExtension DemoExtension + +EXTRA_DIST = build_extensions.sh \ +             jsdoc.conf \ +             .eslintrc.json \ +             package.json \ +             README \ +             unittest_inputvalues.js \ +             unittests.js \ +             webpack.conf.js \ +             webpack.conf_unittests.js diff --git a/lang/js/README b/lang/js/README new file mode 100644 index 00000000..fd95cc45 --- /dev/null +++ b/lang/js/README @@ -0,0 +1,116 @@ +gpgme.js - JavaScript for GPGME +------------------------------- +Initially developed for integration with the Mailvelope Web Extension. + +Overview +-------- + +gpgme.js is a javascript library for direct use of GnuPG in browsers. +It interacts with GPGME through nativeMessaging and gpgme-json. + +It is meant to be distributed directly by its downstream users in +their extension package. As such it is not integrated in the +autotools build system. See build instructions below. + + +gpgme-json +---------- + +gpgme-json (see core src/gpgme-json.c) the json to GPGME bridge is +required as native messaging backend for gpgme.js to work. +It needs to be installed and registered as native messaging +backend with the browser. + +See gpgme-mozilla.json and gpgme-chrome.json examples in +the top level doc/examples as example manifests. + +Any web extension using gpgme.js will need to be whitelisted in the manifest +file by its id. + +Distributors are encouraged to create manifest packages for their +distributions. + + +Building gpgme.js +----------------- + +gpgme.js uses webpack, and thus depends on Node.js for building. +All dependencies will be installed (in a local subdirectory) with the command +`npm install`. + +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 and Test WebExtension: +--------------------------- + +The Demo Extension shows simple examples of the usage of gpgme.js. + +The BrowsertestExtension runs more intensive tests (using the mocha and chai +frameworks). 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 gpgme.js, +which mostly are not exported. + +The file `build_extension.sh` may serve as a pointer on how to build and +assemble these two Extensions and their dependencies. It can directly +be used in most linux systems. + +The resulting folders 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! + +For the Extensions to successfully communicate with gpgme-json, a manifest file +is needed. + +- `~/.config/chromium/NativeMessagingHosts/gpgmejson.json` + +In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json' +is needed, with the following content: + +- For Chrome/Chromium: +  ``` +  { +    "name": "gpgmejson", +    "description": "This is a test application for gpgme.js", +    "path": "/usr/bin/gpgme-json", +    "type": "stdio", +    "allowed_origins": ["chrome-extension://ExtensionIdentifier/"] +  } +  ``` +  The usual path for Linux is similar to: +  `~/.config/chromium/NativeMessagingHosts/gpgmejson.json` for +  For Windows, the path to the manifest needs to be placed in +  `HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\gpgmejson` + +  - For firefox: +  ``` +  { +    "name": "gpgmejson", +    "description": "This is a test application for gpgme.js", +    "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. + +  The manifest for linux is usually placed at: +    `~/.mozilla/native-messaging-hosts/gpgmejson.json` + + +Documentation +------------- + +The documentation can be built by jsdoc. It currently uses the command +`./node_modules/.bin/jsdoc -c jsdoc.conf`. 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/jsdoc.conf b/lang/js/jsdoc.conf new file mode 100644 index 00000000..12ae35e9 --- /dev/null +++ b/lang/js/jsdoc.conf @@ -0,0 +1,24 @@ +{ +    "tags": { +        "allowUnknownTags": false, +        "dictionaries": ["jsdoc"] +    }, +    "source": { +        "include": ["./src"], +        "includePattern": ".+\\.js(doc|x)?$", +        "excludePattern": "(^|\\/|\\\\)_" +    }, +    "opts":{ +        "destination": "./doc/", +        "recurse": true +    }, +    "sourceType": "module", +    "plugins": [], +    "templates": { +        "cleverLinks": false, +        "monospaceLinks": false, +        "default": { +            "outputSourceFiles": true +        } +    } +}
\ No newline at end of file diff --git a/lang/js/package.json b/lang/js/package.json new file mode 100644 index 00000000..54af2982 --- /dev/null +++ b/lang/js/package.json @@ -0,0 +1,17 @@ +{ +  "name": "gpgmejs", +  "version": "0.0.1-dev", +  "description": "Javascript part of the GPGME nativeMessaging integration", +  "main": "src/index.js", +  "private": true, +  "keywords": [], +  "author": "", +  "license": "LGPL-2.1+", +  "devDependencies": { +    "webpack": "^4.5.0", +    "webpack-cli": "^3.0.8", +    "chai": "^4.1.2", +    "mocha": "^5.1.1", +    "jsdoc": "^3.5.5" +  } +} diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js new file mode 100644 index 00000000..928ac681 --- /dev/null +++ b/lang/js/src/Connection.js @@ -0,0 +1,283 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/* global chrome */ + +import { permittedOperations } from './permittedOperations'; +import { gpgme_error } from './Errors'; +import { GPGME_Message, createMessage } from './Message'; +import { decode } from './Helpers'; + +/** + * A Connection handles the nativeMessaging interaction via a port. As the + * protocol only allows up to 1MB of message sent from the nativeApp to the + * browser, the connection will stay open until all parts of a communication + * are finished. For a new request, a new port will open, to avoid mixing + * contexts. + * @class + */ +export class Connection{ + +    constructor (){ +        this._connection = chrome.runtime.connectNative('gpgmejson'); +    } + +    /** +     * Immediately closes an open port. +     */ +    disconnect () { +        if (this._connection){ +            this._connection.disconnect(); +            this._connection = null; +        } +    } + + +    /** +    * @typedef {Object} backEndDetails +    * @property {String} gpgme Version number of gpgme +    * @property {Array<Object>} info Further information about the backend +    * and the used applications (Example: +    * { +    *          "protocol":     "OpenPGP", +    *          "fname":        "/usr/bin/gpg", +    *          "version":      "2.2.6", +    *          "req_version":  "1.4.0", +    *          "homedir":      "default" +    * } +    */ + +    /** +     * Retrieves the information about the backend. +     * @param {Boolean} details (optional) If set to false, the promise will +     *  just return if a connection was successful. +     * @returns {Promise<backEndDetails>|Promise<Boolean>} Details from the +     * backend +     * @async +     */ +    checkConnection (details = true){ +        const msg = createMessage('version'); +        if (details === true) { +            return this.post(msg); +        } else { +            let me = this; +            return new Promise(function (resolve) { +                Promise.race([ +                    me.post(msg), +                    new Promise(function (resolve, reject){ +                        setTimeout(function (){ +                            reject(gpgme_error('CONN_TIMEOUT')); +                        }, 500); +                    }) +                ]).then(function (){ // success +                    resolve(true); +                }, function (){ // failure +                    resolve(false); +                }); +            }); +        } +    } + +    /** +     * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It +     * resolves with the completed answer after all parts have been +     * received and reassembled, or rejects with an {@link GPGME_Error}. +     * +     * @param {GPGME_Message} message +     * @returns {Promise<Object>} The collected answer +     * @async +     */ +    post (message){ +        if (!message || !(message instanceof GPGME_Message)){ +            this.disconnect(); +            return Promise.reject(gpgme_error( +                'PARAM_WRONG', 'Connection.post')); +        } +        if (message.isComplete() !== true){ +            this.disconnect(); +            return Promise.reject(gpgme_error('MSG_INCOMPLETE')); +        } +        let chunksize = message.chunksize; +        const me = this; +        return new Promise(function (resolve, reject){ +            let answer = new Answer(message); +            let listener = function (msg) { +                if (!msg){ +                    me._connection.onMessage.removeListener(listener); +                    me._connection.disconnect(); +                    reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); +                } else { +                    let answer_result = answer.collect(msg); +                    if (answer_result !== true){ +                        me._connection.onMessage.removeListener(listener); +                        me._connection.disconnect(); +                        reject(answer_result); +                    } else { +                        if (msg.more === true){ +                            me._connection.postMessage({ +                                'op': 'getmore', +                                'chunksize': chunksize +                            }); +                        } else { +                            me._connection.onMessage.removeListener(listener); +                            me._connection.disconnect(); +                            const message = answer.getMessage(); +                            if (message instanceof Error){ +                                reject(message); +                            } else { +                                resolve(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 (){ +                            me._connection.disconnect(); +                            reject(gpgme_error('CONN_TIMEOUT')); +                        }, 5000); +                    } +                ]).then(function (result){ +                    return result; +                }, function (reject){ +                    if (!(reject instanceof Error)) { +                        me._connection.disconnect(); +                        return gpgme_error('GNUPG_ERROR', reject); +                    } else { +                        return reject; +                    } +                }); +            } +        }); +    } +} + + +/** + * A class for answer objects, checking and processing the return messages of + * the nativeMessaging communication. + * @protected + */ +class Answer{ + +    /** +     * @param {GPGME_Message} message +     */ +    constructor (message){ +        this._operation = message.operation; +        this._expected = message.expected; +        this._response_b64 = null; +    } + +    get operation (){ +        return this._operation; +    } + +    get expected (){ +        return this._expected; +    } + +    /** +     * Adds incoming base64 encoded data to the existing response +     * @param {*} msg base64 encoded data. +     * @returns {Boolean} +     * +     * @private +     */ +    collect (msg){ +        if (typeof (msg) !== 'object' || !msg.hasOwnProperty('response')) { +            return gpgme_error('CONN_UNEXPECTED_ANSWER'); +        } +        if (!this._response_b64){ +            this._response_b64 = msg.response; +            return true; +        } else { +            this._response_b64 += msg.response; +            return true; +        } +    } +    /** +     * Returns the base64 encoded answer data with the content verified +     * against {@link permittedOperations}. +     */ +    getMessage (){ +        if (this._response_b64 === null){ +            return gpgme_error('CONN_UNEXPECTED_ANSWER'); +        } +        let _decodedResponse = JSON.parse(atob(this._response_b64)); +        let _response = {}; +        let messageKeys = Object.keys(_decodedResponse); +        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 (_decodedResponse.type === 'error'){ +                    return (gpgme_error('GNUPG_ERROR', +                        decode(_decodedResponse.msg))); +                } else if (poa.type.indexOf(_decodedResponse.type) < 0){ +                    return gpgme_error('CONN_UNEXPECTED_ANSWER'); +                } +                break; +            case 'base64': +                break; +            case 'msg': +                if (_decodedResponse.type === 'error'){ +                    return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); +                } +                break; +            default: +                if (!poa.data.hasOwnProperty(key)){ +                    return gpgme_error('CONN_UNEXPECTED_ANSWER'); +                } +                if ( typeof (_decodedResponse[key]) !== poa.data[key] ){ +                    return gpgme_error('CONN_UNEXPECTED_ANSWER'); +                } +                if (_decodedResponse.base64 === true +                    && poa.data[key] === 'string' +                    && this.expected !== 'base64' +                ){ +                    _response[key] = decodeURIComponent( +                        atob(_decodedResponse[key]).split('').map( +                            function (c) { +                                return '%' + +                            ('00' + c.charCodeAt(0).toString(16)).slice(-2); +                            }).join('')); +                } else { +                    _response[key] = decode(_decodedResponse[key]); +                } +                break; +            } +        } +        return _response; +    } +} diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js new file mode 100644 index 00000000..73418028 --- /dev/null +++ b/lang/js/src/Errors.js @@ -0,0 +1,169 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/** + * Listing of all possible error codes and messages of a {@link GPGME_Error}. + */ +export const err_list = { +    // Connection +    'CONN_NO_CONNECT': { +        msg:'Connection with the nativeMessaging host could not be' +            + ' established.', +        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' +    }, +    'KEY_NOKEY': { +        msg:'This key does not exist in GPG', +        type: 'error' +    }, +    'KEY_NO_INIT': { +        msg:'This property has not been retrieved yet from GPG', +        type: 'error' +    }, +    'KEY_ASYNC_ONLY': { +        msg: 'This property cannot be used in synchronous calls', +        type: 'error' +    }, +    'KEY_NO_DEFAULT': { +        msg:'A default key could not be established. Please check yout gpg ' + +            'configuration', +        type: 'error' +    }, +    'SIG_WRONG': { +        msg:'A malformed signature was created', +        type: 'error' +    }, +    'SIG_NO_SIGS': { +        msg:'There were no signatures found', +        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 {@link GPGME_Error} 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' + * @returns {GPGME_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'){ +            // eslint-disable-next-line no-console +            // 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'); +    } +} + +/** + * An error class with additional info about the origin of the error, as string + * @property {String} code Short description of origin and type of the error + * @property {String} msg Additional info + * @class + * @protected + * @extends Error + */ +class GPGME_Error extends Error{ +    constructor (code = 'GENERIC_ERROR', msg=''){ + +        if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){ +            super(msg); +        } else if (err_list.hasOwnProperty(code)){ +            if (msg){ +                super(err_list[code].msg + '--' + msg); +            } else { +                super(err_list[code].msg); +            } +        } else { +            super(err_list['GENERIC_ERROR'].msg); +        } +        this._code = code; +    } + +    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..ba4277ab --- /dev/null +++ b/lang/js/src/Helpers.js @@ -0,0 +1,137 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +import { gpgme_error } from './Errors'; + +/** + * Tries to return an array of fingerprints, either from input fingerprints or + * from Key objects (openpgp Keys or GPGME_Keys are both accepted). + * + * @param {Object | Array<Object> | String | Array<String>} input + * @returns {Array<String>} Array of fingerprints, or an empty array + */ +export function toKeyIdArray (input){ +    if (!input){ +        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 { +                // MSG_NOT_A_FPR is just a console warning if warning enabled +                // in src/Errors.js +                gpgme_error('MSG_NOT_A_FPR'); +            } +        } else if (typeof (input[i]) === 'object'){ +            let fpr = ''; +            if (input[i].hasOwnProperty('fingerprint')){ +                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){ +        return []; +    } else { +        return result; +    } +} + +/** + * Check if values are valid hexadecimal values of a specified length + * @param {String} key input value. + * @param {int} len the expected length of the value + * @returns {Boolean} true if value passes test + * @private + */ +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 Fingerprint + *      (Hex string with a length of 40 characters) + * @param {String} value to check + * @returns {Boolean} true if value passes test + */ +export function isFingerprint (value){ +    return hextest(value, 40); +} + +/** + * check if the input is a valid gnupg long ID (Hex string with a length of 16 + * characters) + * @param {String} value to check + * @returns {Boolean} true if value passes test + */ +export function isLongId (value){ +    return hextest(value, 16); +} + +/** + * Recursively decodes input (utf8) to output (utf-16; javascript) strings + * @param {Object | Array | String} property + */ +export function decode (property){ +    if (typeof property === 'string'){ +        return decodeURIComponent(escape(property)); +    } else if (Array.isArray(property)){ +        let res = []; +        for (let arr=0; arr < property.length; arr++){ +            res.push(decode(property[arr])); +        } +        return res; +    } else if (typeof property === 'object'){ +        const keys = Object.keys(property); +        if (keys.length){ +            let res = {}; +            for (let k=0; k < keys.length; k++ ){ +                res[keys[k]] = decode(property[keys[k]]); +            } +            return res; +        } +        return property; +    } +    return property; +}
\ No newline at end of file diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js new file mode 100644 index 00000000..d0f87eda --- /dev/null +++ b/lang/js/src/Key.js @@ -0,0 +1,688 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +import { isFingerprint, isLongId } from './Helpers'; +import { gpgme_error } from './Errors'; +import { createMessage } from './Message'; + +/** + * Validates the given fingerprint and creates a new {@link GPGME_Key} + * @param {String} fingerprint + * @param {Boolean} async If True, Key properties (except fingerprint) will be + * queried from gnupg on each call, making the operation up-to-date, the + * answers will be Promises, and the performance will likely suffer + * @param {Object} data additional initial properties this Key will have. Needs + * a full object as delivered by gpgme-json + * @returns {Object} The verified and updated data + */ +export function createKey (fingerprint, async = false, data){ +    if (!isFingerprint(fingerprint) || typeof (async) !== 'boolean'){ +        throw gpgme_error('PARAM_WRONG'); +    } +    if (data !== undefined){ +        data = validateKeyData(fingerprint, data); +    } +    if (data instanceof Error){ +        throw gpgme_error('KEY_INVALID'); +    } else { +        return new GPGME_Key(fingerprint, async, data); +    } +} + +/** + * Represents the Keys as stored in the gnupg backend + * It allows to query almost all information defined in gpgme Key Objects + * Refer to {@link validKeyProperties} for available information, and the gpgme + * documentation on their meaning + * (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) + * + * @class + */ +class GPGME_Key { + +    constructor (fingerprint, async, data){ + +        /** +         * @property {Boolean} If true, most answers will be asynchronous +         */ +        this._async = async; + +        this._data = { fingerprint: fingerprint.toUpperCase() }; +        if (data !== undefined +            && data.fingerprint.toUpperCase() === this._data.fingerprint +        ) { +            this._data = data; +        } +    } + +    /** +     * Query any property of the Key listed in {@link validKeyProperties} +     * @param {String} property property to be retreived +     * @returns {Boolean| String | Date | Array | Object} +     * the value of the property. If the Key is set to Async, the value +     * will be fetched from gnupg and resolved as a Promise. If Key is not +     * async, the armored property is not available (it can still be +     * retrieved asynchronously by {@link Key.getArmor}) +     */ +    get (property) { +        if (this._async === true) { +            switch (property){ +            case 'armored': +                return this.getArmor(); +            case 'hasSecret': +                return this.getGnupgSecretState(); +            default: +                return getGnupgState(this.fingerprint, property); +            } +        } else { +            if (property === 'armored') { +                throw gpgme_error('KEY_ASYNC_ONLY'); +            } +            // eslint-disable-next-line no-use-before-define +            if (!validKeyProperties.hasOwnProperty(property)){ +                throw gpgme_error('PARAM_WRONG'); +            } else { +                return (this._data[property]); +            } +        } +    } + +    /** +     * Reloads the Key information from gnupg. This is only useful if you +     * use the GPGME_Keys cached. Note that this is a performance hungry +     * operation. If you desire more than a few refreshs, it may be +     * advisable to run {@link Keyring.getKeys} instead. +     * @returns {Promise<GPGME_Key|GPGME_Error>} +     * @async +     */ +    refreshKey () { +        let me = this; +        return new Promise(function (resolve, reject) { +            if (!me._data.fingerprint){ +                reject(gpgme_error('KEY_INVALID')); +            } +            let msg = createMessage('keylist'); +            msg.setParameter('sigs', true); +            msg.setParameter('keys', me._data.fingerprint); +            msg.post().then(function (result){ +                if (result.keys.length === 1){ +                    const newdata = validateKeyData( +                        me._data.fingerprint, result.keys[0]); +                    if (newdata instanceof Error){ +                        reject(gpgme_error('KEY_INVALID')); +                    } else { +                        me._data = newdata; +                        me.getGnupgSecretState().then(function (){ +                            me.getArmor().then(function (){ +                                resolve(me); +                            }, function (error){ +                                reject(error); +                            }); +                        }, function (error){ +                            reject(error); +                        }); +                    } +                } else { +                    reject(gpgme_error('KEY_NOKEY')); +                } +            }, function (error) { +                reject(gpgme_error('GNUPG_ERROR'), error); +            }); +        }); +    } + +    /** +     * Query the armored block of the Key directly from gnupg. Please note +     * that this will not get you any export of the secret/private parts of +     * a Key +     * @returns {Promise<String|GPGME_Error>} +     * @async +     */ +    getArmor () { +        const me = this; +        return new Promise(function (resolve, reject) { +            if (!me._data.fingerprint){ +                reject(gpgme_error('KEY_INVALID')); +            } +            let msg = createMessage('export'); +            msg.setParameter('armor', true); +            msg.setParameter('keys', me._data.fingerprint); +            msg.post().then(function (result){ +                resolve(result.data); +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * Find out if the Key is part of a Key pair including public and +     * private key(s). If you want this information about more than a few +     * Keys in synchronous mode, it may be advisable to run +     * {@link Keyring.getKeys} instead, as it performs faster in bulk +     * querying this state. +     * @returns {Promise<Boolean|GPGME_Error>} True if a private Key is +     * available in the gnupg Keyring. +     * @async +     */ +    getGnupgSecretState (){ +        const me = this; +        return new Promise(function (resolve, reject) { +            if (!me._data.fingerprint){ +                reject(gpgme_error('KEY_INVALID')); +            } else { +                let msg = createMessage('keylist'); +                msg.setParameter('keys', me._data.fingerprint); +                msg.setParameter('secret', true); +                msg.post().then(function (result){ +                    me._data.hasSecret = null; +                    if ( +                        result.keys && +                        result.keys.length === 1 && +                        result.keys[0].secret === true +                    ) { +                        me._data.hasSecret = true; +                        resolve(true); +                    } else { +                        me._data.hasSecret = false; +                        resolve(false); +                    } +                }, function (error){ +                    reject(error); +                }); +            } +        }); +    } + +    /** +     * Deletes the (public) Key from the GPG Keyring. Note that a deletion +     * of a secret key is not supported by the native backend. +     * @returns {Promise<Boolean|GPGME_Error>} Success if key was deleted, +     * rejects with a GPG error otherwise. +     */ +    delete (){ +        const me = this; +        return new Promise(function (resolve, reject){ +            if (!me._data.fingerprint){ +                reject(gpgme_error('KEY_INVALID')); +            } +            let msg = createMessage('delete'); +            msg.setParameter('key', me._data.fingerprint); +            msg.post().then(function (result){ +                resolve(result.success); +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * @returns {String} The fingerprint defining this Key. Convenience getter +     */ +    get fingerprint (){ +        return this._data.fingerprint; +    } +} + +/** + * Representing a subkey of a Key. + * @class + * @protected + */ +class GPGME_Subkey { + +    /** +     * Initializes with the json data sent by gpgme-json +     * @param {Object} data +     * @private +     */ +    constructor (data){ +        this._data = {}; +        let keys = Object.keys(data); +        const me = this; + +        /** +         * Validates a subkey property against {@link validSubKeyProperties} and +         * sets it if validation is successful +         * @param {String} property +         * @param {*} value +         * @param private +         */ +        const setProperty = function (property, value){ +            // eslint-disable-next-line no-use-before-define +            if (validSubKeyProperties.hasOwnProperty(property)){ +                // eslint-disable-next-line no-use-before-define +                if (validSubKeyProperties[property](value) === true) { +                    if (property === 'timestamp' || property === 'expires'){ +                        me._data[property] = new Date(value * 1000); +                    } else { +                        me._data[property] = value; +                    } +                } +            } +        }; +        for (let i=0; i< keys.length; i++) { +            setProperty(keys[i], data[keys[i]]); +        } +    } + +    /** +     * Fetches any information about this subkey +     * @param {String} property Information to request +     * @returns {String | Number | Date} +     */ +    get (property) { +        if (this._data.hasOwnProperty(property)){ +            return (this._data[property]); +        } +    } + +} + +/** + * Representing user attributes associated with a Key or subkey + * @class + * @protected + */ +class GPGME_UserId { + +    /** +     * Initializes with the json data sent by gpgme-json +     * @param {Object} data +     * @private +     */ +    constructor (data){ +        this._data = {}; +        const me = this; +        let keys = Object.keys(data); +        const setProperty = function (property, value){ +            // eslint-disable-next-line no-use-before-define +            if (validUserIdProperties.hasOwnProperty(property)){ +                // eslint-disable-next-line no-use-before-define +                if (validUserIdProperties[property](value) === true) { +                    if (property === 'last_update'){ +                        me._data[property] = new Date(value*1000); +                    } else { +                        me._data[property] = value; +                    } +                } +            } +        }; +        for (let i=0; i< keys.length; i++) { +            setProperty(keys[i], data[keys[i]]); +        } +    } + +    /** +     * Fetches information about the user +     * @param {String} property Information to request +     * @returns {String | Number} +     */ +    get (property) { +        if (this._data.hasOwnProperty(property)){ +            return (this._data[property]); +        } +    } + +} + +/** + * Validation definition for userIds. Each valid userId property is represented + * as a key- Value pair, with their value being a validation function to check + * against + * @protected + * @const + */ +const validUserIdProperties = { +    'revoked': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'invalid':  function (value){ +        return typeof (value) === 'boolean'; +    }, +    'uid': function (value){ +        if (typeof (value) === 'string' || value === ''){ +            return true; +        } +        return false; +    }, +    'validity': function (value){ +        if (typeof (value) === 'string'){ +            return true; +        } +        return false; +    }, +    'name': function (value){ +        if (typeof (value) === 'string' || value === ''){ +            return true; +        } +        return false; +    }, +    'email': function (value){ +        if (typeof (value) === 'string' || value === ''){ +            return true; +        } +        return false; +    }, +    'address': function (value){ +        if (typeof (value) === 'string' || value === ''){ +            return true; +        } +        return false; +    }, +    'comment': function (value){ +        if (typeof (value) === 'string' || value === ''){ +            return true; +        } +        return false; +    }, +    'origin':  function (value){ +        return Number.isInteger(value); +    }, +    'last_update':  function (value){ +        return Number.isInteger(value); +    } +}; + +/** + * Validation definition for subKeys. Each valid userId property is represented + * as a key-value pair, with the value being a validation function + * @protected + * @const + */ +const validSubKeyProperties = { +    'invalid': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_encrypt': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_sign': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_certify':  function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_authenticate':  function (value){ +        return typeof (value) === 'boolean'; +    }, +    'secret': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'is_qualified': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'is_cardkey':  function (value){ +        return typeof (value) === 'boolean'; +    }, +    'is_de_vs':  function (value){ +        return typeof (value) === 'boolean'; +    }, +    'pubkey_algo_name': function (value){ +        return typeof (value) === 'string'; +        // TODO: check against list of known?[''] +    }, +    'pubkey_algo_string': function (value){ +        return typeof (value) === 'string'; +        // TODO: check against list of known?[''] +    }, +    'keyid': function (value){ +        return isLongId(value); +    }, +    'pubkey_algo': function (value) { +        return (Number.isInteger(value) && value >= 0); +    }, +    'length': function (value){ +        return (Number.isInteger(value) && value > 0); +    }, +    'timestamp': function (value){ +        return (Number.isInteger(value) && value > 0); +    }, +    'expires': function (value){ +        return (Number.isInteger(value) && value > 0); +    } +}; + +/** + * Validation definition for Keys. Each valid Key property is represented + * as a key-value pair, with their value being a validation function. For + * details on the meanings, please refer to the gpgme documentation + * https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#Key-objects + * @param {String} fingerprint + * @param {Boolean} revoked + * @param {Boolean} expired + * @param {Boolean} disabled + * @param {Boolean} invalid + * @param {Boolean} can_encrypt + * @param {Boolean} can_sign + * @param {Boolean} can_certify + * @param {Boolean} can_authenticate + * @param {Boolean} secret + * @param {Boolean}is_qualified + * @param {String} protocol + * @param {String} issuer_serial + * @param {String} issuer_name + * @param {Boolean} chain_id + * @param {String} owner_trust + * @param {Date} last_update + * @param {String} origin + * @param {Array<GPGME_Subkey>} subkeys + * @param {Array<GPGME_UserId>} userids + * @param {Array<String>} tofu + * @param {Boolean} hasSecret + * @protected + * @const + */ +const validKeyProperties = { +    'fingerprint': function (value){ +        return isFingerprint(value); +    }, +    'revoked': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'expired': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'disabled': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'invalid': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_encrypt': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_sign': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_certify': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'can_authenticate': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'secret': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'is_qualified': function (value){ +        return typeof (value) === 'boolean'; +    }, +    'protocol': function (value){ +        return typeof (value) === 'string'; +        // TODO check for implemented ones +    }, +    'issuer_serial': function (value){ +        return typeof (value) === 'string'; +    }, +    'issuer_name': function (value){ +        return typeof (value) === 'string'; +    }, +    'chain_id': function (value){ +        return typeof (value) === 'string'; +    }, +    'owner_trust': function (value){ +        return typeof (value) === 'string'; +    }, +    'last_update': function (value){ +        return (Number.isInteger(value)); +        // TODO undefined/null possible? +    }, +    'origin': function (value){ +        return (Number.isInteger(value)); +    }, +    'subkeys': function (value){ +        return (Array.isArray(value)); +    }, +    'userids': function (value){ +        return (Array.isArray(value)); +    }, +    'tofu': function (value){ +        return (Array.isArray(value)); +    }, +    'hasSecret': function (value){ +        return typeof (value) === 'boolean'; +    } + +}; + +/** +* sets the Key data in bulk. It can only be used from inside a Key, either +* during construction or on a refresh callback. +* @param {Object} key the original internal key data. +* @param {Object} data Bulk set the data for this key, with an Object structure +* as sent by gpgme-json. +* @returns {Object|GPGME_Error} the changed data after values have been set, +* an error if something went wrong. +* @private +*/ +function validateKeyData (fingerprint, data){ +    const key = {}; +    if (!fingerprint || typeof (data) !== 'object' || !data.fingerprint +     || fingerprint !== data.fingerprint.toUpperCase() +    ){ +        return gpgme_error('KEY_INVALID'); +    } +    let props = Object.keys(data); +    for (let i=0; i< props.length; i++){ +        if (!validKeyProperties.hasOwnProperty(props[i])){ +            return gpgme_error('KEY_INVALID'); +        } +        // running the defined validation function +        if (validKeyProperties[props[i]](data[props[i]]) !== true ){ +            return gpgme_error('KEY_INVALID'); +        } +        switch (props[i]){ +        case 'subkeys': +            key.subkeys = []; +            for (let i=0; i< data.subkeys.length; i++) { +                key.subkeys.push( +                    new GPGME_Subkey(data.subkeys[i])); +            } +            break; +        case 'userids': +            key.userids = []; +            for (let i=0; i< data.userids.length; i++) { +                key.userids.push( +                    new GPGME_UserId(data.userids[i])); +            } +            break; +        case 'last_update': +            key[props[i]] = new Date( data[props[i]] * 1000 ); +            break; +        default: +            key[props[i]] = data[props[i]]; +        } +    } +    return key; +} + +/** + * Fetches and sets properties from gnupg + * @param {String} fingerprint + * @param {String} property to search for. + * @private + * @async + */ +function getGnupgState (fingerprint, property){ +    return new Promise(function (resolve, reject) { +        if (!isFingerprint(fingerprint)) { +            reject(gpgme_error('KEY_INVALID')); +        } else { +            let msg = createMessage('keylist'); +            msg.setParameter('keys', fingerprint); +            msg.post().then(function (res){ +                if (!res.keys || res.keys.length !== 1){ +                    reject(gpgme_error('KEY_INVALID')); +                } else { +                    const key = res.keys[0]; +                    let result; +                    switch (property){ +                    case 'subkeys': +                        result = []; +                        if (key.subkeys.length){ +                            for (let i=0; i < key.subkeys.length; i++) { +                                result.push( +                                    new GPGME_Subkey(key.subkeys[i])); +                            } +                        } +                        resolve(result); +                        break; +                    case 'userids': +                        result = []; +                        if (key.userids.length){ +                            for (let i=0; i< key.userids.length; i++) { +                                result.push( +                                    new GPGME_UserId(key.userids[i])); +                            } +                        } +                        resolve(result); +                        break; +                    case 'last_update': +                        if (key.last_update === undefined){ +                            reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); +                        } else if (key.last_update !== null){ +                            resolve(new Date( key.last_update * 1000)); +                        } else { +                            resolve(null); +                        } +                        break; +                    default: +                        if (!validKeyProperties.hasOwnProperty(property)){ +                            reject(gpgme_error('PARAM_WRONG')); +                        } else { +                            if (key.hasOwnProperty(property)){ +                                resolve(key[property]); +                            } else { +                                reject(gpgme_error( +                                    'CONN_UNEXPECTED_ANSWER')); +                            } +                        } +                        break; +                    } +                } +            }, function (error){ +                reject(gpgme_error(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..cb053ba1 --- /dev/null +++ b/lang/js/src/Keyring.js @@ -0,0 +1,435 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + + +import { createMessage } from './Message'; +import { createKey } from './Key'; +import { isFingerprint } from './Helpers'; +import { gpgme_error } from './Errors'; + +/** + * This class offers access to the gnupg keyring + */ +export class GPGME_Keyring { + +    /** +     * Queries Keys (all Keys or a subset) from gnupg. +     * +     * @param {String | Array<String>} pattern (optional) A pattern to +     * search for in userIds or KeyIds. +     * @param {Boolean} prepare_sync (optional) if set to true, most data +     * (with the exception of armored Key blocks) will be cached for the +     * Keys. This enables direct, synchronous use of these properties for +     * all keys. It does not check for changes on the backend. The cached +     * information can be updated with the {@link Key.refresh} method. +     * @param {Boolean} search (optional) retrieve Keys from external +     * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) +     * @returns {Promise<Array<GPGME_Key>>} +     * @static +     * @async +     */ +    getKeys (pattern, prepare_sync=false, search=false){ +        return new Promise(function (resolve, reject) { +            let msg = createMessage('keylist'); +            if (pattern !== undefined && pattern !== null){ +                msg.setParameter('keys', pattern); +            } +            msg.setParameter('sigs', true); +            if (search === true){ +                msg.setParameter('locate', true); +            } +            msg.post().then(function (result){ +                let resultset = []; +                if (result.keys.length === 0){ +                    resolve([]); +                } else { +                    let secondrequest; +                    if (prepare_sync === true) { +                        secondrequest = function () { +                            let msg2 = createMessage('keylist'); +                            if (pattern){ +                                msg2.setParameter('keys', pattern); +                            } +                            msg2.setParameter('secret', true); +                            return msg2.post(); +                        }; +                    } else { +                        secondrequest = function () { +                            return Promise.resolve(true); +                        }; +                    } +                    secondrequest().then(function (answer) { +                        for (let i=0; i < result.keys.length; i++){ +                            if (prepare_sync === true){ +                                if (answer && answer.keys) { +                                    for (let j=0; +                                        j < answer.keys.length; j++ ){ +                                        const a = answer.keys[j]; +                                        const b = result.keys[i]; +                                        if ( +                                            a.fingerprint === b.fingerprint +                                        ) { +                                            if (a.secret === true){ +                                                b.hasSecret = true; +                                            } else { +                                                b.hasSecret = false; +                                            } +                                            break; +                                        } +                                    } +                                } +                            } +                            let k = createKey(result.keys[i].fingerprint, +                                !prepare_sync, result.keys[i]); +                            resultset.push(k); +                        } +                        resolve(resultset); +                    }, function (error){ +                        reject(error); +                    }); +                } +            }); +        }); +    } + +    /** +     * @typedef {Object} exportResult The result of a getKeysArmored +     * operation. +     * @property {String} armored The public Key(s) as armored block. Note +     * that the result is one armored block, and not a block per key. +     * @property {Array<String>} secret_fprs (optional) list of +     * fingerprints for those Keys that also have a secret Key available in +     * gnupg. The secret key will not be exported, but the fingerprint can +     * be used in operations needing a secret key. +     */ + +    /** +     * Fetches the armored public Key blocks for all Keys matching the +     * pattern (if no pattern is given, fetches all keys known to gnupg). +     * @param {String|Array<String>} pattern (optional) The Pattern to +     * search for +     * @param {Boolean} with_secret_fpr (optional) also return a list of +     * fingerprints for the keys that have a secret key available +     * @returns {Promise<exportResult|GPGME_Error>} Object containing the +     * armored Key(s) and additional information. +     * @static +     * @async +     */ +    getKeysArmored (pattern, with_secret_fpr) { +        return new Promise(function (resolve, reject) { +            let msg = createMessage('export'); +            msg.setParameter('armor', true); +            if (with_secret_fpr === true) { +                msg.setParameter('with-sec-fprs', true); +            } +            if (pattern !== undefined && pattern !== null){ +                msg.setParameter('keys', pattern); +            } +            msg.post().then(function (answer){ +                const result = { armored: answer.data }; +                if (with_secret_fpr === true +                    && answer.hasOwnProperty('sec-fprs') +                ) { +                    result.secret_fprs = answer['sec-fprs']; +                } +                resolve(result); +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * Returns the Key used by default in gnupg. +     * (a.k.a. 'primary Key or 'main key'). +     * It looks up the gpg configuration if set, or the first key that +     * contains a secret key. +     * +     * @returns {Promise<GPGME_Key|GPGME_Error>} +     * @async +     * @static +     */ +    getDefaultKey (prepare_sync = false) { +        let me = this; +        return new Promise(function (resolve, reject){ +            let msg = createMessage('config_opt'); +            msg.setParameter('component', 'gpg'); +            msg.setParameter('option', 'default-key'); +            msg.post().then(function (resp){ +                if (resp.option !== undefined +                    && resp.option.hasOwnProperty('value') +                    && resp.option.value.length === 1 +                    && resp.option.value[0].hasOwnProperty('string') +                    && typeof (resp.option.value[0].string) === 'string'){ +                    me.getKeys(resp.option.value[0].string, true).then( +                        function (keys){ +                            if (keys.length === 1){ +                                resolve(keys[0]); +                            } else { +                                reject(gpgme_error('KEY_NO_DEFAULT')); +                            } +                        }, function (error){ +                            reject(error); +                        }); +                } else { +                    let msg = createMessage('keylist'); +                    msg.setParameter('secret', true); +                    msg.post().then(function (result){ +                        if (result.keys.length === 0){ +                            reject(gpgme_error('KEY_NO_DEFAULT')); +                        } else { +                            for (let i=0; i< result.keys.length; i++ ) { +                                if (result.keys[i].invalid === false) { +                                    let k = createKey( +                                        result.keys[i].fingerprint, +                                        !prepare_sync, +                                        result.keys[i]); +                                    resolve(k); +                                    break; +                                } else if (i === result.keys.length - 1){ +                                    reject(gpgme_error('KEY_NO_DEFAULT')); +                                } +                            } +                        } +                    }, function (error){ +                        reject(error); +                    }); +                } +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * @typedef {Object} importResult The result of a Key update +     * @property {Object} summary Numerical summary of the result. See the +     * feedbackValues variable for available Keys values and the gnupg +     * documentation. +     * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html +     * for details on their meaning. +     * @property {Array<importedKeyResult>} Keys Array of Object containing +     * GPGME_Keys with additional import information +     * +     */ + +    /** +     * @typedef {Object} importedKeyResult +     * @property {GPGME_Key} key The resulting key +     * @property {String} status: +     *  'nochange' if the Key was not changed, +     *  'newkey' if the Key was imported in gpg, and did not exist +     *    previously, +     *  'change' if the key existed, but details were updated. For details, +     *    Key.changes is available. +     * @property {Boolean} changes.userId Changes in userIds +     * @property {Boolean} changes.signature Changes in signatures +     * @property {Boolean} changes.subkey Changes in subkeys +     */ + +    /** +     * Import an armored Key block into gnupg. Note that this currently +     * will not succeed on private Key blocks. +     * @param {String} armored Armored Key block of the Key(s) to be +     * imported into gnupg +     * @param {Boolean} prepare_sync prepare the keys for synched use +     * (see {@link getKeys}). +     * @returns {Promise<importResult>} A summary and Keys considered. +     * @async +     * @static +     */ +    importKey (armored, prepare_sync) { +        let feedbackValues = ['considered', 'no_user_id', 'imported', +            'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', +            'new_signatures', 'new_revocations', 'secret_read', +            'secret_imported', 'secret_unchanged', 'skipped_new_keys', +            'not_imported', 'skipped_v3_keys']; +        if (!armored || typeof (armored) !== 'string'){ +            return Promise.reject(gpgme_error('PARAM_WRONG')); +        } +        let me = this; +        return new Promise(function (resolve, reject){ +            let msg = createMessage('import'); +            msg.setParameter('data', armored); +            msg.post().then(function (response){ +                let infos = {}; +                let fprs = []; +                let summary = {}; +                for (let i=0; i < feedbackValues.length; i++ ){ +                    summary[feedbackValues[i]] = +                        response.result[feedbackValues[i]]; +                } +                if (!response.result.hasOwnProperty('imports') || +                    response.result.imports.length === 0 +                ){ +                    resolve({ Keys:[],summary: summary }); +                    return; +                } +                for (let res=0; res<response.result.imports.length; res++){ +                    let result = response.result.imports[res]; +                    let status = ''; +                    if (result.status === 0){ +                        status = 'nochange'; +                    } else if ((result.status & 1) === 1){ +                        status = 'newkey'; +                    } else { +                        status = 'change'; +                    } +                    let changes = {}; +                    changes.userId = (result.status & 2) === 2; +                    changes.signature = (result.status & 4) === 4; +                    changes.subkey = (result.status & 8) === 8; +                    // 16 new secret key: not implemented + +                    fprs.push(result.fingerprint); +                    infos[result.fingerprint] = { +                        changes: changes, +                        status: status +                    }; +                } +                let resultset = []; +                if (prepare_sync === true){ +                    me.getKeys(fprs, true).then(function (result){ +                        for (let i=0; i < result.length; i++) { +                            resultset.push({ +                                key: result[i], +                                changes: +                                    infos[result[i].fingerprint].changes, +                                status: infos[result[i].fingerprint].status +                            }); +                        } +                        resolve({ Keys:resultset,summary: summary }); +                    }, function (error){ +                        reject(error); +                    }); +                } else { +                    for (let i=0; i < fprs.length; i++) { +                        resultset.push({ +                            key: createKey(fprs[i]), +                            changes: infos[fprs[i]].changes, +                            status: infos[fprs[i]].status +                        }); +                    } +                    resolve({ Keys:resultset,summary:summary }); +                } + +            }, function (error){ +                reject(error); +            }); + + +        }); + + +    } + +    /** +     * Convenience function for deleting a Key. See {@link Key.delete} for +     * further information about the return values. +     * @param {String} fingerprint +     * @returns {Promise<Boolean|GPGME_Error>} +     * @async +     * @static +     */ +    deleteKey (fingerprint){ +        if (isFingerprint(fingerprint) === true) { +            let key = createKey(fingerprint); +            return key.delete(); +        } else { +            return Promise.reject(gpgme_error('KEY_INVALID')); +        } +    } + +    /** +     * Generates a new Key pair directly in gpg, and returns a GPGME_Key +     * representing that Key. Please note that due to security concerns, +     * secret Keys can not be deleted or exported from inside gpgme.js. +     * +     * @param {String} userId The user Id, e.g. 'Foo Bar <[email protected]>' +     * @param {String} algo (optional) algorithm (and optionally key size) +     * to be used. See {@link supportedKeyAlgos} below for supported +     * values. If ommitted, 'default' is used. +     * @param {Number} expires (optional) Expiration time in seconds from now. +     * If not set or set to 0, expiration will be 'never' +     * @param {String} subkey_algo (optional) algorithm of the encryption +     * subkey. If ommited the same as algo is used. +     * +     * @return {Promise<Key|GPGME_Error>} +     * @async +     */ +    generateKey (userId, algo = 'default', expires, subkey_algo){ +        if ( +            typeof (userId) !== 'string' || +            // eslint-disable-next-line no-use-before-define +            supportedKeyAlgos.indexOf(algo) < 0 || +            (expires && !( Number.isInteger(expires) || expires < 0 )) +        ){ +            return Promise.reject(gpgme_error('PARAM_WRONG')); +        } +        // eslint-disable-next-line no-use-before-define +        if (subkey_algo && supportedKeyAlgos.indexOf(subkey_algo) < 0 ){ +            return Promise.reject(gpgme_error('PARAM_WRONG')); +        } +        let me = this; +        return new Promise(function (resolve, reject){ +            let msg = createMessage('createkey'); +            msg.setParameter('userid', userId); +            msg.setParameter('algo', algo ); +            if (subkey_algo) { +                msg.setParameter('subkey-algo', subkey_algo ); +            } +            if (expires){ +                msg.setParameter('expires', expires); +            } else { +                msg.setParameter('expires', 0); +            } +            msg.post().then(function (response){ +                me.getKeys(response.fingerprint, true).then( +                    // TODO prepare_sync? +                    function (result){ +                        resolve(result); +                    }, function (error){ +                        reject(error); +                    }); +            }, function (error) { +                reject(error); +            }); +        }); +    } +} + + +/** + * List of algorithms supported for key generation. Please refer to the gnupg + * documentation for details + */ +const supportedKeyAlgos = [ +    'default', +    'rsa', 'rsa2048', 'rsa3072', 'rsa4096', +    'dsa', 'dsa2048', 'dsa3072', 'dsa4096', +    'elg', 'elg2048', 'elg3072', 'elg4096', +    'ed25519', +    'cv25519', +    'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1', +    'NIST P-256', 'NIST P-384', 'NIST P-521' +];
\ No newline at end of file diff --git a/lang/js/src/Makefile.am b/lang/js/src/Makefile.am new file mode 100644 index 00000000..dc58fd31 --- /dev/null +++ b/lang/js/src/Makefile.am @@ -0,0 +1,30 @@ +# Makefile.am for gpgme.js. +# Copyright (C) 2018 Intevation GmbH +# +# This file is part of GPGME. +# +# gpgme.js is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# gpgme.js 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +EXTRA_DIST = Connection.js \ +             Errors.js \ +             gpgmejs.js \ +             Helpers.js \ +             index.js \ +             Key.js \ +             Keyring.js \ +             Message.js \ +             permittedOperations.js \ +             Signature.js diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js new file mode 100644 index 00000000..b83caf6d --- /dev/null +++ b/lang/js/src/Message.js @@ -0,0 +1,239 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +import { permittedOperations } from './permittedOperations'; +import { gpgme_error } from './Errors'; +import { Connection } from './Connection'; + +/** + * Initializes a message for gnupg, validating the message's purpose with + *   {@link permittedOperations} first + * @param {String} operation + * @returns {GPGME_Message} The Message object + */ +export function createMessage (operation){ +    if (typeof (operation) !== 'string'){ +        throw gpgme_error('PARAM_WRONG'); +    } +    if (permittedOperations.hasOwnProperty(operation)){ +        return new GPGME_Message(operation); +    } else { +        throw gpgme_error('MSG_WRONG_OP'); +    } +} + +/** + * A Message collects, validates and handles all information required to + * successfully establish a meaningful communication with gpgme-json via + * {@link Connection.post}. The definition on which communication is available + * can be found in {@link permittedOperations}. + * @class + */ +export class GPGME_Message { + +    constructor (operation){ +        this._msg = { +            op: operation, +            chunksize: 1023* 1024 +        }; +        this._expected = null; +    } + +    get operation (){ +        return this._msg.op; +    } + +    set expected (value){ +        if (value === 'base64'){ +            this._expected = value; +        } +    } + +    get expected () { +        return this._expected; +    } +    /** +     * The maximum size of responses from gpgme in bytes. As of July 2018, +     * most browsers will only accept answers up to 1 MB of size. +     * Everything above that threshold will not pass through +     * nativeMessaging; answers that are larger need to be sent in parts. +     * The lower limit is set to 10 KB. Messages smaller than the threshold +     * will not encounter problems, larger messages will be received in +     * chunks. If the value is not explicitly specified, 1023 KB is used. +     */ +    set chunksize (value){ +        if ( +            Number.isInteger(value) && +            value > 10 * 1024 && +            value <= 1024 * 1024 +        ){ +            this._msg.chunksize = value; +        } +    } + +    get chunksize (){ +        return this._msg.chunksize; +    } + +    /** +     * 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; +        } +    } + +    /** +     * Sets a parameter for the message. It validates with +     *      {@link permittedOperations} +     * @param {String} param Parameter to set +     * @param {any} value Value to set +     * @returns {Boolean} If the parameter was set successfully +     */ +    setParameter ( param,value ){ +        if (!param || typeof (param) !== 'string'){ +            throw gpgme_error('PARAM_WRONG'); +        } +        let po = permittedOperations[this._msg.op]; +        if (!po){ +            throw 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 { +            throw gpgme_error('PARAM_WRONG'); +        } +        // check incoming value for correctness +        let checktype = function (val){ +            switch (typeof (val)){ +            case 'string': +                if (poparam.allowed.indexOf(typeof (val)) >= 0 +                        && val.length > 0) { +                    return true; +                } +                throw gpgme_error('PARAM_WRONG'); +            case 'number': +                if ( +                    poparam.allowed.indexOf('number') >= 0 +                        && isNaN(value) === false){ +                    return true; +                } +                throw gpgme_error('PARAM_WRONG'); + +            case 'boolean': +                if (poparam.allowed.indexOf('boolean') >= 0){ +                    return true; +                } +                throw gpgme_error('PARAM_WRONG'); +            case 'object': +                if (Array.isArray(val)){ +                    if (poparam.array_allowed !== true){ +                        throw 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; +                    } +                    throw gpgme_error('PARAM_WRONG'); +                } else { +                    throw gpgme_error('PARAM_WRONG'); +                } +                break; +            default: +                throw 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, that is +     * all 'required' parameters according to {@link permittedOperations}. +     * @returns {Boolean} true if message is complete. +     */ +    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){ +                return false; +            } +        } +        return true; +    } +    /** +     * Sends the Message via nativeMessaging and resolves with the answer. +     * @returns {Promise<Object|GPGME_Error>} +     * @async +     */ +    post (){ +        let me = this; +        return new Promise(function (resolve, reject) { +            if (me.isComplete() === true) { + +                let conn  = new Connection; +                conn.post(me).then(function (response) { +                    resolve(response); +                }, function (reason) { +                    reject(reason); +                }); +            } +            else { +                reject(gpgme_error('MSG_INCOMPLETE')); +            } +        }); +    } + +} diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js new file mode 100644 index 00000000..a6539048 --- /dev/null +++ b/lang/js/src/Signature.js @@ -0,0 +1,200 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ +import { gpgme_error } from './Errors'; + +/** + * Validates an object containing a signature, as sent by the nativeMessaging + * interface + * @param {Object} sigObject Object as returned by gpgme-json. The definition + * of the expected values are to be found in {@link expKeys}, {@link expSum}, + * {@link expNote}. + * @returns {GPGME_Signature|GPGME_Error} Signature Object + */ +export function createSignature (sigObject){ +    if ( +        typeof (sigObject) !=='object' || +        !sigObject.hasOwnProperty('summary') || +        !sigObject.hasOwnProperty('fingerprint') || +        !sigObject.hasOwnProperty('timestamp') +        // TODO check if timestamp is mandatory in specification +    ){ +        return gpgme_error('SIG_WRONG'); +    } +    let keys = Object.keys(sigObject); +    for (let i=0; i< keys.length; i++){ +        // eslint-disable-next-line no-use-before-define +        if ( typeof (sigObject[keys[i]]) !== expKeys[keys[i]] ){ +            return gpgme_error('SIG_WRONG'); +        } +    } +    let sumkeys = Object.keys(sigObject.summary); +    for (let i=0; i< sumkeys.length; i++){ +        // eslint-disable-next-line no-use-before-define +        if ( typeof (sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){ +            return gpgme_error('SIG_WRONG'); +        } +    } +    if (sigObject.hasOwnProperty('notations')){ +        if (!Array.isArray(sigObject.notations)){ +            return gpgme_error('SIG_WRONG'); +        } +        for (let i=0; i < sigObject.notations.length; i++){ +            let notation = sigObject.notations[i]; +            let notekeys = Object.keys(notation); +            for (let j=0; j < notekeys.length; j++){ +                // eslint-disable-next-line no-use-before-define +                if ( typeof (notation[notekeys[j]]) !== expNote[notekeys[j]] ){ +                    return gpgme_error('SIG_WRONG'); +                } +            } +        } +    } +    return new GPGME_Signature(sigObject); +} + + +/** + * Representing the details of a signature. The full details as given by + * gpgme-json can be read from the _rawSigObject. + * + * Note to reviewers: This class should be read only except via + * {@link createSignature} + * @protected + * @class + */ +class GPGME_Signature { + +    constructor (sigObject){ +        this._rawSigObject = sigObject; +    } +    get fingerprint (){ +        if (!this._rawSigObject.fingerprint){ +            return gpgme_error('SIG_WRONG'); +        } else { +            return this._rawSigObject.fingerprint; +        } +    } + +    /** +     * The expiration of this Signature as Javascript date, or null if +     * signature does not expire +     * @returns {Date | null} +     */ +    get expiration (){ +        if (!this._rawSigObject.exp_timestamp){ +            return null; +        } +        return new Date(this._rawSigObject.exp_timestamp* 1000); +    } + +    /** +     * The creation date of this Signature in Javascript Date +     * @returns {Date} +     */ +    get timestamp (){ +        return new Date(this._rawSigObject.timestamp * 1000); +    } + +    /** +     * The overall validity of the key. If false, errorDetails may contain +     * additional information. +     */ +    get valid () { +        if (this._rawSigObject.summary.valid === true){ +            return true; +        } else { +            return false; +        } +    } + +    /** +     * gives more information on non-valid signatures. Refer to the gpgme +     * docs https://www.gnupg.org/documentation/manuals/gpgme/Verify.html +     * for details on the values. +     * @returns {Object} Object with boolean properties +     */ +    get errorDetails (){ +        let properties = ['revoked', 'key-expired', 'sig-expired', +            'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', +            'sys-error']; +        let result = {}; +        for (let i=0; i< properties.length; i++){ +            if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ +                result[properties[i]] = this._rawSigObject[properties[i]]; +            } +        } +        return result; +    } +} + +/** + * Keys and their value's type for the signature Object + */ +const expKeys = { +    'wrong_key_usage': 'boolean', +    'chain_model': 'boolean', +    'summary': 'object', +    'is_de_vs': 'boolean', +    'status_string':'string', +    'fingerprint':'string', +    'validity_string': 'string', +    'pubkey_algo_name':'string', +    'hash_algo_name':'string', +    'pka_address':'string', +    'status_code':'number', +    'timestamp':'number', +    'exp_timestamp':'number', +    'pka_trust':'number', +    'validity':'number', +    'validity_reason':'number', +    'notations': 'object' +}; + +/** + * Keys and their value's type for the summary + */ +const expSum = { +    'valid': 'boolean', +    'green': 'boolean', +    'red': 'boolean', +    'revoked': 'boolean', +    'key-expired': 'boolean', +    'sig-expired': 'boolean', +    'key-missing': 'boolean', +    'crl-missing': 'boolean', +    'crl-too-old': 'boolean', +    'bad-policy': 'boolean', +    'sys-error': 'boolean', +    'sigsum': 'object' +}; + +/** + * Keys and their value's type for notations objects + */ +const expNote = { +    'human_readable': 'boolean', +    'critical':'boolean', +    'name': 'string', +    'value': 'string', +    'flags': 'number' +}; diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js new file mode 100644 index 00000000..7692298f --- /dev/null +++ b/lang/js/src/gpgmejs.js @@ -0,0 +1,391 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + + +import { GPGME_Message, createMessage } from './Message'; +import { toKeyIdArray } from './Helpers'; +import { gpgme_error } from './Errors'; +import { GPGME_Keyring } from './Keyring'; +import { createSignature } from './Signature'; + +/** + * @typedef {Object} decrypt_result + * @property {String} data The decrypted data + * @property {Boolean} base64 indicating whether data is base64 encoded. + * @property {Boolean} is_mime (optional) the data claims to be a MIME + * object. + * @property {String} file_name (optional) the original file name + * @property {signatureDetails} signatures Verification details for + * signatures + */ + +/** + * @typedef {Object} signatureDetails + * @property {Boolean} all_valid Summary if all signatures are fully valid + * @property {Number} count Number of signatures found + * @property {Number} failures Number of invalid signatures + * @property {Array<GPGME_Signature>} signatures.good All valid signatures + * @property {Array<GPGME_Signature>} signatures.bad All invalid signatures + */ + +/** + * @typedef {Object} encrypt_result The result of an encrypt operation + * @property {String} data The encrypted message + * @property {Boolean} base64 Indicating whether data is base64 encoded. + */ + +/** + * @typedef { GPGME_Key | String | Object } inputKeys + * Accepts different identifiers of a gnupg Key that can be parsed by + * {@link toKeyIdArray}. Expected inputs are: One or an array of + * GPGME_Keys; one or an array of fingerprint strings; one or an array of + * openpgpjs Key objects. + */ + +/** + * @typedef {Object} signResult The result of a signing operation + * @property {String} data The resulting data. Includes the signature in + *  clearsign mode + * @property {String} signature The detached signature (if in detached mode) + */ + +/** @typedef {Object} verifyResult The result of a verification + * @property {Boolean} data: The verified data + * @property {Boolean} is_mime (optional) the data claims to be a MIME + * object. + * @property {String} file_name (optional) the original file name + * @property {signatureDetails} signatures Verification details for + * signatures + */ + +/** + * The main entry point for gpgme.js. + * @class + */ +export class GpgME { + +    constructor (){ +        this._Keyring = null; +    } + +    /** +     * setter for {@link setKeyring}. +     * @param {GPGME_Keyring} keyring A Keyring to use +     */ +    set Keyring (keyring){ +        if (keyring && keyring instanceof GPGME_Keyring){ +            this._Keyring = keyring; +        } +    } +    /** +     * Accesses the {@link GPGME_Keyring}. +     */ +    get Keyring (){ +        if (!this._Keyring){ +            this._Keyring = new GPGME_Keyring; +        } +        return this._Keyring; +    } + +    /** +     * Encrypt (and optionally sign) data +     * @param {String|Object} data text/data to be encrypted as String. Also +     * accepts Objects with a getText method +     * @param {inputKeys} publicKeys +     * Keys used to encrypt the message +     * @param {inputKeys} secretKeys (optional) Keys used to sign the +     * message. If Keys are present, the  operation requested is assumed +     * to be 'encrypt and sign' +     * @param {Boolean} base64 (optional) The data will be interpreted as +     * base64 encoded data. +     * @param {Boolean} armor (optional) Request the output as armored +     * block. +     * @param {Boolean} wildcard (optional) If true, recipient information +     * will not be added to the message. +     * @param {Object} additional use additional valid gpg options as +     * defined in {@link permittedOperations} +     * @returns {Promise<encrypt_result>} Object containing the encrypted +     * message and additional info. +     * @async +     */ +    encrypt (data, publicKeys, secretKeys, base64=false, armor=true, +        wildcard=false, additional = {}){ +        let msg = createMessage('encrypt'); +        if (msg instanceof Error){ +            return Promise.reject(msg); +        } +        msg.setParameter('armor', armor); +        msg.setParameter('always-trust', true); +        if (base64 === true) { +            msg.setParameter('base64', true); +        } +        let pubkeys = toKeyIdArray(publicKeys); +        msg.setParameter('keys', pubkeys); +        let sigkeys = toKeyIdArray(secretKeys); +        if (sigkeys.length > 0) { +            msg.setParameter('signing_keys', sigkeys); +        } +        putData(msg, data); +        if (wildcard === true){ +            msg.setParameter('throw-keyids', true); +        } +        if (additional){ +            let additional_Keys = Object.keys(additional); +            for (let k = 0; k < additional_Keys.length; k++) { +                try { +                    msg.setParameter(additional_Keys[k], +                        additional[additional_Keys[k]]); +                } +                catch (error){ +                    return Promise.reject(error); +                } +            } +        } +        if (msg.isComplete() === true){ +            return msg.post(); +        } else { +            return Promise.reject(gpgme_error('MSG_INCOMPLETE')); +        } +    } + +    /** +    * Decrypts a Message +    * @param {String|Object} data text/data to be decrypted. Accepts +    * Strings and Objects with a getText method +    * @param {Boolean} base64 (optional) false if the data is an armored +    * block, true if it is base64 encoded binary data +    * @returns {Promise<decrypt_result>} Decrypted Message and information +    * @async +    */ +    decrypt (data, base64=false){ +        if (data === undefined){ +            return Promise.reject(gpgme_error('MSG_EMPTY')); +        } +        let msg = createMessage('decrypt'); + +        if (msg instanceof Error){ +            return Promise.reject(msg); +        } +        if (base64 === true){ +            msg.setParameter('base64', true); +        } +        putData(msg, data); +        return new Promise(function (resolve, reject){ +            msg.post().then(function (result){ +                let _result = { data: result.data }; +                _result.base64 = result.base64 ? true: false; +                if (result.hasOwnProperty('dec_info')){ +                    _result.is_mime = result.dec_info.is_mime ? true: false; +                    if (result.dec_info.file_name) { +                        _result.file_name = result.dec_info.file_name; +                    } +                } +                if (!result.file_name) { +                    _result.file_name = null; +                } +                if (result.hasOwnProperty('info') +                    && result.info.hasOwnProperty('signatures') +                    && Array.isArray(result.info.signatures) +                ) { +                    _result.signatures = collectSignatures( +                        result.info.signatures); +                } +                if (_result.signatures instanceof Error){ +                    reject(_result.signatures); +                } else { +                    resolve(_result); +                } +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * Sign a Message +     * @param {String|Object} data text/data to be signed. Accepts Strings +     * and Objects with a getText method. +     * @param {inputKeys} keys The key/keys to use for signing +     * @param {String} mode The signing mode. Currently supported: +     *  'clearsign':The Message is embedded into the signature; +     *  'detached': The signature is stored separately +     * @param {Boolean} base64 input is considered base64 +     * @returns {Promise<signResult>} +     * @async +     */ +    sign (data, keys, mode='clearsign', base64=false) { +        if (data === undefined){ +            return Promise.reject(gpgme_error('MSG_EMPTY')); +        } +        let key_arr = toKeyIdArray(keys); +        if (key_arr.length === 0){ +            return Promise.reject(gpgme_error('MSG_NO_KEYS')); +        } +        let msg = createMessage('sign'); + +        msg.setParameter('keys', key_arr); +        if (base64 === true){ +            msg.setParameter('base64', true); +        } +        msg.setParameter('mode', mode); +        putData(msg, data); +        return new Promise(function (resolve,reject) { +            if (mode ==='detached'){ +                msg.expected ='base64'; +            } +            msg.post().then( function (message) { +                if (mode === 'clearsign'){ +                    resolve({ +                        data: message.data } +                    ); +                } else if (mode === 'detached') { +                    resolve({ +                        data: data, +                        signature: message.data +                    }); +                } +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * Verifies data. +     * @param {String|Object} data text/data to be verified. Accepts Strings +     * and Objects with a getText method +     * @param {String} (optional) A detached signature. If not present, +     * opaque mode is assumed +     * @param {Boolean} (optional) Data and signature are base64 encoded +     * @returns {Promise<verifyResult>} +     *@async +    */ +    verify (data, signature, base64 = false){ +        let msg = createMessage('verify'); +        let dt = putData(msg, data); +        if (dt instanceof Error){ +            return Promise.reject(dt); +        } +        if (signature){ +            if (typeof (signature)!== 'string'){ +                return Promise.reject(gpgme_error('PARAM_WRONG')); +            } else { +                msg.setParameter('signature', signature); +            } +        } +        if (base64 === true){ +            msg.setParameter('base64', true); +        } +        return new Promise(function (resolve, reject){ +            msg.post().then(function (message){ +                if (!message.info || !message.info.signatures){ +                    reject(gpgme_error('SIG_NO_SIGS')); +                } else { +                    let _result = { +                        signatures: collectSignatures(message.info.signatures) +                    }; +                    if (_result.signatures instanceof Error){ +                        reject(_result.signatures); +                    } else { +                        _result.is_mime = message.info.is_mime? true: false; +                        if (message.info.filename){ +                            _result.file_name = message.info.filename; +                        } +                        _result.data = message.data; +                        resolve(_result); +                    } +                } +            }, function (error){ +                reject(error); +            }); +        }); +    } +} + +/** + * Sets the data of the message, setting flags according on the data type + * @param {GPGME_Message} message The message where this data will be set + * @param { String| Object } data The data to enter. Expects either a string of + * data, or an object with a getText method + * @returns {undefined| GPGME_Error} Error if not successful, nothing otherwise + * @private + */ +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'); +    } +} + +/** + * Parses, validates and converts incoming objects into signatures. + * @param {Array<Object>} sigs + * @returns {signatureDetails} Details about the signatures + */ +function collectSignatures (sigs){ +    if (!Array.isArray(sigs)){ +        return gpgme_error('SIG_NO_SIGS'); +    } +    let summary = { +        all_valid: false, +        count: sigs.length, +        failures: 0, +        signatures: { +            good: [], +            bad: [], +        } +    }; +    for (let i=0; i< sigs.length; i++){ +        let sigObj = createSignature(sigs[i]); +        if (sigObj instanceof Error) { +            return gpgme_error('SIG_WRONG'); +        } +        if (sigObj.valid !== true){ +            summary.failures += 1; +            summary.signatures.bad.push(sigObj); +        } else { +            summary.signatures.good.push(sigObj); +        } +    } +    if (summary.failures === 0){ +        summary.all_valid = true; +    } +    return summary; +}
\ No newline at end of file diff --git a/lang/js/src/index.js b/lang/js/src/index.js new file mode 100644 index 00000000..cf6e2d03 --- /dev/null +++ b/lang/js/src/index.js @@ -0,0 +1,52 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + + +import { GpgME } from './gpgmejs'; +import { gpgme_error } from './Errors'; +import { Connection } from './Connection'; + +/** + * Initializes gpgme.js by testing the nativeMessaging connection once. + * @returns {Promise<GpgME> | GPGME_Error} + * + * @async + */ +function init (){ +    return new Promise(function (resolve, reject){ +        const connection = new Connection; +        connection.checkConnection(false).then( +            function (result){ +                if (result === true) { +                    resolve(new GpgME()); +                } else { +                    reject(gpgme_error('CONN_NO_CONNECT')); +                } +            }, function (){ // unspecific connection error. Should not happen +                reject(gpgme_error('CONN_NO_CONNECT')); +            }); +    }); +} + +const exportvalue = { init:init }; +export default exportvalue;
\ 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..6c05fc6c --- /dev/null +++ b/lang/js/src/permittedOperations.js @@ -0,0 +1,403 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + +/** + * @typedef {Object} messageProperty + * A message Property is defined by it's key. + * @property {Array<String>} allowed Array of allowed types. + * Currently accepted values are 'number', 'string', 'boolean'. + * @property {Boolean} array_allowed If the value can be an array of types + *      defined in allowed + * @property {Array<*>} allowed_data (optional) restricts to the given values +  */ + +/** + * Definition of the possible interactions with gpgme-json. + * @param {Object} operation Each operation is named by a key and contains + * the following properties: + * @property {messageProperty} required An object with all required parameters + * @property {messageProperty} optional An object with all optional parameters + * @property {Boolean} pinentry (optional) If true, a password dialog is + *      expected, thus a connection tuimeout is not advisable + * @property {Object} answer The definition on what to expect as answer, if the + *      answer is not an error + * @property {Array<String>} answer.type the type(s) as reported by gpgme-json. + * @property {Object} answer.data key-value combinations of expected properties + * of an answer and their type ('boolean', 'string', object) +  @const +*/ +export const permittedOperations = { +    encrypt: { +        pinentry: true, // TODO only with signing_keys +        required: { +            'keys': { +                allowed: ['string'], +                array_allowed: true +            }, +            'data': { +                allowed: ['string'] +            } +        }, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'signing_keys': { +                allowed: ['string'], +                array_allowed: true +            }, +            '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': 'string', +                'base64':'boolean' +            } +        } +    }, + +    decrypt: { +        pinentry: true, +        required: { +            'data': { +                allowed: ['string'] +            } +        }, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'base64': { +                allowed: ['boolean'] +            } +        }, +        answer: { +            type: ['plaintext'], +            data: { +                'data': 'string', +                'base64': 'boolean', +                'mime': 'boolean', +                'info': 'object', +                'dec_info': 'object' +            } +        } +    }, + +    sign: { +        pinentry: true, +        required: { +            'data': { +                allowed: ['string'] }, +            'keys': { +                allowed: ['string'], +                array_allowed: true +            } +        }, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'sender': { +                allowed: ['string'], +            }, +            'mode': { +                allowed: ['string'], +                allowed_data: ['detached', 'clearsign'] +                // TODO 'opaque' is not used, but available on native app +            }, +            'base64': { +                allowed: ['boolean'] +            }, +            'armor': { +                allowed: ['boolean'] +            }, +        }, +        answer: { +            type: ['signature', 'ciphertext'], +            data: { +                'data': 'string', +                'base64':'boolean' +            } + +        } +    }, + +    // note: For the meaning of the optional keylist flags, refer to +    // https://www.gnupg.org/documentation/manuals/gpgme/Key-Listing-Mode.html +    keylist:{ +        required: {}, + +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'secret': { +                allowed: ['boolean'] +            }, +            'extern': { +                allowed: ['boolean'] +            }, +            'local':{ +                allowed: ['boolean'] +            }, +            'locate': { +                allowed: ['boolean'] +            }, +            'sigs':{ +                allowed: ['boolean'] +            }, +            'notations':{ +                allowed: ['boolean'] +            }, +            'tofu': { +                allowed: ['boolean'] +            }, +            'ephemeral': { +                allowed: ['boolean'] +            }, +            'validate': { +                allowed: ['boolean'] +            }, +            'keys': { +                allowed: ['string'], +                array_allowed: true +            } +        }, +        answer: { +            type: ['keys'], +            data: { +                'base64': 'boolean', +                'keys': 'object' +            } +        } +    }, + +    export: { +        required: {}, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'keys': { +                allowed: ['string'], +                array_allowed: true +            }, +            'armor': { +                allowed: ['boolean'] +            }, +            'extern': { +                allowed: ['boolean'] +            }, +            'minimal': { +                allowed: ['boolean'] +            }, +            'raw': { +                allowed: ['boolean'] +            }, +            'pkcs12': { +                allowed: ['boolean'] +            }, +            'with-sec-fprs': { +                allowed: ['boolean'] +            } +            // secret: not yet implemented +        }, +        answer: { +            type: ['keys'], +            data: { +                'data': 'string', +                'base64': 'boolean', +                'sec-fprs': 'object' +            } +        } +    }, + +    import: { +        required: { +            'data': { +                allowed: ['string'] +            } +        }, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'base64': { +                allowed: ['boolean'] +            }, +        }, +        answer: { +            type: [], +            data: { +                'result': 'object' +            } +        } +    }, + +    delete: { +        pinentry: true, +        required:{ +            'key': { +                allowed: ['string'] +            } +        }, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +        }, +        answer: { +            data: { +                'success': 'boolean' +            } +        } +    }, + +    version: { +        required: {}, +        optional: {}, +        answer: { +            type:  [''], +            data: { +                'gpgme': 'string', +                'info': 'object' +            } +        } +    }, + +    createkey: { +        pinentry: true, +        required: { +            userid: { +                allowed: ['string'] +            } +        }, +        optional: { +            algo: { +                allowed: ['string'] +            }, +            'subkey-algo': { +                allowed: ['string'] +            }, +            expires: { +                allowed: ['number'], +            } +        }, +        answer: { +            type: [''], +            data: { 'fingerprint': 'string' } +        } +    }, + +    verify: { +        required: { +            data: { +                allowed: ['string'] +            } +        }, +        optional: { +            'protocol': { +                allowed: ['string'], +                allowed_data: ['cms', 'openpgp'] +            }, +            'signature': { +                allowed: ['string'] +            }, +            'base64':{ +                allowed: ['boolean'] +            } +        }, +        answer: { +            type: ['plaintext'], +            data:{ +                data: 'string', +                base64:'boolean', +                info: 'object' +                // info.file_name: Optional string of the plaintext file name. +                // info.is_mime: Boolean if the messages claims it is MIME. +                // info.signatures: Array of signatures +            } +        } +    }, + +    config_opt: { +        required: { +            'component':{ +                allowed: ['string'], +                // allowed_data: ['gpg'] // TODO check all available +            }, +            'option': { +                allowed: ['string'], +                // allowed_data: ['default-key'] // TODO check all available +            } +        }, +        optional: {}, +        answer: { +            type: [], +            data: { +                option: 'object' +            } +        } +    } + +    /** +     * TBD handling of secrets +     * TBD key modification? +     */ + +}; diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js new file mode 100644 index 00000000..659ef85c --- /dev/null +++ b/lang/js/unittest_inputvalues.js @@ -0,0 +1,123 @@ +import { createKey } from './src/Key'; + +export const helper_params = { +    validLongId: '0A0A0A0A0A0A0A0A', +    validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', +        createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), +        'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], +    validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', +    validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', +        '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], +    invalidLongId: '9A9A7A7A8A9A9A7A7A8A', +    invalidFingerprints: [{ hello:'World' }, ['kekekeke'], new Uint32Array(40)], +    invalidKeyArray: { curiosity:'uncat' }, +    invalidKeyArray_OneBad: [ +        createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), +        'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', +        '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], +    invalidErrorCode: 'Please type in all your passwords.', +    validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', true), +    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'), null] +    } +}; + +export const whatever_params = { +    four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'], +}; +export const key_params = { +// A Key you own (= having a secret Key) in GPG. See testkey.pub/testkey.sec +    validKeyFingerprint: 'D41735B91236FDB882048C5A2301635EEFF0CB05', +    // A Key you do not own (= having no secret Key) in GPG. See testkey2.pub +    validFingerprintNoSecret: 'E059A1E0866D31AE131170884D9A2E13304153D1', +    // A Key not in your Keyring. This is just a random hex string. +    invalidKeyFingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A', +    validKeyProperties: ['expired', 'disabled','invalid','can_encrypt', +        'can_sign','can_certify','can_authenticate','secret','is_qualified'] +}; +export const armoredKey = { +    fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A', +    key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + +        '\n' + +        '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\n' + +        '=qP6s\n' + +        '-----END PGP PUBLIC KEY BLOCK-----\n', +    keyChangedUserId: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + +        '\n' + +        'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' + +        'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' + +        'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' + +        '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' + +        'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' + +        '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' + +        'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' + +        'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' + +        'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' + +        'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' + +        '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' + +        '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' + +        'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' + +        'uzEXPGW+sq0WRp3hynn7kVP6QQYvtCZTb21lb25lIEVsc2UgPHNvbWVvbmVlbHNl\n' + +        'QGV4YW1wbGUub3JnPokBVAQTAQgAPhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJb\n' + +        'D705AhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEPY3kMEeYCeK\n' + +        'aIUH/2o+Ra+GzxgZrVexXLL+FCSmcu0cxeWfMhL8jd96c6uXIT21qQMRU2jgvnUp\n' + +        'Wdi/BeLKp5lYwywm04PFhmRVxWXLuLArCsDu+CFys+aPeybnjikPBZov6P8/cZV3\n' + +        'cd6zxFvqB9J15HjDMcl/r5v6d4CgSLKlFebrO5WKxHa6zGK9TRMQrqTu1heKHRf6\n' + +        '4+Wj+MZmYnPzEQePjiBw/VkJ1Nm37Dd24gKdcN/qJFwEOqvbI5RIjB7xqoDslZk9\n' + +        'sAivBXwF0E9HKqvh4WZZeA7uaWNdGo/cQkD5rab5SdHGNPHLbzoRWScsM8WYtsME\n' + +        'dEMp5iPuG9M63+TD7losAkJ/TlS5AQ0EWw+8rQEIALC8FyZucYmTxeEQFG7ZA3eM\n' + +        'qBWzcIbmcTZeHbQmKlnAC4Ye6AMu/r+OWLPPPmO2PdIAdW0kHyUlbwjRw1IQhfxt\n' + +        'FgfgLuU/YNNv0KAxwYStAjIrjzT4cixuxmElI0d2NRSXog8QRV9JDNZq5+a6zB57\n' + +        '6CKoeJOxqJbbTK1eKXBUamOUcFrGf9umyRnRhFqyCc+oqAkPd3mS6c2pajIKkxwV\n' + +        'iAMRG89uGFx4Vu8UT0b+7eBty5XXmbHnd0NfQia89y8mcM7IlL3KvsCl05XfXakD\n' + +        'UwtvF8+dudW750ZefsP9UIN3loFhU+b7kA2uTrP2Vgof1o7nCBeksB0m+60I4vkA\n' + +        'EQEAAYkBPAQYAQgAJhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJbD7ytAhsMBQkD\n' + +        'wmcAAAoJEPY3kMEeYCeKAxUH/jzgbb7dHRwBqPdX/PiBbcxFIyFxioQzIn8AJ2jv\n' + +        'Lx6it5hbzjclZZUNFjr9hxhga4EE/jtY7gm4/bAiVWfXtc4JKCc2OaaOwhiIDOSi\n' + +        'nvvZMHbujRV6IVfmJZxrDLQQjskJvbfGkj5A/fWSwvbLJFgVcK1dJR9w6sukJjxo\n' + +        'RAxBsAa2RqDXAdOj9N/9WKnyrRkRDjE4pNHb+WXr6Kc/yTMbLNp0SVScfVDU5Rd3\n' + +        'YuP/sGkEcTb76ECMFBGCfJo40XualQ0S7DriIE2zQJ7oqLhFXb4QEucW5ucU1HF8\n' + +        'UlxhdyKOG5/dgJ1bOpiY+lT8K0XhOAOV9Kx3EfFU6admRjc=\n' + +        '=9WZ7\n' + +        '-----END PGP PUBLIC KEY BLOCK-----\n' +};
\ No newline at end of file diff --git a/lang/js/unittests.js b/lang/js/unittests.js new file mode 100644 index 00000000..212effd3 --- /dev/null +++ b/lang/js/unittests.js @@ -0,0 +1,379 @@ +/* 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'; /* global mocha, it, describe*/ +import './node_modules/chai/chai';/* global 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 { key_params as kp } from './unittest_inputvalues'; +import { Connection } from './src/Connection'; +import { gpgme_error, err_list } from './src/Errors'; +import { toKeyIdArray , isFingerprint } from './src/Helpers'; +import { createKey } from './src/Key'; +import { GPGME_Keyring } from './src/Keyring'; +import { GPGME_Message, createMessage } from './src/Message'; + +mocha.setup('bdd'); +const expect = chai.expect; +chai.config.includeStack = true; + +function unittests (){ +    describe('Connection testing', function (){ + +        it('Connecting', function (done) { +            let conn0 = new Connection; +            conn0.checkConnection().then(function (answer) { +                expect(answer).to.not.be.empty; +                expect(answer.gpgme).to.not.be.undefined; +                expect(answer.gpgme).to.be.a('string'); +                expect(answer.info).to.be.an('Array'); +                expect(conn0.disconnect).to.be.a('function'); +                expect(conn0.post).to.be.a('function'); +                done(); +            }); + +        }); + +        it('Disconnecting', function (done) { +            let conn0 = new Connection; +            conn0.checkConnection(false).then(function (answer) { +                expect(answer).to.be.true; +                conn0.disconnect(); +                conn0.checkConnection(false).then(function (result) { +                    expect(result).to.be.false; +                    done(); +                }); +            }); +        }); +    }); + +    describe('Error Object handling', function (){ +        // TODO: new GPGME_Error codes +        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('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('Key has data after a first refresh', function (done) { +            let key = createKey(kp.validKeyFingerprint); +            key.refreshKey().then(function (key2){ +                expect(key2.get).to.be.a('function'); +                for (let i=0; i < kp.validKeyProperties.length; i++) { +                    let prop = key2.get(kp.validKeyProperties[i]); +                    expect(prop).to.not.be.undefined; +                    expect(prop).to.be.a('boolean'); +                } +                expect(isFingerprint(key2.get('fingerprint'))).to.be.true; +                expect( +                    key2.get('fingerprint')).to.equal(kp.validKeyFingerprint); +                expect( +                    key2.get('fingerprint')).to.equal(key.fingerprint); +                done(); +            }); +        }); + +        it('Non-cached key async data retrieval', function (done){ +            let key = createKey(kp.validKeyFingerprint, true); +            key.get('can_authenticate').then(function (result){ +                expect(result).to.be.a('boolean'); +                done(); +            }); +        }); + +        it('Non-cached key async armored Key', function (done){ +            let key = createKey(kp.validKeyFingerprint, true); +            key.get('armored').then(function (result){ +                expect(result).to.be.a('string'); +                expect(result).to.include('KEY BLOCK-----'); +                done(); +            }); +        }); + +        it('Non-cached key async hasSecret', function (done){ +            let key = createKey(kp.validKeyFingerprint, true); +            key.get('hasSecret').then(function (result){ +                expect(result).to.be.a('boolean'); +                done(); +            }); +        }); + +        it('Non-cached key async hasSecret (no secret in Key)', function (done){ +            let key = createKey(kp.validFingerprintNoSecret, true); +            key.get('hasSecret').then(function (result){ +                expect(result).to.be.a('boolean'); +                expect(result).to.equal(false); +                done(); +            }); +        }); + +        it('Querying non-existing Key returns an error', function (done) { +            let key = createKey(kp.invalidKeyFingerprint); +            key.refreshKey().then(function (){}, +                function (error){ +                    expect(error).to.be.an.instanceof(Error); +                    expect(error.code).to.equal('KEY_NOKEY'); +                    done(); +                }); +        }); + +        it('createKey returns error if parameters are wrong', function (){ +            for (let i=0; i< 4; i++){ +                expect(function (){ +                    createKey(wp.four_invalid_params[i]); +                }).to.throw( +                    err_list.PARAM_WRONG.msg +                ); + +            } +        }); + +    //     it('Overwriting getFingerprint does not work', function(){ +    //         const evilFunction = function(){ +    //             return 'bad Data'; +    //         }; +    //         let key = createKey(kp.validKeyFingerprint, true); +    //         expect(key.fingerprint).to.equal(kp.validKeyFingerprint); +    //         try { +    //             key.getFingerprint = evilFunction; +    //         } +    //         catch(e) { +    //             expect(e).to.be.an.instanceof(TypeError); +    //         } +    //         expect(key.fingerprint).to.equal(kp.validKeyFingerprint); +    //         expect(key.getFingerprint).to.not.equal(evilFunction); +    //     }); +    }); + +    describe('GPGME_Keyring', function (){ + +        it('correct Keyring initialization', function (){ +            let keyring = new GPGME_Keyring; +            expect(keyring).to.be.an.instanceof(GPGME_Keyring); +            expect(keyring.getKeys).to.be.a('function'); +        }); + +        it('Loading Keys from Keyring, to be used synchronously', +            function (done){ +                let keyring = new GPGME_Keyring; +                keyring.getKeys(null, true).then(function (result){ +                    expect(result).to.be.an('array'); +                    expect(result[0].get('hasSecret')).to.be.a('boolean'); +                    done(); +                }); +            } +        ); + +        it('Loading specific Key from Keyring, to be used synchronously', +            function (done){ +                let keyring = new GPGME_Keyring; +                keyring.getKeys(kp.validKeyFingerprint, true).then( +                    function (result){ +                        expect(result).to.be.an('array'); +                        expect(result[0].get('hasSecret')).to.be.a('boolean'); +                        done(); +                    } +                ); +            } +        ); + +        it('Querying non-existing Key from Keyring', function (done){ +            let keyring = new GPGME_Keyring; +            keyring.getKeys(kp.invalidKeyFingerprint, true).then( +                function (result){ +                    expect(result).to.be.an('array'); +                    expect(result.length).to.equal(0); +                    done(); +                } +            ); +        }); + +    }); + +    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('keys', hp.validFingerprints); +            expect(test0.isComplete()).to.be.false; +            expect(function (){ +                test0.setParameter('data', ''); +            }).to.throw( +                err_list.PARAM_WRONG.msg); +        }); + +        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', +                'chunksize'); +            expect(test0.message.op).to.equal('encrypt'); +            expect(test0.message.data).to.equal( +                mp.valid_encrypt_data); +        }); + +        it ('Not accepting non-allowed operation', function (){ +            expect(function () { +                createMessage(mp.invalid_op_action); +            }).to.throw( +                err_list.MSG_WRONG_OP.msg); +        }); +        it('Not accepting wrong parameter type', function (){ +            expect(function () { +                createMessage(mp.invalid_op_type); +            }).to.throw( +                err_list.PARAM_WRONG.msg); +        }); + +        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++){ +                expect(function (){ +                    test0.setParameter( +                        mp.invalid_param_test.invalid_param_names[i], +                        'Somevalue');} +                ).to.throw(err_list.PARAM_WRONG.msg); +            } +        }); + +        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++){ +                expect(function (){ +                    test0.setParameter( +                        mp.invalid_param_test.validparam_name_0, +                        mp.invalid_param_test.invalid_values_0[j]); +                }).to.throw(err_list.PARAM_WRONG.msg); +            } +        }); +    }); + +} + +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..19f3bbda --- /dev/null +++ b/lang/js/webpack.conf.js @@ -0,0 +1,36 @@ +/* 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 + */ +/* global require, module, __dirname */ +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..c3c87f39 --- /dev/null +++ b/lang/js/webpack.conf_unittests.js @@ -0,0 +1,36 @@ +/* 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 + */ +/* global require, module, __dirname */ + +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' +    } +}; | 
