diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index a20cfe19..0d3e2936 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -1,27 +1,28 @@

Browsertest

+ diff --git a/lang/js/BrowserTestExtension/tests/KeyInfos.js b/lang/js/BrowserTestExtension/tests/KeyInfos.js new file mode 100644 index 00000000..03773a44 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/KeyInfos.js @@ -0,0 +1,46 @@ +/* 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 . + * SPDX-License-Identifier: LGPL-2.1+ + * + * Author(s): + * Maximilian Krambach + */ + +/* global describe, it, expect, before, Gpgmejs */ +/* global inputvalues, fixedLengthString */ + +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(); + }); + }); +}); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 1e8f1544..7dda47cd 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -1,286 +1,287 @@ /* 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 . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ 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', }, someInputParameter: 'bad string' }; // (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/src/Key.js b/lang/js/src/Key.js index 88c49d3f..eeb27035 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -1,608 +1,609 @@ /* 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 . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ 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 * @returns {GPGME_Key|GPGME_Error} */ export function createKey(fingerprint, async = false){ if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } else return Object.freeze(new GPGME_Key(fingerprint, async)); } /** * 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 */ export class GPGME_Key { constructor(fingerprint, async){ /** * @property {Boolean} If true, most answers will be asynchronous */ this.isAsync = async; - let _data = {fingerprint: fingerprint}; + let _data = {fingerprint: fingerprint.toUpperCase()}; this.getFingerprint = function(){ if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ return gpgme_error('KEY_INVALID'); } return _data.fingerprint; }; /** * Property indicating if the Key possesses a private/secret part. If * this information is not yet cached, it returns an * {@link GPGME_Error} with code 'KEY_NO_INIT'. Running * {@link refreshKey} may help in this case. * @returns {Boolean} If the Key has a secret subkey. */ this.hasSecret= function (){ return this.get('hasSecret'); }; /** * @param {Object} data Bulk set the data for this key, with an Object * sent by gpgme-json. * @returns {GPGME_Key|GPGME_Error} Itself after values have been set, * an error if something went wrong. * @private */ this.setKeyData = function (data){ if (typeof(data) !== 'object') { return gpgme_error('KEY_INVALID'); } - if (!data.fingerprint || data.fingerprint !== _data.fingerprint){ + if (!data.fingerprint || + data.fingerprint.toUpperCase() !== _data.fingerprint){ return gpgme_error('KEY_INVALID'); } let keys = Object.keys(data); for (let i=0; i< keys.length; i++){ if (!validKeyProperties.hasOwnProperty(keys[i])){ return gpgme_error('KEY_INVALID'); } //running the defined validation function if (validKeyProperties[keys[i]](data[keys[i]]) !== true ){ return gpgme_error('KEY_INVALID'); } switch (keys[i]){ case 'subkeys': _data.subkeys = []; for (let i=0; i< data.subkeys.length; i++) { _data.subkeys.push(Object.freeze( new GPGME_Subkey(data.subkeys[i]))); } break; case 'userids': _data.userids = []; for (let i=0; i< data.userids.length; i++) { _data.userids.push(Object.freeze( new GPGME_UserId(data.userids[i]))); } break; case 'last_update': _data[keys[i]] = new Date( data[keys[i]] * 1000 ); break; default: _data[keys[i]] = data[keys[i]]; } } return this; }; /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). * If 'cached' is false, the value will be resolved as a Promise. */ this.get = function(property) { if (this.isAsync === true) { let me = this; return new Promise(function(resolve, reject) { if (property === 'armored'){ resolve(me.getArmor()); } else if (property === 'hasSecret'){ resolve(me.getHasSecret()); } else if (validKeyProperties.hasOwnProperty(property)){ let msg = createMessage('keylist'); msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ if (result.keys && result.keys.length === 1 && result.keys[0].hasOwnProperty(property)){ resolve(result.keys[0][property]); } else { reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); } }, function(error){ reject(gpgme_error(error)); }); } else { reject(gpgme_error('PARAM_WRONG')); } }); } else { if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); } if (!_data.hasOwnProperty(property)){ return gpgme_error('KEY_NO_INIT'); } else { return (_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} * @async */ this.refreshKey = function() { let me = this; return new Promise(function(resolve, reject) { if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('sigs', true); msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeyData(result.keys[0]); me.getHasSecret().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} * @async */ this.getArmor = function(){ return new Promise(function(resolve, reject) { if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('export'); msg.setParameter('armor', true); msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ _data.armored = result.data; resolve(result.data); }, function(error){ reject(error); }); }); }; /** * Find out if the Key includes a secret part. Note that this is a * rather nonperformant operation, as it needs to query gnupg twice. * If you want this inforrmation about more than a few Keys, it may be * advisable to run {@link Keyring.getKeys} instead. * @returns {Promise} True if a secret/private Key * is available in the gnupg Keyring * @async */ this.getHasSecret = function (){ return new Promise(function(resolve, reject) { if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('keys', _data.fingerprint); msg.setParameter('secret', true); msg.post().then(function(result){ _data.hasSecret = null; if ( result.keys && result.keys.length === 1 && result.keys[0].secret === true ) { _data.hasSecret = true; resolve(true); } else { _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} Success if key was deleted, * rejects with a GPG error otherwise. */ this.delete= function (){ return new Promise(function(resolve, reject){ if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('delete'); msg.setParameter('key', _data.fingerprint); msg.post().then(function(result){ resolve(result.success); }, function(error){ reject(error); }); }); }; } /** * @returns {String} The fingerprint defining this Key */ get fingerprint(){ return this.getFingerprint(); } /** * Property for the export of armored Key. If the armored Key is not * cached, it returns an {@link GPGME_Error} with code 'KEY_NO_INIT'. * Running {@link refreshKey} may help in this case. * @returns {String|GPGME_Error} The armored public Key block. */ get armored(){ return this.get('armored', true); } } /** * 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){ let _data = {}; let keys = Object.keys(data); /** * 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){ if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ _data[property] = new Date(value * 1000); } else { _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} */ this.get = function(property) { if (_data.hasOwnProperty(property)){ return (_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){ let _data = {}; let keys = Object.keys(data); const setProperty = function(property, value){ if (validUserIdProperties.hasOwnProperty(property)){ if (validUserIdProperties[property](value) === true) { if (property === 'last_update'){ _data[property] = new Date(value*1000); } else { _data[property] = value; } } } }; for (let i=0; i< keys.length; i++) { setProperty(keys[i], data[keys[i]]); } /** * Validates a subkey property against {@link validUserIdProperties} and * sets it if validation is successful * @param {String} property * @param {*} value * @param private */ /** * Fetches information about the user * @param {String} property Information to request * @returns {String | Number} */ this.get = function (property) { if (_data.hasOwnProperty(property)){ return (_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 * @protected * @const */ const validKeyProperties = { 'fingerprint': function(value){ return isFingerprint(value); }, 'armored': function(value){ return typeof(value === 'string'); }, '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'; } };