js: documentation

--

* Fixed errors:
- src/Message.js post(): Set chunksize to defined default value instead
  of hardcoded
- src/Keys.js: added getHasSecret() to refreshKey operation.

* Reviewed and updated the documentation

* non-documentation changes which do not affect functionality:
- src/Errors: disabled a console.warn that is only useful for debugging
- helpers.js: renamed "string" to "value" in isFingerprint and isLongId
  to avoid confusion
- src/Keyring: prepare_sync, search are both explicitly set to false by
  default
This commit is contained in:
Maximilian Krambach 2018-07-10 14:32:26 +02:00
parent 8964627f6a
commit 4015f5b498
11 changed files with 458 additions and 236 deletions

View File

@ -1,10 +1,17 @@
gpgmejs, as contained in this directory, is a javascript library for direct use gpgme.js, as contained in this directory, is a javascript library for direct use
of gnupg in browsers, with the help of nativeMessaging. of gnupg in browsers, with the help of nativeMessaging.
Installation Prerequisites:
------------- --------------
gpgmejs uses webpack, and thus depends on nodejs for building. All dependencies gpgme.js will make use of the application gpgme-json, which is distributed with
will be installed (in a local subdirectory) with the command `npm install`. gpgme. Gpgme-json needs to be installed; it will further need to accept the
browser extension in the manifest file.
Building gpgme.js
-----------------
gpgme.js uses webpack, and thus depends on nodejs 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 To create a current version of the package, the command is
`npx webpack --config webpack.conf.js`. `npx webpack --config webpack.conf.js`.
@ -14,7 +21,7 @@ in webpack.conf.js.
Demo and Test WebExtension: Demo and Test WebExtension:
--------------------------- ---------------------------
The Demo Extension shows simple examples of the usage of gpgmejs. The Demo Extension shows simple examples of the usage of gpgme.js.
The BrowsertestExtension runs more intensive tests (using the mocha and chai The BrowsertestExtension runs more intensive tests (using the mocha and chai
frameworks). Tests from BrowserTestExtension/tests will be run against the frameworks). Tests from BrowserTestExtension/tests will be run against the
@ -22,11 +29,11 @@ gpgmejs.bundle.js itself. They aim to test the outward facing functionality
and API. and API.
Unittests as defined in ./unittests.js will be bundled in Unittests as defined in ./unittests.js will be bundled in
gpgmejs_unittests.bundle.js, and test the separate components of gpgmejs, gpgmejs_unittests.bundle.js, and test the separate components of gpgme.js,
which mostly are not exported. which mostly are not exported.
. The file `build_extension.sh` may serve as a pointer on how to The file `build_extension.sh` may serve as a pointer on how to build and
build and assemble these two Extensions and their dependencies. It can directly assemble these two Extensions and their dependencies. It can directly
be used in most linux systems. be used in most linux systems.
The resulting folders can just be included in the extensions tab of the browser The resulting folders can just be included in the extensions tab of the browser
@ -46,7 +53,7 @@ is needed, with the following content:
``` ```
{ {
"name": "gpgmejson", "name": "gpgmejson",
"description": "This is a test application for gpgmejs", "description": "This is a test application for gpgme.js",
"path": "/usr/bin/gpgme-json", "path": "/usr/bin/gpgme-json",
"type": "stdio", "type": "stdio",
"allowed_origins": ["chrome-extension://ExtensionIdentifier/"] "allowed_origins": ["chrome-extension://ExtensionIdentifier/"]
@ -61,7 +68,7 @@ is needed, with the following content:
``` ```
{ {
"name": "gpgmejson", "name": "gpgmejson",
"description": "This is a test application for gpgmejs", "description": "This is a test application for gpgme.js",
"path": "/usr/bin/gpgme-json", "path": "/usr/bin/gpgme-json",
"type": "stdio", "type": "stdio",
"allowed_extensions": ["ExtensionIdentifier@temporary-addon"] "allowed_extensions": ["ExtensionIdentifier@temporary-addon"]

View File

@ -28,7 +28,12 @@ import { gpgme_error } from './Errors';
import { GPGME_Message, createMessage } from './Message'; import { GPGME_Message, createMessage } from './Message';
/** /**
* A Connection handles the nativeMessaging interaction. * 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{ export class Connection{
@ -37,19 +42,26 @@ export class Connection{
} }
/** /**
* Retrieves the information about the backend. * @typedef {Object} backEndDetails
* @param {Boolean} details (optional) If set to false, the promise will * @property {String} gpgme Version number of gpgme
* just return a connection status * @property {Array<Object>} info Further information about the backend
* @returns {Promise<Object>} result * and the used applications (Example:
* @returns {String} result.gpgme Version number of gpgme * {
* @returns {Array<Object>} result.info Further information about the
* backends.
* Example:
* "protocol": "OpenPGP", * "protocol": "OpenPGP",
* "fname": "/usr/bin/gpg", * "fname": "/usr/bin/gpg",
* "version": "2.2.6", * "version": "2.2.6",
* "req_version": "1.4.0", * "req_version": "1.4.0",
* "homedir": "default" * "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){ checkConnection(details = true){
if (details === true) { if (details === true) {
@ -74,7 +86,7 @@ export class Connection{
} }
/** /**
* Immediately closes the open port. * Immediately closes an open port.
*/ */
disconnect() { disconnect() {
if (this._connection){ if (this._connection){
@ -93,10 +105,13 @@ export class Connection{
} }
/** /**
* Sends a message and resolves with the answer. * 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 * @param {GPGME_Message} message
* @returns {Promise<Object>} the gnupg answer, or rejection with error * @returns {Promise<Object>} The collected answer
* information. * @async
*/ */
post(message){ post(message){
if (!message || !(message instanceof GPGME_Message)){ if (!message || !(message instanceof GPGME_Message)){
@ -170,16 +185,25 @@ export class Connection{
/** /**
* A class for answer objects, checking and processing the return messages of * A class for answer objects, checking and processing the return messages of
* the nativeMessaging communication. * the nativeMessaging communication.
* @param {String} operation The operation, to look up validity of returning * @protected
* messages
*/ */
class Answer{ class Answer{
/**
* @param {GPGME_Message} message
*/
constructor(message){ constructor(message){
this.operation = message.operation; this.operation = message.operation;
this.expect = message.expect; this.expect = message.expect;
} }
/**
* Adds incoming base64 encoded data to the existing response
* @param {*} msg base64 encoded data.
* @returns {Boolean}
*
* @private
*/
collect(msg){ collect(msg){
if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) {
return gpgme_error('CONN_UNEXPECTED_ANSWER'); return gpgme_error('CONN_UNEXPECTED_ANSWER');
@ -195,6 +219,10 @@ class Answer{
} }
} }
/**
* Returns the base64 encoded answer data with the content verified against
* {@link permittedOperations}.
*/
get message(){ get message(){
if (this._responseb64 === undefined){ if (this._responseb64 === undefined){
return gpgme_error('CONN_UNEXPECTED_ANSWER'); return gpgme_error('CONN_UNEXPECTED_ANSWER');

View File

@ -21,6 +21,9 @@
* Maximilian Krambach <mkrambach@intevation.de> * Maximilian Krambach <mkrambach@intevation.de>
*/ */
/**
* Listing of all possible error codes and messages of a {@link GPGME_Error}.
*/
const err_list = { const err_list = {
// Connection // Connection
'CONN_NO_CONNECT': { 'CONN_NO_CONNECT': {
@ -107,10 +110,11 @@ const err_list = {
}; };
/** /**
* Checks the given error code and returns an error object with some * Checks the given error code and returns an {@link GPGME_Error} error object
* information about meaning and origin * with some information about meaning and origin
* @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR' * @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR'
* @param {*} info Error message passed through if code is 'GNUPG_ERROR' * @param {*} info Error message passed through if code is 'GNUPG_ERROR'
* @returns {GPGME_Error}
*/ */
export function gpgme_error(code = 'GENERIC_ERROR', info){ export function gpgme_error(code = 'GENERIC_ERROR', info){
if (err_list.hasOwnProperty(code)){ if (err_list.hasOwnProperty(code)){
@ -119,7 +123,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){
} }
if (err_list[code].type === 'warning'){ if (err_list[code].type === 'warning'){
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn(code + ': ' + err_list[code].msg); // console.warn(code + ': ' + err_list[code].msg);
} }
return null; return null;
} else if (code === 'GNUPG_ERROR'){ } else if (code === 'GNUPG_ERROR'){
@ -130,6 +134,14 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){
} }
} }
/**
* 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{ class GPGME_Error extends Error{
constructor(code, msg=''){ constructor(code, msg=''){
if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){

View File

@ -26,11 +26,11 @@ import { GPGME_Key } from './Key';
/** /**
* Tries to return an array of fingerprints, either from input fingerprints or * Tries to return an array of fingerprints, either from input fingerprints or
* from Key objects (openpgp Keys or GPGME_Keys are both expected) * 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. * @param {Object | Array<Object> | String | Array<String>} input
* @returns {Array<String>} Array of fingerprints, or an empty array
*/ */
export function toKeyIdArray(input){ export function toKeyIdArray(input){
if (!input){ if (!input){
return []; return [];
@ -44,6 +44,8 @@ export function toKeyIdArray(input){
if (isFingerprint(input[i]) === true){ if (isFingerprint(input[i]) === true){
result.push(input[i]); result.push(input[i]);
} else { } else {
// MSG_NOT_A_FPR is just a console warning if warning enabled
// in src/Errors.js
gpgme_error('MSG_NOT_A_FPR'); gpgme_error('MSG_NOT_A_FPR');
} }
} else if (typeof(input[i]) === 'object'){ } else if (typeof(input[i]) === 'object'){
@ -71,9 +73,11 @@ export function toKeyIdArray(input){
} }
/** /**
* check if values are valid hexadecimal values of a specified length * Check if values are valid hexadecimal values of a specified length
* @param {*} key input value. * @param {String} key input value.
* @param {int} len the expected length of the value * @param {int} len the expected length of the value
* @returns {Boolean} true if value passes test
* @private
*/ */
function hextest(key, len){ function hextest(key, len){
if (!key || typeof(key) !== 'string'){ if (!key || typeof(key) !== 'string'){
@ -87,15 +91,21 @@ function hextest(key, len){
} }
/** /**
* check if the input is a valid Hex string with a length of 40 * 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(string){ export function isFingerprint(value){
return hextest(string, 40); return hextest(value, 40);
} }
/** /**
* check if the input is a valid Hex string with a length of 16 * 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(string){ export function isLongId(value){
return hextest(string, 16); return hextest(value, 16);
} }

View File

@ -26,8 +26,9 @@ import { gpgme_error } from './Errors';
import { createMessage } from './Message'; import { createMessage } from './Message';
/** /**
* Validates the fingerprint. * Validates the given fingerprint and creates a new {@link GPGME_Key}
* @param {String} fingerprint * @param {String} fingerprint
* @returns {GPGME_Key|GPGME_Error}
*/ */
export function createKey(fingerprint){ export function createKey(fingerprint){
if (!isFingerprint(fingerprint)){ if (!isFingerprint(fingerprint)){
@ -37,12 +38,13 @@ export function createKey(fingerprint){
} }
/** /**
* Representing the Keys as stored in GPG * Represents the Keys as stored in the gnupg backend
* It allows to query almost all information defined in gpgme Key Objects * It allows to query almost all information defined in gpgme Key Objects
* Refer to validKeyProperties for available information, and the gpgme * Refer to {@link validKeyProperties} for available information, and the gpgme
* documentation on their meaning * documentation on their meaning
* (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) * (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html)
* *
* @class
*/ */
export class GPGME_Key { export class GPGME_Key {
@ -62,6 +64,9 @@ export class GPGME_Key {
} }
} }
/**
* @returns {String} The fingerprint defining this Key
*/
get fingerprint(){ get fingerprint(){
if (!this._data || !this._data.fingerprint){ if (!this._data || !this._data.fingerprint){
return gpgme_error('KEY_INVALID'); return gpgme_error('KEY_INVALID');
@ -70,11 +75,11 @@ export class GPGME_Key {
} }
/** /**
* * @param {Object} data Bulk set the data for this key, with an Object sent
* @param {Object} data Bulk set data for this key, with the Object as sent
* by gpgme-json. * by gpgme-json.
* @returns {GPGME_Key|GPGME_Error} The Key object itself after values have * @returns {GPGME_Key|GPGME_Error} Itself after values have been set, an
* been set * error if something went wrong
* @private
*/ */
setKeyData(data){ setKeyData(data){
if (this._data === undefined) { if (this._data === undefined) {
@ -126,12 +131,17 @@ export class GPGME_Key {
} }
/** /**
* Query any property of the Key list * Query any property of the Key listed in {@link validKeyProperties}
* @param {String} property Key property to be retreived * @param {String} property property to be retreived
* @param {*} cached (optional) if false, the data will be directly queried * @param {Boolean} cached (optional) if false, the data will be directly
* from gnupg. * queried from gnupg, and the operation will be asynchronous. Else, the
* @returns {*|Promise<*>} the value, or if not cached, a Promise * data will be fetched from the state of the initialization of the Key.
* resolving on the value * The cached mode may contain outdated information, but can be used as
* synchronous operation, where the backend is not expected to change Keys
* during a session. The key still can be reloaded by invoking
* {@link refreshKey}.
* @returns {*|Promise<*>} the value (Boolean, String, Array, Object).
* If 'cached' is true, the value will be resolved as a Promise.
*/ */
get(property, cached=true) { get(property, cached=true) {
if (cached === false) { if (cached === false) {
@ -164,7 +174,12 @@ export class GPGME_Key {
} }
/** /**
* Reloads the Key from gnupg * 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() { refreshKey() {
let me = this; let me = this;
@ -178,7 +193,12 @@ export class GPGME_Key {
msg.post().then(function(result){ msg.post().then(function(result){
if (result.keys.length === 1){ if (result.keys.length === 1){
me.setKeyData(result.keys[0]); me.setKeyData(result.keys[0]);
resolve(me); me.getHasSecret().then(function(){
//TODO retrieve armored Key
resolve(me);
}, function(error){
reject(error);
});
} else { } else {
reject(gpgme_error('KEY_NOKEY')); reject(gpgme_error('KEY_NOKEY'));
} }
@ -189,9 +209,9 @@ export class GPGME_Key {
} }
/** /**
* Query the armored block of the non- secret parts of the Key directly * Query the armored block of the Key directly from gnupg. Please note that
* from gpg. * this will not get you any export of the secret/private parts of a Key
* @returns {Promise<String>} * @returns {Promise<String|GPGME_Error>}
* @async * @async
*/ */
getArmor(){ getArmor(){
@ -213,9 +233,12 @@ export class GPGME_Key {
} }
/** /**
* Find out if the Key includes a secret part * Find out if the Key includes a secret part. Note that this is a rather
* @returns {Promise<Boolean>} * 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<Boolean|GPGME_Error>} True if a secret/private Key is
* available in the gnupg Keyring
* @async * @async
*/ */
getHasSecret(){ getHasSecret(){
@ -253,25 +276,30 @@ export class GPGME_Key {
*/ */
/** /**
* @returns {String} The armored public Key block * 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(){ get armored(){
return this.get('armored', true); return this.get('armored', true);
} }
/** /**
* @returns {Boolean} If the key is considered a "private Key", * Property indicating if the Key possesses a private/secret part. If this
* i.e. owns a secret subkey. * 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.
*/ */
get hasSecret(){ get hasSecret(){
return this.get('hasSecret', true); return this.get('hasSecret', true);
} }
/** /**
* Deletes the public Key from the GPG Keyring. Note that a deletion of a * Deletes the (public) Key from the GPG Keyring. Note that a deletion of a
* secret key is not supported by the native backend. * secret key is not supported by the native backend.
* @returns {Promise<Boolean>} Success if key was deleted, rejects with a * @returns {Promise<Boolean|GPGME_Error>} Success if key was deleted,
* GPG error otherwise * rejects with a GPG error otherwise.
*/ */
delete(){ delete(){
let me = this; let me = this;
@ -291,10 +319,17 @@ export class GPGME_Key {
} }
/** /**
* The subkeys of a Key. Currently, they cannot be refreshed separately * Representing a subkey of a Key.
* @class
* @protected
*/ */
class GPGME_Subkey { class GPGME_Subkey {
/**
* Initializes with the json data sent by gpgme-json
* @param {Object} data
* @private
*/
constructor(data){ constructor(data){
let keys = Object.keys(data); let keys = Object.keys(data);
for (let i=0; i< keys.length; i++) { for (let i=0; i< keys.length; i++) {
@ -302,6 +337,13 @@ class GPGME_Subkey {
} }
} }
/**
* Validates a subkey property against {@link validSubKeyProperties} and
* sets it if validation is successful
* @param {String} property
* @param {*} value
* @param private
*/
setProperty(property, value){ setProperty(property, value){
if (!this._data){ if (!this._data){
this._data = {}; this._data = {};
@ -318,10 +360,9 @@ class GPGME_Subkey {
} }
/** /**
* * Fetches any information about this subkey
* @param {String} property Information to request * @param {String} property Information to request
* @returns {String | Number} * @returns {String | Number | Date}
* TODO: date properties are numbers with Date in seconds
*/ */
get(property) { get(property) {
if (this._data.hasOwnProperty(property)){ if (this._data.hasOwnProperty(property)){
@ -330,15 +371,31 @@ class GPGME_Subkey {
} }
} }
/**
* Representing user attributes associated with a Key or subkey
* @class
* @protected
*/
class GPGME_UserId { class GPGME_UserId {
/**
* Initializes with the json data sent by gpgme-json
* @param {Object} data
* @private
*/
constructor(data){ constructor(data){
let keys = Object.keys(data); let keys = Object.keys(data);
for (let i=0; i< keys.length; i++) { for (let i=0; i< keys.length; i++) {
this.setProperty(keys[i], data[keys[i]]); this.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
*/
setProperty(property, value){ setProperty(property, value){
if (!this._data){ if (!this._data){
this._data = {}; this._data = {};
@ -356,10 +413,9 @@ class GPGME_UserId {
} }
/** /**
* * Fetches information about the user
* @param {String} property Information to request * @param {String} property Information to request
* @returns {String | Number} * @returns {String | Number}
* TODO: date properties are numbers with Date in seconds
*/ */
get(property) { get(property) {
if (this._data.hasOwnProperty(property)){ if (this._data.hasOwnProperty(property)){
@ -368,6 +424,13 @@ class GPGME_UserId {
} }
} }
/**
* 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 = { const validUserIdProperties = {
'revoked': function(value){ 'revoked': function(value){
return typeof(value) === 'boolean'; return typeof(value) === 'boolean';
@ -419,6 +482,12 @@ const validUserIdProperties = {
} }
}; };
/**
* 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 = { const validSubKeyProperties = {
'invalid': function(value){ 'invalid': function(value){
return typeof(value) === 'boolean'; return typeof(value) === 'boolean';
@ -471,8 +540,14 @@ const validSubKeyProperties = {
return (Number.isInteger(value) && value > 0); 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 = { const validKeyProperties = {
//TODO better validation?
'fingerprint': function(value){ 'fingerprint': function(value){
return isFingerprint(value); return isFingerprint(value);
}, },

View File

@ -27,24 +27,30 @@ import {createKey} from './Key';
import { isFingerprint } from './Helpers'; import { isFingerprint } from './Helpers';
import { gpgme_error } from './Errors'; import { gpgme_error } from './Errors';
/**
* This class offers access to the gnupg keyring
*/
export class GPGME_Keyring { export class GPGME_Keyring {
constructor(){ constructor(){
} }
/** /**
* @param {String} pattern (optional) pattern A pattern to search for, * Queries Keys (all Keys or a subset) from gnupg.
* in userIds or KeyIds
* @param {Boolean} prepare_sync (optional, default true) if set to true,
* Key.armor and Key.hasSecret will be called, so they can be used
* inmediately. This allows for full synchronous use. If set to false,
* these will initially only be available as Promises in getArmor() and
* getHasSecret()
* @param {Boolean} search (optional) retrieve the Keys from servers with
* the method(s) defined in gnupg (e.g. WKD/HKP lookup)
* @returns {Promise.<Array<GPGME_Key>>}
* *
* @param {String | Array<String>} pattern (optional) A pattern to search
* for in userIds or KeyIds.
* @param {Boolean} prepare_sync (optional) if set to true, the 'hasSecret'
* and 'armored' properties will be fetched for the Keys as well. These
* require additional calls to gnupg, resulting in a performance hungry
* operation. Calling them here enables direct, synchronous use of these
* properties for all keys, without having to resort to a refresh() first.
* @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>|GPGME_Error>}
* @static
* @async
*/ */
getKeys(pattern, prepare_sync, search){ getKeys(pattern, prepare_sync=false, search=false){
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
let msg = createMessage('keylist'); let msg = createMessage('keylist');
if (pattern !== undefined){ if (pattern !== undefined){
@ -102,10 +108,15 @@ export class GPGME_Keyring {
} }
/** /**
* Fetches the armored public Key blocks for all Keys matchin the pattern * Fetches the armored public Key blocks for all Keys matching the pattern
* (if no pattern is given, fetches all known to gnupg) * (if no pattern is given, fetches all keys known to gnupg). Note that the
* @param {String|Array<String>} pattern (optional) * result may be one big armored block, instead of several smaller armored
* @returns {Promise<String>} Armored Key blocks * blocks
* @param {String|Array<String>} pattern (optional) The Pattern to search
* for
* @returns {Promise<String|GPGME_Error>} Armored Key blocks
* @static
* @async
*/ */
getKeysArmored(pattern) { getKeysArmored(pattern) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
@ -123,15 +134,14 @@ export class GPGME_Keyring {
} }
/** /**
* Returns the Key to be used by default for signing operations, * Returns the Key used by default in gnupg.
* looking up the gpg configuration, or returning the first key that * (a.k.a. 'primary Key or 'main key').
* contains a secret key. * It looks up the gpg configuration if set, or the first key that contains
* @returns {Promise<GPGME_Key>} * a secret key.
* *
* * @returns {Promise<GPGME_Key|GPGME_Error>}
* TODO: getHasSecret always returns false at this moment, so this fucntion * @async
* still does not fully work as intended. * @static
* * @async
*/ */
getDefaultKey() { getDefaultKey() {
let me = this; let me = this;
@ -177,30 +187,40 @@ export class GPGME_Keyring {
} }
/** /**
* @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 * @param {String} armored Armored Key block of the Key(s) to be imported
* into gnupg * into gnupg
* @param {Boolean} prepare_sync prepare the keys for synched use * @param {Boolean} prepare_sync prepare the keys for synched use
* (see getKeys()). * (see {@link getKeys}).
* * @returns {Promise<importResult>} A summary and Keys considered.
* @returns {Promise<Object>} result: A summary and an array of Keys * @async
* considered * @static
*
* @returns result.summary: Numerical summary of the result. See the
* feedbackValues variable for available values and the gnupg documentation
* https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html
* for details on their meaning.
* @returns {Array<Object>} result.Keys: Array of objects containing:
* @returns {GPGME_Key} Key.key The resulting key
* @returns {String} Key.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.
* @returns {Boolean} Key.changes.userId: userIds changed
* @returns {Boolean} Key.changes.signature: signatures changed
* @returns {Boolean} Key.changes.subkey: subkeys changed
*/ */
importKey(armored, prepare_sync) { importKey(armored, prepare_sync) {
let feedbackValues = ['considered', 'no_user_id', 'imported', let feedbackValues = ['considered', 'no_user_id', 'imported',
@ -283,25 +303,36 @@ export class GPGME_Keyring {
} }
/**
* 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){ deleteKey(fingerprint){
if (isFingerprint(fingerprint) === true) { if (isFingerprint(fingerprint) === true) {
let key = createKey(fingerprint); let key = createKey(fingerprint);
key.delete(); return key.delete();
} else {
return Promise.reject(gpgme_error('KEY_INVALID'));
} }
} }
/** /**
* Generates a new Key pair directly in gpg, and returns a GPGME_Key * Generates a new Key pair directly in gpg, and returns a GPGME_Key
* representing that Key. Please note that due to security concerns, secret * representing that Key. Please note that due to security concerns, secret
* Keys can not be _deleted_ from inside gpgmejs. * Keys can not be deleted or exported from inside gpgme.js.
* *
* @param {String} userId The user Id, e.g. "Foo Bar <foo@bar.baz>" * @param {String} userId The user Id, e.g. 'Foo Bar <foo@bar.baz>'
* @param {*} algo (optional) algorithm (and optionally key size to be * @param {String} algo (optional) algorithm (and optionally key size) to
* used. See {@link supportedKeyAlgos } below for supported values. * be used. See {@link supportedKeyAlgos} below for supported values.
* @param {Date} expires (optional) Expiration date. If not set, expiration * @param {Date} expires (optional) Expiration date. If not set, expiration
* will be set to 'never' * will be set to 'never'
* *
* @return {Promise<Key>} * @return {Promise<Key|GPGME_Error>}
* @async
*/ */
generateKey(userId, algo = 'default', expires){ generateKey(userId, algo = 'default', expires){
if ( if (
@ -336,7 +367,8 @@ export class GPGME_Keyring {
} }
/** /**
* A list of algorithms supported for key generation. * List of algorithms supported for key generation. Please refer to the gnupg
* documentation for details
*/ */
const supportedKeyAlgos = [ const supportedKeyAlgos = [
'default', 'default',

View File

@ -25,6 +25,12 @@ import { permittedOperations } from './permittedOperations';
import { gpgme_error } from './Errors'; import { gpgme_error } from './Errors';
import { Connection } from './Connection'; import { Connection } from './Connection';
/**
* Initializes a message for gnupg, validating the message's purpose with
* {@link permittedOperations} first
* @param {String} operation
* @returns {GPGME_Message|GPGME_Error} The Message object
*/
export function createMessage(operation){ export function createMessage(operation){
if (typeof(operation) !== 'string'){ if (typeof(operation) !== 'string'){
return gpgme_error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
@ -37,12 +43,13 @@ export function createMessage(operation){
} }
/** /**
* Prepares a communication request. It checks operations and parameters in * A Message collects, validates and handles all information required to
* ./permittedOperations. * successfully establish a meaningful communication with gpgme-json via
* @param {String} operation * {@link Connection.post}. The definition on which communication is available
* can be found in {@link permittedOperations}.
* @class
*/ */
export class GPGME_Message { export class GPGME_Message {
//TODO getter
constructor(operation){ constructor(operation){
this.operation = operation; this.operation = operation;
@ -63,9 +70,13 @@ export class GPGME_Message {
} }
/** /**
* Set the maximum size of responses from gpgme in bytes. Values allowed * The maximum size of responses from gpgme in bytes. As of July 2018,
* range from 10kB to 1MB. The lower limit is arbitrary, the upper limit * most browsers will only accept answers up to 1 MB of size. Everything
* fixed by browsers' nativeMessaging specifications * 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){ set chunksize(value){
if ( if (
@ -85,8 +96,9 @@ export class GPGME_Message {
} }
/** /**
* If expect is set to 'base64', the response is expected to be base64 * Expect indicates which format data is expected to be in. It currently
* encoded binary * only supports 'base64', indicating that the response data from gnupg are
* expected to be base64 encoded binary
*/ */
set expect(value){ set expect(value){
if (value ==='base64'){ if (value ==='base64'){
@ -103,13 +115,13 @@ export class GPGME_Message {
/** /**
* Sets a parameter for the message. Note that the operation has to be set * Sets a parameter for the message. It validates with
* first, to be able to check if the parameter is permittted * {@link permittedOperations}
* @param {String} param Parameter to set * @param {String} param Parameter to set
* @param {any} value Value to set //TODO: Some type checking * @param {any} value Value to set
* @returns {Boolean} If the parameter was set successfully * @returns {Boolean} If the parameter was set successfully
*/ */
setParameter(param,value){ setParameter( param,value ){
if (!param || typeof(param) !== 'string'){ if (!param || typeof(param) !== 'string'){
return gpgme_error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
@ -125,6 +137,7 @@ export class GPGME_Message {
} else { } else {
return gpgme_error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
// check incoming value for correctness
let checktype = function(val){ let checktype = function(val){
switch(typeof(val)){ switch(typeof(val)){
case 'string': case 'string':
@ -187,9 +200,9 @@ export class GPGME_Message {
} }
/** /**
* Check if the message has the minimum requirements to be sent, according * Check if the message has the minimum requirements to be sent, that is
* to the definitions in permittedOperations * all 'required' parameters according to {@link permittedOperations}.
* @returns {Boolean} * @returns {Boolean} true if message is complete.
*/ */
get isComplete(){ get isComplete(){
if (!this._msg.op){ if (!this._msg.op){
@ -222,13 +235,18 @@ export class GPGME_Message {
} }
/**
* Sends the Message via nativeMessaging and resolves with the answer.
* @returns {Promise<Object|GPGME_Error>}
* @async
*/
post(){ post(){
let me = this; let me = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
if (me.isComplete === true) { if (me.isComplete === true) {
let conn = new Connection; let conn = new Connection;
if (me._msg.chunksize === undefined){ if (me._msg.chunksize === undefined){
me._msg.chunksize = 1023*1024; me._msg.chunksize = me.chunksize;
} }
conn.post(me).then(function(response) { conn.post(me).then(function(response) {
resolve(response); resolve(response);

View File

@ -20,17 +20,16 @@
* Author(s): * Author(s):
* Maximilian Krambach <mkrambach@intevation.de> * Maximilian Krambach <mkrambach@intevation.de>
*/ */
/**
* Validates a signature object and returns
* @param {Object} sigObject Object as returned by gpgme-json. The definition
* of the expected values are to be found in the constants 'expKeys', 'expSum',
* 'expNote' in this file.
* @returns {GPGME_Signature} Signature Object
*/
import { gpgme_error } from './Errors'; 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){ export function createSignature(sigObject){
if ( if (
typeof(sigObject) !=='object' || typeof(sigObject) !=='object' ||
@ -72,18 +71,20 @@ export function createSignature(sigObject){
/** /**
* Representing the details of a signature. It is supposed to be read-only. The * Representing the details of a signature. The full details as given by
* full details as given by gpgme-json can be accessed from the _rawSigObject. * 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 { class GPGME_Signature {
constructor(sigObject){ constructor(sigObject){
this._rawSigObject = sigObject; this._rawSigObject = sigObject;
} }
/**
* The signatures' fingerprint
*/
get fingerprint(){ get fingerprint(){
return this._rawSigObject.fingerprint; return this._rawSigObject.fingerprint;
} }
@ -105,7 +106,7 @@ class GPGME_Signature {
* @returns {Date} * @returns {Date}
*/ */
get timestamp(){ get timestamp(){
return new Date(this._rawSigObject.timestamp* 1000); return new Date(this._rawSigObject.timestamp * 1000);
} }
/** /**

View File

@ -28,10 +28,62 @@ import { gpgme_error } from './Errors';
import { GPGME_Keyring } from './Keyring'; import { GPGME_Keyring } from './Keyring';
import { createSignature } from './Signature'; 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 { export class GpgME {
/**
* initializes GpgME by opening a nativeMessaging port
*/
constructor(){ constructor(){
} }
@ -41,6 +93,9 @@ export class GpgME {
} }
} }
/**
* Accesses the {@link GPGME_Keyring}.
*/
get Keyring(){ get Keyring(){
if (!this._Keyring){ if (!this._Keyring){
this._Keyring = new GPGME_Keyring; this._Keyring = new GPGME_Keyring;
@ -49,23 +104,23 @@ export class GpgME {
} }
/** /**
* Encrypt (and optionally sign) a Message * Encrypt (and optionally sign) data
* @param {String|Object} data text/data to be encrypted as String. Also * @param {String|Object} data text/data to be encrypted as String. Also
* accepts Objects with a getText method * accepts Objects with a getText method
* @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} publicKeys * @param {inputKeys} publicKeys
* Keys used to encrypt the message * Keys used to encrypt the message
* @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} secretKeys * @param {inputKeys} secretKeys (optional) Keys used to sign the message.
* (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 * @param {Boolean} base64 (optional) The data will be interpreted as
* base64 encoded data * base64 encoded data.
* @param {Boolean} armor (optional) Request the output as armored block * @param {Boolean} armor (optional) Request the output as armored block.
* @param {Boolean} wildcard (optional) If true, recipient information will * @param {Boolean} wildcard (optional) If true, recipient information will
* not be added to the message * not be added to the message.
* @param {Object} additional use additional gpg options * @param {Object} additional use additional valid gpg options as defined
* (refer to src/permittedOperations) * in {@link permittedOperations}
* @returns {Promise<Object>} Encrypted message: * @returns {Promise<encrypt_result>} Object containing the encrypted
* data: The encrypted message * message and additional info.
* base64: Boolean indicating whether data is base64 encoded.
* @async * @async
*/ */
encrypt(data, publicKeys, secretKeys, base64=false, armor=true, encrypt(data, publicKeys, secretKeys, base64=false, armor=true,
@ -105,28 +160,12 @@ export class GpgME {
} }
/** /**
* Decrypt a Message * Decrypts a Message
* @param {String|Object} data text/data to be decrypted. Accepts Strings * @param {String|Object} data text/data to be decrypted. Accepts Strings
* and Objects with a getText method * and Objects with a getText method
* @param {Boolean} base64 (optional) false if the data is an armored block, * @param {Boolean} base64 (optional) false if the data is an armored block,
* true if it is base64 encoded binary data * true if it is base64 encoded binary data
* @returns {Promise<Object>} result: Decrypted Message and information * @returns {Promise<decrypt_result>} Decrypted Message and information
* @returns {String} result.data: The decrypted data.
* @returns {Boolean} result.base64: indicating whether data is base64
* encoded.
* @returns {Boolean} result.is_mime: Indicating whether the data is a MIME
* object.
* @returns {String} result.file_name: The optional original file name
* @returns {Object} message.signatures Verification details for signatures:
* @returns {Boolean} message.signatures.all_valid: true if all signatures
* are valid
* @returns {Number} message.signatures.count: Number of signatures found
* @returns {Number} message.signatures.failures Number of invalid
* signatures
* @returns {Array<Object>} message.signatures.signatures. Two arrays
* (good & bad) of {@link GPGME_Signature} objects, offering further
* information.
*
* @async * @async
*/ */
decrypt(data, base64=false){ decrypt(data, base64=false){
@ -169,16 +208,13 @@ export class GpgME {
/** /**
* Sign a Message * Sign a Message
* @param {String|Object} data text/data to be signed. Accepts Strings * @param {String|Object} data text/data to be signed. Accepts Strings
* and Objects with a gettext methos * and Objects with a getText method.
* @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} keys The * @param {inputKeys} keys The key/keys to use for signing
* key/keys to use for signing * @param {String} mode The signing mode. Currently supported:
* @param {*} mode The signing mode. Currently supported: * 'clearsign':The Message is embedded into the signature;
* 'clearsign': (default) The Message is embedded into the signature * 'detached': The signature is stored separately
* 'detached': The signature is stored separately * @param {Boolean} base64 input is considered base64
* @param {*} base64 input is considered base64 * @returns {Promise<signResult>}
* @returns {Promise<Object>}
* data: The resulting data. Includes the signature in clearsign mode
* signature: The detached signature (if in detached mode)
* @async * @async
*/ */
sign(data, keys, mode='clearsign', base64=false) { sign(data, keys, mode='clearsign', base64=false) {
@ -221,20 +257,12 @@ export class GpgME {
/** /**
* Verifies data. * Verifies data.
* @param {String|Object} data text/data to be verified. Accepts Strings * @param {String|Object} data text/data to be verified. Accepts Strings
* and Objects with a gettext method * and Objects with a getText method
* @param {String} (optional) A detached signature. If not present, opaque * @param {String} (optional) A detached signature. If not present, opaque
* mode is assumed * mode is assumed
* @param {Boolean} (optional) Data and signature are base64 encoded * @param {Boolean} (optional) Data and signature are base64 encoded
* // TODO verify if signature really is assumed to be base64 * @returns {Promise<verifyResult>}
* @returns {Promise<Object>} result: *@async
* @returns {Boolean} result.data: The verified data
* @returns {Boolean} result.is_mime: The message claims it is MIME
* @returns {String} result.file_name: The optional filename of the message
* @returns {Boolean} result.all_valid: true if all signatures are valid
* @returns {Number} result.count: Number of signatures found
* @returns {Number} result.failures Number of unsuccessful signatures
* @returns {Array<Object>} result.signatures. Two arrays (good & bad) of
* {@link GPGME_Signature} objects, offering further information.
*/ */
verify(data, signature, base64 = false){ verify(data, signature, base64 = false){
let msg = createMessage('verify'); let msg = createMessage('verify');
@ -275,7 +303,10 @@ export class GpgME {
/** /**
* Sets the data of the message, setting flags according on the data type * 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 {GPGME_Message} message The message where this data will be set
* @param {*} data The data to enter * @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){ function putData(message, data){
if (!message || !(message instanceof GPGME_Message) ) { if (!message || !(message instanceof GPGME_Message) ) {
@ -301,6 +332,11 @@ function putData(message, data){
} }
} }
/**
* Parses, validates and converts incoming objects into signatures.
* @param {Array<Object>} sigs
* @returns {signatureDetails} Details about the signatures
*/
function collectSignatures(sigs){ function collectSignatures(sigs){
if (!Array.isArray(sigs)){ if (!Array.isArray(sigs)){
return gpgme_error('SIG_NO_SIGS'); return gpgme_error('SIG_NO_SIGS');

View File

@ -27,8 +27,8 @@ import { gpgme_error } from './Errors';
import { Connection } from './Connection'; import { Connection } from './Connection';
/** /**
* Tests nativeMessaging once and returns a GpgME object if successful. * Initializes gpgme.js by testing the nativeMessaging connection once.
* @returns {GpgME | Error} * @returns {Promise<GpgME> | GPGME_Error}
* *
* @async * @async
*/ */

View File

@ -22,27 +22,30 @@
*/ */
/** /**
* Definition of the possible interactions with gpgme-json. * @typedef {Object} messageProperty
* operation: <Object> * A message Property is defined by it's key.
required: Array<Object> * @property {Array<String>} allowed Array of allowed types.
<String> name The name of the property * Currently accepted values are 'number', 'string', 'boolean'.
allowed: Array of allowed types. Currently accepted values: * @property {Boolean} array_allowed If the value can be an array of types
['number', 'string', 'boolean', 'Uint8Array'] * defined in allowed
array_allowed: Boolean. If the value can be an array of the above * @property {<Array>} allowed_data (optional) restricts to the given values
allowed_data: <Array> If present, restricts to the given value */
optional: Array<Object>
see 'required', with these parameters not being mandatory for a
complete message
pinentry: boolean If a pinentry dialog is expected, and a timeout of
5000 ms would be too short
answer: <Object>
type: <String< The content type of answer expected
data: <Object>
the properties expected and their type, eg: {'data':'string'}
}
}
*/
/**
* 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 = { export const permittedOperations = {
encrypt: { encrypt: {
pinentry: true, //TODO only with signing_keys pinentry: true, //TODO only with signing_keys