} (if in async
+ * mode)
+ *
+ * Returns the value of the property requested. 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 [getArmor]{@link GPGME_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 the Key
+ * 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 [Keyring.getKeys]{@link Keyring#getKeys} instead.
+ * @returns {Promise}
+ * @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}
+ * @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
+ * [Keyring.getKeys]{@link Keyring#getKeys} instead, as it performs faster
+ * in bulk querying.
+ * @returns {Promise} 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, and gnupg will
+ * refuse to delete a Key if there is still a secret/private Key present
+ * to that public Key
+ * @returns {Promise} Success if key was deleted.
+ */
+ 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. See {@link validSubKeyProperties} for
+ * possible properties.
+ * @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. See
+ * {@link validUserIdProperties} for possible properties.
+ * @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} subkeys
+ * @param {Array} userids
+ * @param {Array} 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 0000000..2071c6d
--- /dev/null
+++ b/lang/js/src/Keyring.js
@@ -0,0 +1,439 @@
+/* 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 { 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 {Object} options:
+ * @param {String | Array} options.pattern (optional) A pattern to
+ * search for in userIds or KeyIds.
+ * @param {Boolean} options.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} options.search (optional) retrieve Keys from external
+ * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup)
+ * @returns {Promise}
+ * @static
+ * @async
+ */
+ getKeys ({ pattern, prepare_sync = false, search = false } = {}){
+ if (typeof arguments[0] !== 'object') {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ if (arguments.length && typeof arguments[0] !== 'object') {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ return new Promise(function (resolve, reject) {
+ let msg = createMessage('keylist');
+ if (pattern) {
+ 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} 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 {Object} options (optional)
+ * @param {String|Array} options.pattern The Pattern to
+ * search for
+ * @param {Boolean} options.with_secret_fpr also return a list of
+ * fingerprints for the keys that have a secret key available
+ * @returns {Promise} 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){
+ msg.setParameter('keys', pattern);
+ }
+ msg.post().then(function (answer){
+ const result = { armored: answer.data };
+ if (with_secret_fpr === true){
+ if (answer.hasOwnProperty('sec-fprs')){
+ result.secret_fprs = answer['sec-fprs'];
+ } else {
+ result.secret_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}
+ * @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({ pattern: resp.option.value[0].string,
+ prepare_sync: 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 &&
+ result.keys[i].expired === false &&
+ result.keys[i].revoked === false &&
+ result.keys[i].can_sign === true
+ ) {
+ 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} 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} 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}
+ * @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 {Object} options
+ * @param {String} option.userId The user Id, e.g. 'Foo Bar '
+ * @param {String} option.algo (optional) algorithm (and optionally key
+ * size) to be used. See {@link supportedKeyAlgos} below for supported
+ * values. If ommitted, 'default' is used.
+ * @param {Number} option.expires (optional) Expiration time in seconds
+ * from now. If not set or set to 0, expiration will be 'never'
+ *
+ * @return {Promise}
+ * @async
+ */
+ generateKey ({ userId, algo = 'default', expires= 0 } = {}){
+ if (typeof userId !== 'string'
+ // eslint-disable-next-line no-use-before-define
+ || (algo && supportedKeyAlgos.indexOf(algo) < 0 )
+ || (!Number.isInteger(expires) || expires < 0 )
+ ){
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ // eslint-disable-next-line no-use-before-define
+ let me = this;
+ return new Promise(function (resolve, reject){
+ let msg = createMessage('createkey');
+ msg.setParameter('userid', userId);
+ msg.setParameter('algo', algo);
+ msg.setParameter('expires', expires);
+ msg.post().then(function (response){
+ me.getKeys({
+ pattern: response.fingerprint,
+ prepare_sync: true
+ }).then(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', 'future-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'
+];
diff --git a/lang/js/src/Makefile.am b/lang/js/src/Makefile.am
new file mode 100644
index 0000000..dc58fd3
--- /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/Makefile.in b/lang/js/src/Makefile.in
new file mode 100644
index 0000000..fe5e274
--- /dev/null
+++ b/lang/js/src/Makefile.in
@@ -0,0 +1,540 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# 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
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lang/js/src
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(top_srcdir)/build-aux/mkinstalldirs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/ax_pkg_swig.m4 \
+ $(top_srcdir)/m4/ax_python_devel.m4 \
+ $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \
+ $(top_srcdir)/m4/gnupg-ttyname.m4 \
+ $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \
+ $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/conf/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_FILEVERSION = @BUILD_FILEVERSION@
+BUILD_REVISION = @BUILD_REVISION@
+BUILD_TIMESTAMP = @BUILD_TIMESTAMP@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENABLED_LANGUAGES = @ENABLED_LANGUAGES@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@
+GLIBC21 = @GLIBC21@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@
+GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@
+GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@
+GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@
+GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@
+GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@
+GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@
+GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@
+GPGME_QT_LIBS = @GPGME_QT_LIBS@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@
+GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@
+GRAPHVIZ = @GRAPHVIZ@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@
+LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@
+LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@
+LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@
+LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@
+LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@
+LIBOBJS = @LIBOBJS@
+LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@
+LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@
+LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MOC = @MOC@
+MOC2 = @MOC2@
+NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PYTHON = @PYTHON@
+PYTHONS = @PYTHONS@
+PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@
+PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@
+PYTHON_LDFLAGS = @PYTHON_LDFLAGS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_SITE_PKG = @PYTHON_SITE_PKG@
+PYTHON_VERSION = @PYTHON_VERSION@
+QTCHOOSER = @QTCHOOSER@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SWIG = @SWIG@
+SWIG_LIB = @SWIG_LIB@
+SYSROOT = @SYSROOT@
+VERSION = @VERSION@
+VERSION_MAJOR = @VERSION_MAJOR@
+VERSION_MICRO = @VERSION_MICRO@
+VERSION_MINOR = @VERSION_MINOR@
+VERSION_NUMBER = @VERSION_NUMBER@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+emacs_local_vars_begin = @emacs_local_vars_begin@
+emacs_local_vars_end = @emacs_local_vars_end@
+emacs_local_vars_read_only = @emacs_local_vars_read_only@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = Connection.js \
+ Errors.js \
+ gpgmejs.js \
+ Helpers.js \
+ index.js \
+ Key.js \
+ Keyring.js \
+ Message.js \
+ permittedOperations.js \
+ Signature.js
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/js/src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lang/js/src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js
new file mode 100644
index 0000000..9f6abb7
--- /dev/null
+++ b/lang/js/src/Message.js
@@ -0,0 +1,243 @@
+/* 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 { 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
+ * [Connection.post]{@link Connection#post}. The definition on which
+ * communication is available can be found in {@link permittedOperations}.
+ * @class
+ * @protected
+ */
+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 === 'uint8' || value === 'base64'){
+ this._expected = value;
+ }
+ }
+
+ get expected () {
+ return this._expected;
+ }
+ /**
+ * The maximum size of responses from gpgme in bytes. As of September 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 after their parameters and the completion
+ * of required parameters have been 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} True if the parameter was set successfully.
+ * Throws errors if the parameters don't match the message operation
+ */
+ 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} GPGME response
+ * @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 0000000..f848e32
--- /dev/null
+++ b/lang/js/src/Signature.js
@@ -0,0 +1,206 @@
+/* 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 { 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
+ * @private
+ */
+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;
+ }
+ /**
+ * @returns {String} the fingerprint of this signature
+ */
+ get fingerprint (){
+ if (!this._rawSigObject.fingerprint){
+ throw 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;
+ }
+ }
+
+ /**
+ * Object with boolean properties giving more information on non-valid
+ * signatures. Refer to the [gpgme docs]{@link https://www.gnupg.org/documentation/manuals/gpgme/Verify.html}
+ * for details on the values.
+ */
+ 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.summary.hasOwnProperty(properties[i]) ){
+ result[properties[i]] = this._rawSigObject.summary[properties[i]];
+ }
+ }
+ return result;
+ }
+}
+
+/**
+ * Expected keys and their value's type for the signature Object
+ * @private
+ */
+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
+ * @private
+ */
+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
+ * @private
+ */
+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 0000000..9105724
--- /dev/null
+++ b/lang/js/src/gpgmejs.js
@@ -0,0 +1,465 @@
+/* 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 { 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|Uint8Array} data The decrypted data.
+ * @property {String} format Indicating how the data was converted after being
+ * received from gpgme:
+ *
+ * 'ascii': Data was ascii-encoded and no further processed
+ * 'string': Data was decoded into an utf-8 string,
+ * 'base64': Data was not processed and is a base64 string
+ * 'uint8': data was turned into a Uint8Array
+ *
+ * @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 Quick summary. True if all signatures are
+ * fully valid according to gnupg.
+ * @property {Number} count Number of signatures parsed.
+ * @property {Number} failures Number of signatures not passing as valid. This
+ * may imply bad signatures, or signatures with e.g. the public Key not being
+ * available.
+ * @property {GPGME_Signature[]} signatures.good Array of all signatures
+ * considered valid.
+ * @property {GPGME_Signature[]} signatures.bad All invalid signatures.
+ */
+
+/**
+ * @typedef {Object} encrypt_result The result of an encrypt operation,
+ * containing the encrypted data and some additional information.
+ * @property {String} data The encrypted message.
+ * @property {String} format Indicating how the data was converted after being
+ * received from gpgme.
+ *
+ * 'ascii': Data was ascii-encoded and no further processed
+ * 'string': Data was decoded into an utf-8 string,
+ * 'base64': Data was not processed and is a base64 string
+ * 'uint8': Data was turned into a Uint8Array
+ *
+ */
+
+/**
+ * @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 (only present in 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 {signatureDetails} signatures Verification details for
+ * signatures
+ */
+
+/**
+ * The main entry point for gpgme.js.
+ * @class
+ */
+export class GpgME {
+
+ constructor (){
+ this._Keyring = null;
+ }
+
+ set Keyring (keyring){
+ if (keyring && keyring instanceof GPGME_Keyring){
+ this._Keyring = keyring;
+ }
+ }
+
+ /**
+ * Accesses the {@link GPGME_Keyring}. From the Keyring, all Keys can be
+ * accessed.
+ */
+ get Keyring (){
+ if (!this._Keyring){
+ this._Keyring = new GPGME_Keyring;
+ }
+ return this._Keyring;
+ }
+
+ /**
+ * Encrypt data for the recipients specified in publicKeys. If privateKeys
+ * are submitted, the data will be signed by those Keys.
+ * @param {Object} options
+ * @param {String|Object} options.data text/data to be encrypted as String.
+ * Also accepts Objects with a getText method.
+ * @param {inputKeys} options.publicKeys
+ * Keys used to encrypt the message
+ * @param {inputKeys} options.secretKeys (optional) Keys used to sign the
+ * message. If Keys are present, the operation requested is assumed
+ * to be 'encrypt and sign'
+ * @param {Boolean} options.base64 (optional, default: false) The data will
+ * be interpreted as base64 encoded data.
+ * @param {Boolean} options.armor (optional, default: true) Request the
+ * output as armored block.
+ * @param {Boolean} options.wildcard (optional, default: false) If true,
+ * recipient information will not be added to the message.
+ * @param {Boolean} options.always_trust (optional, default true) This
+ * assumes that used keys are fully trusted. If set to false, encryption to
+ * a key not fully trusted in gnupg will fail.
+ * @param {String} options.expect (default: 'base64') In case of
+ * armored:false, request how to return the binary result.
+ * Accepts 'base64' or 'uint8'
+ * @param {Object} options.additional use additional valid gpg options as
+ * defined in {@link permittedOperations}
+ * @returns {Promise} Object containing the encrypted
+ * message and additional info.
+ * @async
+ */
+ encrypt ({ data, publicKeys, secretKeys, base64 = false, armor = true,
+ wildcard, always_trust = true, expect = 'base64',
+ additional = {} } = {}){
+ if (typeof arguments[0] !== 'object') {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ if (!data || !publicKeys){
+ return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
+ }
+ let msg = createMessage('encrypt');
+ if (msg instanceof Error){
+ return Promise.reject(msg);
+ }
+ if (armor === false){
+ msg.setParameter('armor', false);
+ if (expect === 'uint8' || expect === 'base64') {
+ msg.expected = expect;
+ } else {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ } else if (armor === true) {
+ msg.setParameter('armor', true);
+ }
+ if (base64 === true) {
+ msg.setParameter('base64', true);
+ }
+ if (always_trust === true) {
+ msg.setParameter('always-trust', true);
+ }
+ let pubkeys = toKeyIdArray(publicKeys);
+ if (!pubkeys.length) {
+ return Promise.reject(gpgme_error('MSG_NO_KEYS'));
+ }
+ 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 (and verifies, if applicable) a message.
+ * @param {Object} options
+ * @param {String|Object} options.data text/data to be decrypted. Accepts
+ * Strings and Objects with a getText method.
+ * @param {Boolean} options.base64 (optional, default: false). Indicate that
+ * the input given is base64-encoded binary instead of an armored block in
+ * gpg armored form.
+ * @param {String} options.expect (optional). By default, the output is
+ * expected to be a string compatible with javascript. In cases of binary
+ * data the decryption may fail due to encoding problems. For data expected
+ * to return as binary data, the decroding after decryption can be bypassed:
+ *
+ * 'uint8': Return as Uint8Array
+ * 'base64': Return as unprocessed (base64 encoded) string.
+ *
+ * @returns {Promise} Decrypted Message and information
+ * @async
+ */
+ decrypt ({ data, base64, expect } = {}){
+ if (typeof arguments[0] !== 'object') {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ if (!data){
+ 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);
+ }
+ if (expect === 'base64' || expect === 'uint8'){
+ msg.expected = expect;
+ }
+ putData(msg, data);
+ return new Promise(function (resolve, reject){
+ msg.post().then(function (result){
+ let returnValue = { data: result.data };
+ returnValue.format = result.format ? result.format : null;
+ if (result.hasOwnProperty('dec_info')){
+ returnValue.is_mime = result.dec_info.is_mime ? true: false;
+ if (result.dec_info.file_name) {
+ returnValue.file_name = result.dec_info.file_name;
+ }
+ }
+ if (!returnValue.file_name) {
+ returnValue.file_name = null;
+ }
+ if (result.hasOwnProperty('info')
+ && result.info.hasOwnProperty('signatures')
+ && Array.isArray(result.info.signatures)
+ ) {
+ returnValue.signatures = collectSignatures(
+ result.info.signatures);
+ }
+ if (returnValue.signatures instanceof Error){
+ reject(returnValue.signatures);
+ } else {
+ resolve(returnValue);
+ }
+ }, function (error){
+ reject(error);
+ });
+ });
+ }
+
+ /**
+ * Sign a Message.
+ * @param {Object} options Signing options
+ * @param {String|Object} options.data text/data to be signed. Accepts
+ * Strings and Objects with a getText method.
+ * @param {inputKeys} options.keys The key/keys to use for signing
+ * @param {String} options.mode The signing mode. Currently supported:
+ *
+ * 'clearsign':The Message is embedded into the signature;
+ * 'detached': The signature is stored separately
+ *
+ * @param {Boolean} options.base64 input is considered base64
+ * @returns {Promise}
+ * @async
+ */
+ sign ({ data, keys, mode = 'clearsign', base64 } = {}){
+ if (typeof arguments[0] !== 'object') {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ if (!data){
+ 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) {
+ 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 {Object} options
+ * @param {String|Object} options.data text/data to be verified. Accepts
+ * Strings and Objects with a getText method
+ * @param {String} options.signature A detached signature. If not present,
+ * opaque mode is assumed
+ * @param {Boolean} options.base64 Indicating that data and signature are
+ * base64 encoded
+ * @returns {Promise}
+ *@async
+ */
+ verify ({ data, signature, base64 } = {}){
+ if (typeof arguments[0] !== 'object') {
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ if (!data){
+ return Promise.reject(gpgme_error('PARAM_WRONG'));
+ }
+ 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 returnValue = {
+ signatures: collectSignatures(message.info.signatures)
+ };
+ if (returnValue.signatures instanceof Error){
+ reject(returnValue.signatures);
+ } else {
+ returnValue.is_mime = message.info.is_mime? true: false;
+ if (message.info.filename){
+ returnValue.file_name = message.info.filename;
+ }
+ returnValue.data = message.data;
+ resolve(returnValue);
+ }
+ }
+ }, 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} sigs
+ * @returns {signatureDetails} Details about the signatures
+ * @private
+ */
+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 0000000..b8e4274
--- /dev/null
+++ b/lang/js/src/index.js
@@ -0,0 +1,58 @@
+/* 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 { GpgME } from './gpgmejs';
+import { gpgme_error } from './Errors';
+import { Connection } from './Connection';
+
+/**
+ * Main entry point for gpgme.js. It initializes by testing the nativeMessaging
+ * connection once, and then offers the available functions as method of the
+ * response object.
+ * An unsuccessful attempt will reject as a GPGME_Error.
+ * @param {Object} config (optional) configuration options
+ * @param {Number} config.timeout set the timeout for the initial connection
+ * check. On some machines and operating systems a default timeout of 500 ms is
+ * too low, so a higher number might be attempted.
+ * @returns {Promise}
+ * @async
+ */
+function init ({ timeout = 500 } = {}){
+ return new Promise(function (resolve, reject){
+ const connection = new Connection;
+ connection.checkConnection(false, timeout).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 0000000..09a1783
--- /dev/null
+++ b/lang/js/src/permittedOperations.js
@@ -0,0 +1,413 @@
+/* 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
+ */
+
+/**
+ * @typedef {Object} messageProperty
+ * A message Property is defined by it's key.
+ * @property {Array} 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} answer.type the type(s) as reported by gpgme-json.
+ * @property {Object} answer.payload key-value combinations of expected
+ * properties of an answer and their type ('boolean', 'string', object), which
+ * may need further decoding from base64
+ * @property {Object} answer.info key-value combinations of expected
+ * properties of an answer and their type ('boolean', 'string', object), which
+ * are meant to be data directly sent by gpgme (i.e. user ids)
+ @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']
+ },
+ 'sender': {
+ allowed: ['string']
+ },
+ 'file_name': {
+ allowed: ['string']
+ }
+ },
+ answer: {
+ type: ['ciphertext'],
+ payload: {
+ 'data': 'string'
+ },
+ info: {
+ 'base64':'boolean'
+ }
+ }
+ },
+
+ decrypt: {
+ pinentry: true,
+ required: {
+ 'data': {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ 'protocol': {
+ allowed: ['string'],
+ allowed_data: ['cms', 'openpgp']
+ },
+ 'base64': {
+ allowed: ['boolean']
+ }
+ },
+ answer: {
+ type: ['plaintext'],
+ payload: {
+ 'data': 'string',
+ },
+ info: {
+ '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'],
+ payload: {
+ 'data': 'string',
+ },
+ info: {
+ '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'],
+ info: {
+ 'keys': 'object',
+ 'base64': 'boolean',
+ }
+ }
+ },
+
+ 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'],
+ payload: {
+ 'data': 'string',
+ },
+ info: {
+ 'base64': 'boolean',
+ 'sec-fprs': 'object'
+ }
+ }
+ },
+
+ import: {
+ required: {
+ 'data': {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ 'protocol': {
+ allowed: ['string'],
+ allowed_data: ['cms', 'openpgp']
+ },
+ 'base64': {
+ allowed: ['boolean']
+ },
+ },
+ answer: {
+ type: [],
+ info: {
+ 'result': 'object'
+ }
+ }
+ },
+
+ delete: {
+ pinentry: true,
+ required:{
+ 'key': {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ 'protocol': {
+ allowed: ['string'],
+ allowed_data: ['cms', 'openpgp']
+ },
+ },
+ answer: {
+ info: {
+ 'success': 'boolean'
+ }
+ }
+ },
+
+ version: {
+ required: {},
+ optional: {},
+ answer: {
+ type: [''],
+ info: {
+ 'gpgme': 'string',
+ 'info': 'object'
+ }
+ }
+ },
+
+ createkey: {
+ pinentry: true,
+ required: {
+ userid: {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ algo: {
+ allowed: ['string']
+ },
+ expires: {
+ allowed: ['number'],
+ }
+ },
+ answer: {
+ type: [''],
+ info: { 'fingerprint': 'string' }
+ }
+ },
+
+ verify: {
+ required: {
+ data: {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ 'protocol': {
+ allowed: ['string'],
+ allowed_data: ['cms', 'openpgp']
+ },
+ 'signature': {
+ allowed: ['string']
+ },
+ 'base64':{
+ allowed: ['boolean']
+ }
+ },
+ answer: {
+ type: ['plaintext'],
+ payload:{
+ 'data': 'string'
+ },
+ info: {
+ '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: [],
+ info: {
+ 'option': 'object'
+ }
+ }
+ }
+};
diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js
new file mode 100644
index 0000000..659ef85
--- /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' +
+ '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' +
+ '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 0000000..f28be76
--- /dev/null
+++ b/lang/js/unittests.js
@@ -0,0 +1,386 @@
+/* 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+
+ */
+
+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;
+const connectionTimeout = 2000;
+
+function unittests (){
+ describe('Connection testing', function (){
+
+ it('Connecting', function (done) {
+ let conn0 = new Connection;
+ conn0.checkConnection(true, connectionTimeout).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, connectionTimeout).then(
+ function (answer) {
+ expect(answer).to.be.true;
+ conn0.disconnect();
+ conn0.checkConnection(false, connectionTimeout).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({ prepare_sync: 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({
+ pattern: kp.validKeyFingerprint,
+ prepare_sync: 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({
+ pattern: kp.invalidKeyFingerprint,
+ prepare_sync: 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 0000000..19f3bbd
--- /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 .
+ * 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 0000000..c3c87f3
--- /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 .
+ * 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'
+ }
+};
diff --git a/lang/python/MANIFEST.in b/lang/python/MANIFEST.in
index c34e84a..f4def41 100644
--- a/lang/python/MANIFEST.in
+++ b/lang/python/MANIFEST.in
@@ -1,4 +1,10 @@
-recursive-include examples *.py
+recursive-include doc *.org
+recursive-include doc *.rst
+recursive-include doc *.tex
+recursive-include doc *.texi
+recursive-include doc *.info
+recursive-include examples *.py *.pyx
+include README README.org
include gpgme.i
include helpers.c helpers.h private.h
include version.py
diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index 8d74cbd..6988faf 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -22,6 +22,7 @@ EXTRA_DIST = \
gpgme.i \
helpers.c helpers.h private.h \
examples \
+ doc \
src
SUBDIRS = . tests
@@ -33,7 +34,7 @@ prepare: copystamp
# distutils are not VPATH-aware.
copystamp:
ln -sf "$(top_srcdir)/src/data.h" .
- ln -sf "$(top_builddir)/config.h" .
+ ln -sf "$(top_builddir)/conf/config.h" .
ln -sf "$(srcdir)/src" gpg
touch $@
@@ -54,7 +55,8 @@ python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp
top_builddir="$(top_builddir)" \
$(PYTHON) setup.py sdist --verbose --dist-dir=python$(PYTHON_VERSION)-gpg-dist \
--manifest=python$(PYTHON_VERSION)-gpg-dist/MANIFEST
- gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
+ gpgbin=gpgconf --list-components | grep OpenPGP | sed -e 's/gpg:OpenPGP://g'
+ $(gpgbin) --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
.PHONY: sdist
sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
diff --git a/lang/python/Makefile.in b/lang/python/Makefile.in
index ae0334f..95d6e7e 100644
--- a/lang/python/Makefile.in
+++ b/lang/python/Makefile.in
@@ -114,7 +114,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
CONFIG_CLEAN_FILES = version.py setup.py
CONFIG_CLEAN_VPATH_FILES =
AM_V_P = $(am__v_P_@AM_V@)
@@ -398,6 +398,7 @@ EXTRA_DIST = \
gpgme.i \
helpers.c helpers.h private.h \
examples \
+ doc \
src
SUBDIRS = . tests
@@ -730,7 +731,7 @@ prepare: copystamp
# distutils are not VPATH-aware.
copystamp:
ln -sf "$(top_srcdir)/src/data.h" .
- ln -sf "$(top_builddir)/config.h" .
+ ln -sf "$(top_builddir)/conf/config.h" .
ln -sf "$(srcdir)/src" gpg
touch $@
@@ -751,7 +752,8 @@ python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp
top_builddir="$(top_builddir)" \
$(PYTHON) setup.py sdist --verbose --dist-dir=python$(PYTHON_VERSION)-gpg-dist \
--manifest=python$(PYTHON_VERSION)-gpg-dist/MANIFEST
- gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
+ gpgbin=gpgconf --list-components | grep OpenPGP | sed -e 's/gpg:OpenPGP://g'
+ $(gpgbin) --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
.PHONY: sdist
sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
diff --git a/lang/python/README b/lang/python/README
index 99da4dd..a13345f 100644
--- a/lang/python/README
+++ b/lang/python/README
@@ -13,7 +13,7 @@ Table of Contents
The "gpg" module is a python interface to the GPGME library:
-[https://www.gnupg.org/software/gpgme/]
+
"gpg" offers two interfaces, one is a high-level, curated, and idiomatic
interface that is implemented as a shim on top of the low-level
@@ -27,16 +27,16 @@ functionality of the underlying library.
ââââââââââââââ
For general discussion and help see the gnupg-users mailing list:
- [https://lists.gnupg.org/mailman/listinfo/gnupg-users]
+
For development see the gnupg-devel mailing list:
- [https://lists.gnupg.org/mailman/listinfo/gnupg-devel]
+
2 Bugs
ââââââ
- Please report bugs using our bug tracker [https://bugs.gnupg.org] with
+ Please report bugs using our bug tracker with
tag (aka project) 'gpgme'.
@@ -44,8 +44,8 @@ functionality of the underlying library.
âââââââââ
PyME was created by John Goerzen, and maintained, developed, and
- cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, and everyone
- who contributed to it in any way.
+ cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, Justus
+ Winter, and everyone who contributed to it in any way.
In 2016 we merged a port of PyME to into the GPGME repository, and
development will continue there. Please see the VCS history for the
@@ -64,14 +64,14 @@ functionality of the underlying library.
⢠The bindings have been merged into the GPGME repository in 2016.
⢠The latest version of PyME for Python 3.2 and above (as of May,
- 2015) is v0.9.1. [https://git.gnupg.org/gpgme.git/lang/py3-pyme]
+ 2015) is v0.9.1.
⢠The latest version of PyME for Python 2.6 and 2.7 (as of this
- writing) is v0.9.0. [https://bitbucket.org/malb/pyme]
+ writing) is v0.9.0.
⢠A previous version of PyME v0.8.0 can be found on sourceforge:
- [http://pyme.sourceforge.net/]
+
⢠A previous version of PyME v0.5.1 which works with GPGME v0.3.15 can
- be found on John Goerzen's PyME page: [http://quux.org/devel/pyme/]
- [http://www.complete.org/JohnGoerzen]
+ be found on John Goerzen's PyME page:
+
diff --git a/lang/python/doc/README b/lang/python/doc/README
new file mode 100644
index 0000000..a14e1ad
--- /dev/null
+++ b/lang/python/doc/README
@@ -0,0 +1,47 @@
+GPGME Python Bindings Documentation
+===================================
+
+As the GPGME Python bindings exist in two worlds within the FOSS
+universe, it's always had a little issue with regards to its
+documentation and specifically to the format of it. The GnuPG
+Project, like much of the rest of the GNU Project, uses Texinfo to
+build its documentation. While the actual format used to write and
+edit that documentation is Org mode. Largely because most, if not
+all, of the GnuPG developers use GNU Emacs for much of their work.
+
+The Python world, however, utilises reStructuredText almost
+universally. This in turn is used by Sphinx or Docutils directly to
+build the documentation.
+
+Each has various advantages for their own ecisystems, but this part of
+the GnuPG effort is aimed at both sides. So, long story short, this
+documentation is provided as both Texinfo and reStructuredText files.
+
+This docs directory contains four main subdirectories:
+
+ 1. meta
+ 2. src
+ 3. rst
+ 4. texinfo
+
+The Meta directory is for docs that are not intended for distribution
+or are about the docs themselves. The sole exception being this RDME
+file.
+
+The Src directory is where the original edited files are, from which
+the following two formats are generated initially. Most, if not all,
+of these are written in Org Mode.
+
+The ReST directory contains reStructuredText files ehich have been
+converted to that format from the Org Mode files via Pandoc.
+
+The Texinfo directory contains Texinfo files which have been exported
+to that format from the Org Mode files by Org Mode itself within GNU
+Emacs.
+
+Those latter two directories should then be used by their respective
+build systems to produce the various output file formats they normally
+do. They should not spill out into this parent directory.
+Particularly since it is quite possible, perhaps even likely, that
+alternatives to both of them may be added to this parent documentation
+directory at some future point.
diff --git a/lang/python/doc/meta/TODO.org b/lang/python/doc/meta/TODO.org
new file mode 100644
index 0000000..0be99b3
--- /dev/null
+++ b/lang/python/doc/meta/TODO.org
@@ -0,0 +1,251 @@
+#+TITLE: Stuff To Do
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Latin Modern Roman}
+
+* Project Task List
+ :PROPERTIES:
+ :CUSTOM_ID: task-list
+ :END:
+
+** DONE Documentation default format
+ CLOSED: [2018-02-15 Thu 21:29]
+ :PROPERTIES:
+ :CUSTOM_ID: todo-docs-default
+ :END:
+
+ Decide on a default file format for documentation. The two main
+ contenders being Org Mode, the default for the GnuPG Project and
+ reStructuredText, the default for Python projects. A third option
+ of DITA XML was considered due to a number of beneficial features
+ it provides.
+
+ The decision was made to use Org Mode in order to fully integrate
+ with the rest of the GPGME and GnuPG documentation. It is possible
+ to produce reST versions via Pandoc and DITA XML can be reached
+ through converting to either Markdown or XHTML first.
+
+
+** TODO Documentation build systems
+ :PROPERTIES:
+ :CUSTOM_ID: todo-docs-build-systems
+ :END:
+
+Though Org Mode is being used for the default documentation format, it
+still needs to end up as usable by end users. So the Org Mode files
+are used to produce the "source" files used by the two main contenders
+for documenting the bindings: Texinfo and ReStructuredText/Docutils.
+
+
+*** TODO Texinfo documentation
+ :PROPERTIES:
+ :CUSTOM_ID: todo-docs-build-texinfo
+ :END:
+
+Need to add all of Texinfo's ... special systems to make it do its
+things.
+
+
+*** TODO ReStructuredText documentation
+ :PROPERTIES:
+ :CUSTOM_ID: todo-docs-build-docutils
+ :END:
+
+Need to run Sphinx's quick start, add it to the requirements and tweak
+the index page for the rst files to point to the HOWTO and other files.
+
+It might just be easier to do all that in Org Mode and convert the
+lot, then the Sphinx bits can be automated.
+
+
+** STARTED Documentation HOWTO
+ :PROPERTIES:
+ :CUSTOM_ID: todo-docs-howto
+ :END:
+
+ - State "STARTED" from "TODO" [2018-03-08 Thu 13:59] \\
+ Started yesterday.
+ Write a HOWTO style guide for the current Python bindings.
+
+*** DONE Start python bindings HOWTO
+ CLOSED: [2018-03-07 Wed 18:14]
+ :PROPERTIES:
+ :CUSTOM_ID: howto-start
+ :END:
+
+
+*** STARTED Include certain specific instructions in the HOWTO
+ :PROPERTIES:
+ :CUSTOM_ID: howto-requests
+ :END:
+
+ Note: moved the S/MIME bits out to their own section of the TODO
+ list and may be served better by separate HOWTO documentation
+ anyway.
+
+ - State "STARTED" from "TODO" [2018-03-09 Fri 15:27]
+ Some functions can be worked out from the handful of examples
+ available, but many more can't and I've already begun receiving
+ requests for certain functions to be explained.
+
+
+**** DONE Standard scenarios
+ CLOSED: [2018-03-19 Mon 12:34]
+ :PROPERTIES:
+ :CUSTOM_ID: howto-the-basics
+ :END:
+
+ - State "DONE" from "STARTED" [2018-03-19 Mon 12:34] \\
+ All four of those are done.
+ - State "STARTED" from "TODO" [2018-03-09 Fri 15:26] \\
+ Began with the example code, now to add the text.
+ What everyone expects: encryption, decryption, signing and verifying.
+
+
+**** STARTED Key control
+ :PROPERTIES:
+ :CUSTOM_ID: howto-key-control
+ :END:
+
+ - State "STARTED" from "TODO" [2018-03-19 Mon 12:35] \\
+ Generating keys and subkeys are done, but revocation is still to be done.
+ Generating keys, adding subkeys, revoking subkeys (and keeping
+ the cert key), adding and revoking UIDs, signing/certifying keys.
+
+
+**** DONE More key control
+ CLOSED: [2018-03-19 Mon 12:36]
+ :PROPERTIES:
+ :CUSTOM_ID: howto-key-selection
+ :END:
+
+ - State "DONE" from "TODO" [2018-03-19 Mon 12:36] \\
+ Key selection, searching, matching and counting is done.
+ Selecting keys to encrypt to or manipulate in other ways (e.g. as
+ with key control or the basics).
+
+
+** TODO Documentation SWIG
+ :PROPERTIES:
+ :CUSTOM_ID: todo-docs-swig
+ :END:
+
+ Write documentation for the complete SWIG bindings demonstrating
+ the correspondence with GPGME itself.
+
+ Note: it is likely that this will be more in the nature of
+ something to be used in conjunction with the existing GPGME
+ documentation which makes it easier for Python developers to use.
+
+
+** TODO GUI examples
+ :PROPERTIES:
+ :CUSTOM_ID: todo-gui-examples
+ :END:
+
+ Create some examples of using Python bindings in a GUI application
+ to either match or be similar to the old GTK2 examples available
+ with PyME.
+
+
+** TODO Replace SWIG
+ :PROPERTIES:
+ :CUSTOM_ID: todo-replace-swig
+ :END:
+
+ Selecting SWIG for this project in 2002 was understandable and
+ effectively the only viable option. The options available now,
+ however, are significantly improved and some of those would resolve
+ a number of existing problems with using SWIG, particularly when
+ running code on both POSIX compliant and Windows platforms.
+
+ The long term goal is to replace SWIG by reimplementing the Python
+ bindings using a more suitable means of interfacing with the GPGME
+ C source code.
+
+
+*** TODO Replacement for SWIG
+ :PROPERTIES:
+ :CUSTOM_ID: todo-replace-swig-replacement
+ :END:
+
+ Decide on a replacement for SWIG. Currently CFFI is looking like
+ the most viable candidate, but some additional testing and checks
+ are yet to be completed.
+
+
+** TODO API for an API
+ :PROPERTIES:
+ :CUSTOM_ID: todo-api-squared
+ :END:
+
+ A C API like GPGME is not what most modern developers think of when
+ they hear the term API. Normally they think of something they can
+ interact with like a RESTful web API. Though RESTful is unlikely
+ given the nature of GPGME and the process of encryption, it may be
+ possible to provide a more familiar interface which can be utilised
+ by developers of other languages for which bindings are not
+ available or for which it is too difficult to create proper
+ bindings.
+
+
+** TODO S/MIME
+ :PROPERTIES:
+ :CUSTOM_ID: s-mime
+ :END:
+
+ Eventually add some of this, but the OpenPGP details are far more
+ important at the moment.
+
+
+* Project Task Details
+ :PROPERTIES:
+ :CUSTOM_ID: detailed-tasks
+ :END:
+
+** Working examples
+ :PROPERTIES:
+ :CUSTOM_ID: working-examples
+ :END:
+
+ The old GUI examples were unable to be retained since they depended
+ on GTK2 and Python 2's integration with GTK2.
+
+ Current GPGME examples so far only include command line tools or
+ basic Python code for use with either Python 2.7 or Python 3.4 and
+ above.
+
+ Future GUI examples ought to utilise available GUI modules and
+ libraries supported by Python 3. This may include Qt frameworks,
+ Tkinter, GTK3 or something else entirely.
+
+** Documentation
+ :PROPERTIES:
+ :CUSTOM_ID: documentation
+ :END:
+
+ The legacy documentation which no longer applies to the Python
+ bindings has been removed.
+
+ Current and future documentation will adhere to the GnuPG standard
+ of using Org Mode and not use the reStructuredText (reST) format
+ more commonly associated with Python documentation. The reasons
+ for this are that this project is best served as shipping with the
+ rest of GPGME and the documentation ought to match that. There are
+ also aspects of Org Mode's publishing features which are superior
+ to the defaults of reST, including the capacity to generate fully
+ validating strict XHTML output.
+
+ If reST files are required at a later point for future inclusion
+ with other Python packages, then that format can be generated from
+ the .org files with Pandoc before being leveraged by either
+ Docutils, Sphinx or something else.
+
+ While there are some advanced typesetting features of reST which
+ are not directly available to Org Mode, more often than not those
+ features are best implemented with either HTML and CSS, with LaTeX
+ to produce a PDF or via a number of XML solutions. Both reST and
+ Org Mode have multiple paths by which to achieve all of these.
diff --git a/lang/python/doc/meta/old-commits.log b/lang/python/doc/meta/old-commits.log
new file mode 100644
index 0000000..93661e3
--- /dev/null
+++ b/lang/python/doc/meta/old-commits.log
@@ -0,0 +1,2445 @@
+commit 2145348ec54c6027f2ea20f695de0277e2871405
+Merge: 348ba88 2036f1a
+Author: Ben McGinnes
+Date: Wed May 6 03:04:19 2015 +1000
+
+ Merge pull request #4 from Hasimir/master
+
+ history
+
+commit 2036f1a0a670a0561993e195c458059220b36114
+Merge: dbabf0c 348ba88
+Author: Ben McGinnes
+Date: Wed May 6 02:57:44 2015 +1000
+
+ Merge branch 'master' of github:adversary-org/pyme3
+
+commit dbabf0cf1f2985755c2293b619011832e34faa9c
+Author: Ben McGinnes
+Date: Wed May 6 02:52:23 2015 +1000
+
+ Added a short history
+
+ * A (very) brief summary of the project's history since 2002.
+ * Deals with why the commit log in the GPGME repo does not include the
+ history of PyME.
+ * Mentions that intact git repos will be maintained, but not where they
+ are (one will be on github, another will be in a user directory on
+ playfair.gnupg.org).
+
+ docs/Short_History.rst | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 57 insertions(+)
+
+commit 348ba883424778c711c04ae9b66035ccdb36eb8c
+Merge: 127d0a5 7c37a27
+Author: Ben McGinnes
+Date: Wed May 6 02:21:34 2015 +1000
+
+ Merge pull request #3 from Hasimir/master
+
+ Version release preparation
+
+commit 7c37a27a6845c58222d4d947c2efbe38e955b612
+Merge: f692cff 127d0a5
+Author: Ben McGinnes
+Date: Wed May 6 02:17:14 2015 +1000
+
+ Merge branch 'master' of github:adversary-org/pyme3
+
+commit f692cff50a89c2c61acdbd3d7dd60f5ce3cd15af
+Author: Ben McGinnes
+Date: Wed May 6 02:09:44 2015 +1000
+
+ TODO update
+
+ * Removed reference to GitHub, replaced with impending new home at gnupg.org.
+
+ docs/TODO.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit bd5ccf9e3bfe69fa681613757577e87b72ca08ec
+Author: Ben McGinnes
+Date: Wed May 6 02:00:44 2015 +1000
+
+ Version bump
+
+ * Bumped version number to 0.9.1 to keep it somewhat in line with the
+ existing PyME project, even though there will be some divergence at
+ some point (or even re-merging, depending on how many of the Python 3
+ modifications can be back-ported to the Python 2 version).
+ * Updated the author and copyright information to reflect the two
+ current authors (Martin and I).
+ * Replaced Igor's contact details with mine.
+ * Replaced project home page with the GnuPG one.
+
+ pyme/version.py | 16 +++++++++-------
+ 1 file changed, 9 insertions(+), 7 deletions(-)
+
+commit ec167512f4ca88d8f6e89e2ae831798c8283b4df
+Author: Ben McGinnes
+Date: Wed May 6 01:48:01 2015 +1000
+
+ README preparation.
+
+ * Changes in preparation for impending move of code to the GnuPG git
+ server as a part of GPGME.
+
+ README.rst | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+commit 8a48515e884c36b5bdb24a13cb4d2e49f4ee6f17
+Author: Ben McGinnes
+Date: Wed May 6 01:43:53 2015 +1000
+
+ TODO moved to docs
+
+ * As it says.
+
+ TODO.rst | 25 -------------------------
+ docs/TODO.rst | 25 +++++++++++++++++++++++++
+ 2 files changed, 25 insertions(+), 25 deletions(-)
+
+commit f968c777472f01f308f6e57eac1740bf5c76c205
+Author: Ben McGinnes
+Date: Sun May 3 16:52:13 2015 +1000
+
+ Started another TODO file.
+
+ TODO.rst | 25 +++++++++++++++++++++++++
+ 1 file changed, 25 insertions(+)
+
+commit 127d0a56fa9f7ad1d4fb39d0b529b890a8d67365
+Merge: db72dea 44837f6
+Author: Ben McGinnes
+Date: Sun May 3 14:59:44 2015 +1000
+
+ Merge pull request #2 from Hasimir/master
+
+ Minor editing.
+
+commit 44837f6e50fc539c86aef1f75a6a3538b02029ea
+Author: Ben McGinnes
+Date: Sun May 3 14:56:55 2015 +1000
+
+ Minor editing.
+
+ * Fixed another URL.
+ * Changed Py3 version's version number to v0.9.1-beta0.
+
+ README.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit db72deaae19c3513391df040bcaf66a88d9213af
+Merge: db34286 48eb185
+Author: Ben McGinnes
+Date: Sun May 3 14:26:11 2015 +1000
+
+ Merge pull request #1 from Hasimir/master
+
+ Links
+
+commit 48eb1856cb0739cc9f0b9084da9d965e1fc7fddd
+Author: Ben McGinnes
+Date: Sun May 3 14:22:30 2015 +1000
+
+ Links
+
+ * Fixed URLs for authors.
+ * Updated my entry to point to github location.
+ ** I strongly suspect the result of this work will be concurrent
+ projects, so preparing for that eventuality with this repo.
+
+ README.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+commit db3428659783f30b9a76204403daedf9fc4cf7cf
+Author: Ben McGinnes
+Date: Sun May 3 11:29:00 2015 +1000
+
+ Explicit over Implicit ...
+
+ ... isn't just for code.
+
+ * Removed the 2to3 working directory and its contents.
+ * Made the README.rst file a little more clear that this branch is for
+ Python 3 (set Python 3.2 as a fairly arbitrary requirement for the
+ moment, but will probably raise this to 3.3).
+
+ 2to3/2to3-output-remaining.log | 60 ---
+ 2to3/2to3-output-setup.log | 35 --
+ 2to3/2to3-output.log | 950 -----------------------------------------
+ README.rst | 10 +-
+ 4 files changed, 7 insertions(+), 1048 deletions(-)
+
+commit 3edf07a4ba8a86af3a33246234d6e133074862af
+Author: Ben McGinnes
+Date: Sun May 3 11:19:41 2015 +1000
+
+ Added authors.
+
+ * In alphabetical order.
+ * Mine will need updating once Martin and I have decided what to do
+ regarding the two main branches.
+
+ README.rst | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+commit 811eb14b53e8856312d99f46b77215f7f9bd672c
+Author: Ben McGinnes
+Date: Sun May 3 10:23:00 2015 +1000
+
+ Docs and other things.
+
+ * Now able to import pyme.core without error, indicates port process is
+ successful.
+ * Code is *not* compatible with the Python 2 version.
+ * Will need to consider making this a parallel project with the master
+ branch.
+ * Got rid of the .org TODO file.
+ * Changed the README to use the reST file extension since it's full of
+ reST anyway.
+
+ 2to3/TODO.org | 5 -----
+ README.rst | 32 ++++++++++++++++++++++++++++++++
+ README.txt | 32 --------------------------------
+ 3 files changed, 32 insertions(+), 37 deletions(-)
+
+commit 79e784bdcce1de6f7856921b5431044c62c6f015
+Author: Ben McGinnes
+Date: Sun May 3 10:18:40 2015 +1000
+
+ Fixed another implicit import by making it explicit. Hopefully this is the last one.
+
+ pyme/util.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 2b52b46ccda3e7abcc50eed0745062259d698661
+Author: Ben McGinnes
+Date: Sun May 3 10:16:01 2015 +1000
+
+ Fixed another implicit import by making it explicit.
+
+ pyme/errors.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 409c8fd565e21f23cd41daaeffc867e6d23a0863
+Author: Ben McGinnes
+Date: Sun May 3 10:08:22 2015 +1000
+
+ Bytes vs. Unicode
+
+ * Trying PyBytes instead of PyUnicode.
+
+ gpgme.i | 14 +++++++-------
+ helpers.c | 8 ++++----
+ 2 files changed, 11 insertions(+), 11 deletions(-)
+
+commit d8164aa2ae98bf8c807c16e2d9be12c5fbea7cfd
+Author: Ben McGinnes
+Date: Sun May 3 09:22:58 2015 +1000
+
+ String to Unicode
+
+ * Replaced all instances of PyString with PyUnicode (and hoping there's
+ no byte data in there).
+
+ gpgme.i | 14 +++++++-------
+ helpers.c | 8 ++++----
+ 2 files changed, 11 insertions(+), 11 deletions(-)
+
+commit bd99b7865656e559b17c419c6b64b412a22c6c44
+Author: Ben McGinnes
+Date: Sun May 3 09:17:06 2015 +1000
+
+ PyInt_AsLong
+
+ * Replaced all instances of PyInt with PyLong, as per C API docs.
+
+ gpgme.i | 4 ++--
+ helpers.c | 8 ++++----
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+commit 3c91e2ccf8ca788b51e3308e292c6b64888fdb15
+Author: Ben McGinnes
+Date: Sun May 3 05:59:36 2015 +1000
+
+ Import correction
+
+ * Once pygpgme.py is generated and moved, it will be in the right
+ directory for the explicit "from . import pygpgme" to be correct.
+
+ pyme/core.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 23a49e7070812ff1ce138d8d4cc46d0b80328897
+Author: Ben McGinnes
+Date: Sun May 3 05:38:29 2015 +1000
+
+ The -py3 flag.
+
+ Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit b1549587d6db5e33081b9c20f75d1348a1d25938
+Author: Ben McGinnes
+Date: Sun May 3 05:01:42 2015 +1000
+
+ Fixed indentation - 4.
+
+ pyme/core.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit a685142ce46761ee6f5176e90717176e38e0d24f
+Author: Ben McGinnes
+Date: Sun May 3 05:00:16 2015 +1000
+
+ Fixed indentation - 3.
+
+ pyme/core.py | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+commit 488a70b490cc64eb1c47d2483cb2f4079c6767f7
+Author: Ben McGinnes
+Date: Sun May 3 04:53:21 2015 +1000
+
+ Pet Peeve
+
+ def pet_peeve(self):
+ peeve = print("people who don't press return after a colon!")
+
+ FFS!
+
+ pyme/core.py | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+commit a5d38eb47d64bb17bb609fe594dae2aca480bac9
+Author: Ben McGinnes
+Date: Sun May 3 04:47:54 2015 +1000
+
+ Fixed indentation - 2.
+
+ pyme/core.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 476a207f732b8559abb1ea3c23147c0e34804730
+Author: Ben McGinnes
+Date: Sun May 3 04:46:01 2015 +1000
+
+ Fixed indentation.
+
+ pyme/core.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit 0572900eba9bcd9b0283c7d8e022e8972f06f9f8
+Author: Ben McGinnes
+Date: Sun May 3 04:43:49 2015 +1000
+
+ Replaced all tabs with 4 spaces.
+
+ pyme/core.py | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+commit 78c0b7677e94ce1e11b8cdb833a9064527187330
+Author: Ben McGinnes
+Date: Sun May 3 04:39:07 2015 +1000
+
+ SWIG flags in the wrong place.
+
+ Makefile | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit dfa7f2589963494a8f89277560d8c1116604a3c8
+Author: Ben McGinnes
+Date: Sun May 3 04:35:09 2015 +1000
+
+ Fixed subprocess call for swig (again).
+
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 249bfd8c714dcda53127b99b6cc8a6c7c4a99f20
+Author: Ben McGinnes
+Date: Sun May 3 04:32:40 2015 +1000
+
+ Fixed subprocess call for swig.
+
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 6fd7e719cf4c975f466ceb39835db7007df36fb2
+Author: Ben McGinnes
+Date: Sun May 3 03:51:48 2015 +1000
+
+ Linking swig to py3
+
+ * Changed the swig invocations to run with the -python -py3 flags explicitly.
+
+ Makefile | 4 ++--
+ setup.py | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+commit 7a6b584f50ed6ddc8617a642185eea1f24ff791a
+Author: Ben McGinnes
+Date: Sat May 2 11:12:00 2015 +1000
+
+ String fun
+
+ * streamlined confdata details, including decoding strom binary to string.
+
+ setup.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+commit f7fd3f270592021a95a8f779bfe85ac18f4e390b
+Author: Ben McGinnes
+Date: Sat May 2 10:46:59 2015 +1000
+
+ Open File
+
+ * Removed deprecated file() and replaced with open().
+
+ examples/PyGtkGpgKeys.py | 2 +-
+ examples/pygpa.py | 6 +++---
+ gpgme-h-clean.py | 2 +-
+ 3 files changed, 5 insertions(+), 5 deletions(-)
+
+commit 4227d486f9558015e7e548d71085e58e1b50ec08
+Author: Ben McGinnes
+Date: Sat May 2 10:36:15 2015 +1000
+
+ print() fix
+
+ * Makefile includes a python print, changed from statement to function.
+
+ Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 406f7f2567b701502186fe0a325dc2a3491ff7f8
+Author: Ben McGinnes
+Date: Sat May 2 10:28:42 2015 +1000
+
+ Updated Makefile
+
+ * set make to use python3 instead.
+ * This will mean a successful port may need to be maintained seperately
+ from the original python2 code instead of merged, but ought to be able
+ to share most things. So maybe merge with separated make files or a
+ pre-make script to set python2 or python3 prior to building ... decide
+ later, after it works.
+
+ Makefile | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+commit 90b3efa5b193d37e08dc9b4ee766ba9ebc9412af
+Author: Ben McGinnes
+Date: Sat May 2 10:15:20 2015 +1000
+
+ Env and a little license issue
+
+ * Updated all the /usr/bin/env paths to point to python3.
+ * Also fixed the hard coded /usr/bin/python paths.
+ * Updated part of setup.py which gave the impression this package was
+ only licensed under the GPL (it's actually licensed under the LGPL as
+ well, essentially the same dual licensing as the GPGME library).
+
+ examples/PyGtkGpgKeys.py | 2 +-
+ examples/delkey.py | 2 +-
+ examples/encrypt-to-all.py | 2 +-
+ examples/exportimport.py | 2 +-
+ examples/genkey.py | 2 +-
+ examples/inter-edit.py | 2 +-
+ examples/pygpa.py | 2 +-
+ examples/sign.py | 2 +-
+ examples/signverify.py | 2 +-
+ examples/simple.py | 2 +-
+ examples/t-edit.py | 2 +-
+ examples/testCMSgetkey.py | 2 +-
+ examples/verifydetails.py | 2 +-
+ gpgme-h-clean.py | 2 +-
+ setup.py | 4 ++--
+ 15 files changed, 16 insertions(+), 16 deletions(-)
+
+commit 1a4b55dbccd2774344352e579130bf494bc5fa4b
+Author: Ben McGinnes
+Date: Sat May 2 08:50:54 2015 +1000
+
+ Removed extraneous files.
+
+ * The two .bak files.
+
+ pyme/errors.py.bak | 46 ---------------------
+ setup.py.bak | 116 -----------------------------------------------------
+ 2 files changed, 162 deletions(-)
+
+commit 208879d4f2a6d0514c3f8ee2fc0da8bba42350de
+Author: Ben McGinnes
+Date: Sat May 2 08:19:37 2015 +1000
+
+ Added TODO.org
+
+ * TODO list in Emacs org-mode.
+ * Will eventually be removed along with this entire directory when the
+ porting process is complete.
+
+ 2to3/TODO.org | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+commit 1548bf201059638675c5387c6f124d4b703363a9
+Author: Ben McGinnes
+Date: Sat May 2 07:58:40 2015 +1000
+
+ 2to3 conversion of remaining files
+
+ * Ran the extended version against all the unmodified python files.
+ * Only pyme/errors.py required additional work.
+
+ 2to3/2to3-output-remaining.log | 60 ++++++++++++++++++++++++++++++++++++++++++
+ pyme/errors.py | 2 +-
+ pyme/errors.py.bak | 46 ++++++++++++++++++++++++++++++++
+ 3 files changed, 107 insertions(+), 1 deletion(-)
+
+commit 1230650bc6bbe4c14d1284f7877aa932f3e86eb4
+Author: Ben McGinnes
+Date: Sat May 2 07:50:39 2015 +1000
+
+ 2to3 conversion of setup.py
+
+ * Ran extended 2to3 command to produce python 3 code for setup.py.
+ * Effectively testing for what to run against the other originally
+ unmodified py2 files.
+
+ 2to3/2to3-output-setup.log | 35 ++++++++++++++
+ setup.py | 7 ++-
+ setup.py.bak | 116 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 154 insertions(+), 4 deletions(-)
+
+commit edad44955f59aa879e95a369591717fb19eec6b7
+Author: Ben McGinnes
+Date: Fri May 1 21:50:07 2015 +1000
+
+ Removing 2to3 generated .bak files.
+
+ * Not really needed with a real VCS, but couldn't hurt to have them for
+ a couple of revisions. ;)
+
+ examples/PyGtkGpgKeys.py.bak | 663 ---------------
+ examples/encrypt-to-all.py.bak | 65 --
+ examples/exportimport.py.bak | 75 --
+ examples/genkey.py.bak | 45 -
+ examples/inter-edit.py.bak | 57 --
+ examples/pygpa.py.bak | 1457 --------------------------------
+ examples/sign.py.bak | 31 -
+ examples/signverify.py.bak | 78 --
+ examples/simple.py.bak | 52 --
+ examples/t-edit.py.bak | 59 --
+ examples/testCMSgetkey.py.bak | 45 -
+ examples/verifydetails.py.bak | 100 ---
+ gpgme-h-clean.py.bak | 42 -
+ pyme/callbacks.py.bak | 47 --
+ pyme/constants/data/__init__.py.bak | 4 -
+ pyme/constants/keylist/__init__.py.bak | 4 -
+ pyme/constants/sig/__init__.py.bak | 4 -
+ pyme/core.py.bak | 463 ----------
+ pyme/util.py.bak | 72 --
+ pyme/version.py.bak | 41 -
+ 20 files changed, 3404 deletions(-)
+
+commit 1cfc3c969f885ed191610bffbbd60ac23fdd349e
+Author: Ben McGinnes
+Date: Fri May 1 21:45:50 2015 +1000
+
+ 2to3 conversion log
+
+ * The output of the command to convert the code from Python 2 to 3.
+ * Note: this contains the list of files which were not modified and
+ which will or may need to be modified.
+
+ 2to3/2to3-output.log | 950 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 950 insertions(+)
+
+commit 078f6cf878aa62d12704fab424198a613a24cc8c
+Author: Ben McGinnes
+Date: Fri May 1 21:36:58 2015 +1000
+
+ 2to3 conversion of pyme master
+
+ * Branch from commit 459f3eca659b4949e394c4a032d9ce2053e6c721
+ * Ran this: or x in `find . | egrep .py$` ; do 2to3 -w $x; done ;
+ * Multiple files not modified, will record elsewhere (see next commit).
+
+ examples/PyGtkGpgKeys.py | 10 +-
+ examples/PyGtkGpgKeys.py.bak | 663 +++++++++++++++
+ examples/encrypt-to-all.py | 12 +-
+ examples/encrypt-to-all.py.bak | 65 ++
+ examples/exportimport.py | 20 +-
+ examples/exportimport.py.bak | 75 ++
+ examples/genkey.py | 2 +-
+ examples/genkey.py.bak | 45 +
+ examples/inter-edit.py | 8 +-
+ examples/inter-edit.py.bak | 57 ++
+ examples/pygpa.py | 40 +-
+ examples/pygpa.py.bak | 1457 ++++++++++++++++++++++++++++++++
+ examples/sign.py | 2 +-
+ examples/sign.py.bak | 31 +
+ examples/signverify.py | 18 +-
+ examples/signverify.py.bak | 78 ++
+ examples/simple.py | 8 +-
+ examples/simple.py.bak | 52 ++
+ examples/t-edit.py | 12 +-
+ examples/t-edit.py.bak | 59 ++
+ examples/testCMSgetkey.py | 8 +-
+ examples/testCMSgetkey.py.bak | 45 +
+ examples/verifydetails.py | 34 +-
+ examples/verifydetails.py.bak | 100 +++
+ gpgme-h-clean.py | 2 +-
+ gpgme-h-clean.py.bak | 42 +
+ pyme/callbacks.py | 6 +-
+ pyme/callbacks.py.bak | 47 ++
+ pyme/constants/data/__init__.py | 2 +-
+ pyme/constants/data/__init__.py.bak | 4 +
+ pyme/constants/keylist/__init__.py | 2 +-
+ pyme/constants/keylist/__init__.py.bak | 4 +
+ pyme/constants/sig/__init__.py | 2 +-
+ pyme/constants/sig/__init__.py.bak | 4 +
+ pyme/core.py | 26 +-
+ pyme/core.py.bak | 463 ++++++++++
+ pyme/util.py | 6 +-
+ pyme/util.py.bak | 72 ++
+ pyme/version.py | 2 +-
+ pyme/version.py.bak | 41 +
+ 40 files changed, 3515 insertions(+), 111 deletions(-)
+
+commit 459f3eca659b4949e394c4a032d9ce2053e6c721
+Merge: c5966ab dae7f14
+Author: Martin Albrecht
+Date: Wed Jul 9 10:48:33 2014 +0100
+
+ Merged in jerrykan/pyme/fix_setup_26 (pull request #1)
+
+ Provide support for using setup.py with Python v2.6
+
+commit dae7f14a54e6c2bde0ad4da7308cc7fc0d0c0469
+Author: John Kristensen
+Date: Wed Jul 9 15:54:39 2014 +1000
+
+ Provide support for using setup.py with Python v2.6
+
+ The setup.py script uses subprocess.check_output() which was introduced
+ in Python v2.7. The equivalent functionality can be achieved without
+ adding much extra code and provide support for Python v2.6.
+
+ setup.py | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+commit c5966abec9d772b3922d32650da288fd50a217be
+Author: Martin Albrecht
+Date: Thu May 15 19:43:00 2014 +0100
+
+ README.txt in ReST, including headlines
+
+ README.txt | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+commit 43ee8c6f34fa9b6d3975aa6ea60b3d4a741fa721
+Author: Martin Albrecht
+Date: Thu May 15 19:37:15 2014 +0100
+
+ README.txt in ReST
+
+ README.txt | 25 +++++++++++++------------
+ 1 file changed, 13 insertions(+), 12 deletions(-)
+
+commit f71a369484cba8801df23ccc5842335fa496c0df
+Author: Martin Albrecht
+Date: Thu May 15 19:28:12 2014 +0100
+
+ added MANIFEST.in and README.txt (instead of .md)
+
+ MANIFEST.in | 6 ++++++
+ README.md | 27 ---------------------------
+ README.txt | 27 +++++++++++++++++++++++++++
+ 3 files changed, 33 insertions(+), 27 deletions(-)
+
+commit d0d6755229f920b0bed043e9c2731de2d57c096c
+Author: Martin Albrecht
+Date: Tue May 13 09:52:44 2014 +0100
+
+ added mailing list to README
+
+ README.md | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+commit 30ca60ddf92df684de261cb24c83c68089be0adc
+Author: Martin Albrecht
+Date: Sun May 11 13:34:28 2014 +0100
+
+ we don't need a separate out of date ChangeLog file
+
+ ChangeLog | 802 --------------------------------------------------------------
+ 1 file changed, 802 deletions(-)
+
+commit 8263f1a6d38fdb7f5f3dd5c7e28f83caa7528a08
+Author: Martin Albrecht
+Date: Sun May 11 13:32:31 2014 +0100
+
+ adding README.md
+
+ README.md | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+commit 3fc71b47e9e14b0b984801c28d722723baa4b406
+Author: Martin Albrecht
+Date: Sat May 10 15:43:06 2014 +0100
+
+ ValueError -> RuntimeError
+
+ setup.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit eec432abea56296b9fa36aac0d10926a2335b739
+Merge: eea6537 d2738b3
+Author: Martin Albrecht
+Date: Sat May 10 15:41:02 2014 +0100
+
+ Merge branch 'master' of bitbucket.org:malb/pyme
+
+ Conflicts:
+ setup.py
+
+commit eea6537921061b4dcfc54e00a99d3fa110e71433
+Author: Martin Albrecht
+Date: Sat May 10 15:39:51 2014 +0100
+
+ check for swig
+
+ setup.py | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+commit 53867bf9715ee1b4ea873bf5e2fbb7d9740a2b4a
+Author: Martin Albrecht
+Date: Sat May 10 15:35:04 2014 +0100
+
+ more friendly error message if gpgme is missing
+
+ setup.py | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+commit d2738b35d63b1492d69641c5466103685f2d3a30
+Author: Martin Albrecht
+Date: Sat May 10 15:35:04 2014 +0100
+
+ more friendly error message if gpgme is missing
+
+ setup.py | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+commit c0b01240becf8ba6cf1d4c1f64b2cb4c056f5163
+Author: Martin Albrecht
+Date: Fri May 9 15:20:24 2014 +0100
+
+ version number should have three digits
+
+ pyme/version.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 6672bb60b9bec60d38e854016c48658b57774578
+Author: Martin Albrecht
+Date: Wed May 7 15:11:08 2014 +0100
+
+ bump version number for upcoming release
+
+ pyme/version.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 7bd6de700f33ca5d1f27bc16ebbd401f21d2e788
+Author: Martin Albrecht
+Date: Sat May 3 19:36:25 2014 +0100
+
+ bump version number to indicate changes
+
+ pyme/version.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 4fb6bd9b3f47c1a343242ac83b326cacd12a136e
+Author: Martin Albrecht
+Date: Sat May 3 19:34:07 2014 +0100
+
+ pyme instead of pygpgme
+
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 9548973138d78241a45ccb82333b25f2cf36ce7d
+Author: Martin Albrecht
+Date: Sat May 3 19:31:10 2014 +0100
+
+ dirty hack to make 'python setup.py install' work
+
+ setup.py | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+commit a961d7eab9db478b7e603324bc5d243bd3c84bad
+Author: Martin Albrecht
+Date: Sat May 3 19:05:44 2014 +0100
+
+ moved everything down to the toplevel directory
+
+ COPYING | 340 ++
+ COPYING.LESSER | 510 +++
+ ChangeLog | 802 +++++
+ INSTALL | 15 +
+ Makefile | 104 +
+ debian/README.Debian | 6 +
+ debian/changelog | 93 +
+ debian/control | 34 +
+ debian/copyright | 25 +
+ debian/docs | 2 +
+ debian/examples | 2 +
+ debian/rules | 99 +
+ examples/PyGtkGpgKeys.glade | 1394 ++++++++
+ examples/PyGtkGpgKeys.gladep | 8 +
+ examples/PyGtkGpgKeys.py | 663 ++++
+ examples/delkey.py | 34 +
+ examples/encrypt-to-all.py | 65 +
+ examples/exportimport.py | 75 +
+ examples/genkey.py | 45 +
+ examples/inter-edit.py | 57 +
+ examples/pygpa.glade | 5546 +++++++++++++++++++++++++++++++
+ examples/pygpa.py | 1457 ++++++++
+ examples/sign.py | 31 +
+ examples/signverify.py | 78 +
+ examples/simple.py | 52 +
+ examples/t-edit.py | 59 +
+ examples/testCMSgetkey.py | 45 +
+ examples/verifydetails.py | 100 +
+ gpgme-h-clean.py | 42 +
+ gpgme.i | 267 ++
+ helpers.c | 154 +
+ helpers.h | 36 +
+ pyme/COPYING | 340 --
+ pyme/COPYING.LESSER | 510 ---
+ pyme/ChangeLog | 802 -----
+ pyme/INSTALL | 15 -
+ pyme/Makefile | 104 -
+ pyme/__init__.py | 137 +
+ pyme/callbacks.py | 47 +
+ pyme/constants/__init__.py | 7 +
+ pyme/constants/data/__init__.py | 4 +
+ pyme/constants/data/encoding.py | 20 +
+ pyme/constants/event.py | 20 +
+ pyme/constants/import.py | 20 +
+ pyme/constants/keylist/__init__.py | 4 +
+ pyme/constants/keylist/mode.py | 20 +
+ pyme/constants/md.py | 20 +
+ pyme/constants/pk.py | 20 +
+ pyme/constants/protocol.py | 20 +
+ pyme/constants/sig/__init__.py | 4 +
+ pyme/constants/sig/mode.py | 20 +
+ pyme/constants/sigsum.py | 20 +
+ pyme/constants/status.py | 20 +
+ pyme/constants/validity.py | 20 +
+ pyme/core.py | 463 +++
+ pyme/debian/README.Debian | 6 -
+ pyme/debian/changelog | 93 -
+ pyme/debian/control | 34 -
+ pyme/debian/copyright | 25 -
+ pyme/debian/docs | 2 -
+ pyme/debian/examples | 2 -
+ pyme/debian/rules | 99 -
+ pyme/errors.py | 46 +
+ pyme/examples/PyGtkGpgKeys.glade | 1394 --------
+ pyme/examples/PyGtkGpgKeys.gladep | 8 -
+ pyme/examples/PyGtkGpgKeys.py | 663 ----
+ pyme/examples/delkey.py | 34 -
+ pyme/examples/encrypt-to-all.py | 65 -
+ pyme/examples/exportimport.py | 75 -
+ pyme/examples/genkey.py | 45 -
+ pyme/examples/inter-edit.py | 57 -
+ pyme/examples/pygpa.glade | 5546 -------------------------------
+ pyme/examples/pygpa.py | 1457 --------
+ pyme/examples/sign.py | 31 -
+ pyme/examples/signverify.py | 78 -
+ pyme/examples/simple.py | 52 -
+ pyme/examples/t-edit.py | 59 -
+ pyme/examples/testCMSgetkey.py | 45 -
+ pyme/examples/verifydetails.py | 100 -
+ pyme/gpgme-h-clean.py | 42 -
+ pyme/gpgme.i | 267 --
+ pyme/helpers.c | 154 -
+ pyme/helpers.h | 36 -
+ pyme/pyme/__init__.py | 137 -
+ pyme/pyme/callbacks.py | 47 -
+ pyme/pyme/constants/__init__.py | 7 -
+ pyme/pyme/constants/data/__init__.py | 4 -
+ pyme/pyme/constants/data/encoding.py | 20 -
+ pyme/pyme/constants/event.py | 20 -
+ pyme/pyme/constants/import.py | 20 -
+ pyme/pyme/constants/keylist/__init__.py | 4 -
+ pyme/pyme/constants/keylist/mode.py | 20 -
+ pyme/pyme/constants/md.py | 20 -
+ pyme/pyme/constants/pk.py | 20 -
+ pyme/pyme/constants/protocol.py | 20 -
+ pyme/pyme/constants/sig/__init__.py | 4 -
+ pyme/pyme/constants/sig/mode.py | 20 -
+ pyme/pyme/constants/sigsum.py | 20 -
+ pyme/pyme/constants/status.py | 20 -
+ pyme/pyme/constants/validity.py | 20 -
+ pyme/pyme/core.py | 463 ---
+ pyme/pyme/errors.py | 46 -
+ pyme/pyme/util.py | 72 -
+ pyme/pyme/version.py | 41 -
+ pyme/setup.py | 99 -
+ pyme/util.py | 72 +
+ pyme/version.py | 41 +
+ setup.py | 99 +
+ 108 files changed, 13384 insertions(+), 13384 deletions(-)
+
+commit 8148cdd424c434e833ce427612ea8c89abc6e41c
+Author: Martin Albrecht
+Date: Sat May 3 18:58:52 2014 +0100
+
+ removing pyme-web
+
+ pyme-web/Makefile | 15 -
+ pyme-web/default.css | 37 --
+ pyme-web/doc/gpgme/ASCII-Armor.html | 57 ---
+ pyme-web/doc/gpgme/Advanced-Key-Editing.html | 98 ----
+ pyme-web/doc/gpgme/Algorithms.html | 47 --
+ pyme-web/doc/gpgme/Building-the-Source.html | 82 ----
+ .../doc/gpgme/Callback-Based-Data-Buffers.html | 148 ------
+ pyme-web/doc/gpgme/Cancellation.html | 67 ---
+ pyme-web/doc/gpgme/Concept-Index.html | 186 -------
+ pyme-web/doc/gpgme/Context-Attributes.html | 52 --
+ pyme-web/doc/gpgme/Contexts.html | 61 ---
+ pyme-web/doc/gpgme/Creating-Contexts.html | 49 --
+ pyme-web/doc/gpgme/Creating-Data-Buffers.html | 47 --
+ pyme-web/doc/gpgme/Creating-a-Signature.html | 143 ------
+ pyme-web/doc/gpgme/Crypto-Engine.html | 79 ---
+ pyme-web/doc/gpgme/Crypto-Operations.html | 67 ---
+ .../doc/gpgme/Cryptographic-Message-Syntax.html | 42 --
+ .../doc/gpgme/Data-Buffer-I_002fO-Operations.html | 104 ----
+ pyme-web/doc/gpgme/Data-Buffer-Meta_002dData.html | 100 ----
+ pyme-web/doc/gpgme/Decrypt-and-Verify.html | 79 ---
+ pyme-web/doc/gpgme/Decrypt.html | 123 -----
+ pyme-web/doc/gpgme/Deleting-Keys.html | 67 ---
+ pyme-web/doc/gpgme/Destroying-Contexts.html | 46 --
+ pyme-web/doc/gpgme/Destroying-Data-Buffers.html | 70 ---
+ pyme-web/doc/gpgme/Encrypt.html | 45 --
+ pyme-web/doc/gpgme/Encrypting-a-Plaintext.html | 147 ------
+ pyme-web/doc/gpgme/Engine-Configuration.html | 65 ---
+ pyme-web/doc/gpgme/Engine-Information.html | 119 -----
+ pyme-web/doc/gpgme/Engine-Version-Check.html | 48 --
+ pyme-web/doc/gpgme/Error-Codes.html | 133 -----
+ pyme-web/doc/gpgme/Error-Handling.html | 72 ---
+ pyme-web/doc/gpgme/Error-Sources.html | 89 ----
+ pyme-web/doc/gpgme/Error-Strings.html | 80 ---
+ pyme-web/doc/gpgme/Error-Values.html | 159 ------
+ pyme-web/doc/gpgme/Exchanging-Data.html | 58 ---
+ pyme-web/doc/gpgme/Exporting-Keys.html | 101 ----
+ pyme-web/doc/gpgme/Features.html | 59 ---
+ pyme-web/doc/gpgme/File-Based-Data-Buffers.html | 74 ---
+ pyme-web/doc/gpgme/Function-and-Data-Index.html | 229 ---------
+ pyme-web/doc/gpgme/Generating-Keys.html | 144 ------
+ pyme-web/doc/gpgme/Getting-Started.html | 55 ---
+ pyme-web/doc/gpgme/Hash-Algorithms.html | 59 ---
+ pyme-web/doc/gpgme/Header.html | 53 --
+ .../doc/gpgme/I_002fO-Callback-Example-GDK.html | 85 ----
+ .../gpgme/I_002fO-Callback-Example-GTK_002b.html | 86 ----
+ .../doc/gpgme/I_002fO-Callback-Example-Qt.html | 99 ----
+ pyme-web/doc/gpgme/I_002fO-Callback-Example.html | 259 ----------
+ pyme-web/doc/gpgme/I_002fO-Callback-Interface.html | 142 ------
+ pyme-web/doc/gpgme/Importing-Keys.html | 171 -------
+ pyme-web/doc/gpgme/Included-Certificates.html | 70 ---
+ pyme-web/doc/gpgme/Information-About-Keys.html | 207 --------
+ .../doc/gpgme/Information-About-Trust-Items.html | 75 ---
+ pyme-web/doc/gpgme/Introduction.html | 53 --
+ pyme-web/doc/gpgme/Key-Listing-Mode.html | 99 ----
+ pyme-web/doc/gpgme/Key-Management.html | 260 ----------
+ pyme-web/doc/gpgme/Key-Signatures.html | 130 -----
+ .../doc/gpgme/Largefile-Support-_0028LFS_0029.html | 110 -----
+ pyme-web/doc/gpgme/Library-Copying.html | 542 ---------------------
+ pyme-web/doc/gpgme/Library-Version-Check.html | 97 ----
+ pyme-web/doc/gpgme/Listing-Keys.html | 204 --------
+ pyme-web/doc/gpgme/Listing-Trust-Items.html | 88 ----
+ pyme-web/doc/gpgme/Locale.html | 69 ---
+ pyme-web/doc/gpgme/Manipulating-Data-Buffers.html | 45 --
+ pyme-web/doc/gpgme/Manipulating-Keys.html | 63 ---
+ pyme-web/doc/gpgme/Manipulating-Trust-Items.html | 62 ---
+ pyme-web/doc/gpgme/Memory-Based-Data-Buffers.html | 107 ----
+ pyme-web/doc/gpgme/Multi-Threading.html | 93 ----
+ pyme-web/doc/gpgme/OpenPGP.html | 44 --
+ pyme-web/doc/gpgme/Overview.html | 57 ---
+ pyme-web/doc/gpgme/Passphrase-Callback.html | 101 ----
+ pyme-web/doc/gpgme/Preparation.html | 54 --
+ pyme-web/doc/gpgme/Progress-Meter-Callback.html | 80 ---
+ pyme-web/doc/gpgme/Protocol-Selection.html | 60 ---
+ pyme-web/doc/gpgme/Protocols-and-Engines.html | 82 ----
+ pyme-web/doc/gpgme/Public-Key-Algorithms.html | 74 ---
+ .../doc/gpgme/Registering-I_002fO-Callbacks.html | 81 ---
+ pyme-web/doc/gpgme/Run-Control.html | 53 --
+ pyme-web/doc/gpgme/Selecting-Signers.html | 64 ---
+ pyme-web/doc/gpgme/Sign.html | 50 --
+ pyme-web/doc/gpgme/Signal-Handling.html | 61 ---
+ pyme-web/doc/gpgme/Signature-Notation-Data.html | 85 ----
+ pyme-web/doc/gpgme/Text-Mode.html | 63 ---
+ pyme-web/doc/gpgme/Trust-Item-Management.html | 68 ---
+ pyme-web/doc/gpgme/Using-Automake.html | 74 ---
+ pyme-web/doc/gpgme/Using-External-Event-Loops.html | 74 ---
+ pyme-web/doc/gpgme/Using-Libtool.html | 44 --
+ pyme-web/doc/gpgme/Verify.html | 492 -------------------
+ pyme-web/doc/gpgme/Waiting-For-Completion.html | 77 ---
+ pyme-web/doc/gpgme/index.html | 169 -------
+ pyme-web/doc/pyme/index.html | 164 -------
+ pyme-web/doc/pyme/pyme.callbacks.html | 42 --
+ .../doc/pyme/pyme.constants.data.encoding.html | 48 --
+ pyme-web/doc/pyme/pyme.constants.data.html | 29 --
+ pyme-web/doc/pyme/pyme.constants.event.html | 48 --
+ pyme-web/doc/pyme/pyme.constants.html | 39 --
+ pyme-web/doc/pyme/pyme.constants.import.html | 49 --
+ pyme-web/doc/pyme/pyme.constants.keylist.html | 29 --
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 49 --
+ pyme-web/doc/pyme/pyme.constants.md.html | 58 ---
+ pyme-web/doc/pyme/pyme.constants.pk.html | 50 --
+ pyme-web/doc/pyme/pyme.constants.protocol.html | 48 --
+ pyme-web/doc/pyme/pyme.constants.sig.html | 29 --
+ pyme-web/doc/pyme/pyme.constants.sig.mode.html | 47 --
+ pyme-web/doc/pyme/pyme.constants.sigsum.html | 55 ---
+ pyme-web/doc/pyme/pyme.constants.status.html | 126 -----
+ pyme-web/doc/pyme/pyme.constants.validity.html | 50 --
+ pyme-web/doc/pyme/pyme.core.html | 277 -----------
+ pyme-web/doc/pyme/pyme.errors.html | 82 ----
+ pyme-web/doc/pyme/pyme.html | 164 -------
+ pyme-web/doc/pyme/pyme.util.html | 81 ---
+ pyme-web/doc/pyme/pyme.version.html | 37 --
+ pyme-web/index.html | 72 ---
+ 112 files changed, 10551 deletions(-)
+
+commit 684d95feb7e10e538a56fb1b27f1456111bacb60
+Author: Martin Albrecht
+Date: Mon Jan 6 17:44:20 2014 +0100
+
+ fixing op_export_keys()
+
+ the conversion of gpgme_key_t [] was restricted to gpgme_key_t [] with the
+ name recv, i.e. only the use-cases of encryption were covered.
+
+ see: http://sourceforge.net/mailarchive/forum.php?forum_name=pyme-help&max_rows=25&style=nested&viewmonth=201309
+
+ pyme/gpgme.i | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+commit 658d23b95110d21eeb50abf4e74701a667521a88
+Author: Martin Albrecht
+Date: Mon Jan 6 17:41:33 2014 +0100
+
+ deleting CVSROOT
+
+ CVSROOT/checkoutlist | 13 -------------
+ CVSROOT/commitinfo | 15 ---------------
+ CVSROOT/config | 21 ---------------------
+ CVSROOT/cvswrappers | 19 -------------------
+ CVSROOT/editinfo | 21 ---------------------
+ CVSROOT/loginfo | 26 --------------------------
+ CVSROOT/modules | 26 --------------------------
+ CVSROOT/notify | 12 ------------
+ CVSROOT/rcsinfo | 13 -------------
+ CVSROOT/taginfo | 20 --------------------
+ CVSROOT/verifymsg | 21 ---------------------
+ 11 files changed, 207 deletions(-)
+
+commit 576b555499c094c4786d42de9e59aa9826009b89
+Author: convert-repo
+Date: Mon Jan 6 15:22:44 2014 +0000
+
+ update tags
+
+commit 2dcf0c5b702eb5a18c66ff1e42a72eaa7427af1d
+Author: belyi
+Date: Wed Nov 26 02:38:33 2008 +0000
+
+ Move Windows specific fix from helpers.c to helpers.h so that it works
+ for edit callback as well as for the passphrase one.
+
+ pyme/helpers.c | 5 -----
+ pyme/helpers.h | 5 +++++
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+commit 42a035f2ef62470fea7a7f8ee33a1297fa90a603
+Author: belyi
+Date: Mon Nov 24 21:44:30 2008 +0000
+
+ Update the way build directives are constructed on MinGW to have a bit
+ more robust. Update PyMe build version to 0.8.1 in version.py
+
+ pyme/pyme/version.py | 2 +-
+ pyme/setup.py | 10 ++++++++--
+ 2 files changed, 9 insertions(+), 3 deletions(-)
+
+commit 3aaa20fbcba17066c9ffd580f5209946022793a2
+Author: belyi
+Date: Mon Nov 24 06:57:11 2008 +0000
+
+ Update changelog
+
+ pyme/debian/changelog | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+commit 689ff46b2550547e3883f809a6dc40c22c3e137e
+Author: belyi
+Date: Mon Nov 24 06:50:41 2008 +0000
+
+ Fix hang problem on Windows when password is written to a filehandle.
+ Fix the way path is constructed on MinGW platform.
+
+ pyme/helpers.c | 5 +++++
+ pyme/setup.py | 4 ++--
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+commit 852a60d541d66cb56f40378182b976fd87a02c46
+Author: belyi
+Date: Sun Nov 23 04:31:31 2008 +0000
+
+ Add Bernard's example testCMSgetkey.py and his updates for
+ verifydetails.py
+
+ pyme/examples/testCMSgetkey.py | 45 ++++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/verifydetails.py | 43 +++++++++++++++++++++++++++++-----------
+ 2 files changed, 77 insertions(+), 11 deletions(-)
+
+commit f080527d9184f3360f0a8ef6136b9a188d8e7d2a
+Author: belyi
+Date: Thu May 29 18:29:37 2008 +0000
+
+ Remove debian packaging for python2.3 since it is removed from both
+ testing and unstable dists.
+ Update docs build target to have correct PYTHONPATH set.
+
+ pyme/Makefile | 2 +-
+ pyme/debian/changelog | 4 +++-
+ pyme/debian/control | 4 ++--
+ pyme/debian/rules | 2 --
+ 4 files changed, 6 insertions(+), 6 deletions(-)
+
+commit c25d133fcbadf3c7f6e655586b4a05d6e3cf6f0b
+Author: belyi
+Date: Thu Apr 3 13:37:12 2008 +0000
+
+ Forgot to adjust mainText margin. Doing it now.
+
+ pyme-web/default.css | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 897286a54a32336d060cd03305cdecb7905f34f1
+Author: belyi
+Date: Thu Apr 3 13:00:11 2008 +0000
+
+ Fix an error in default.css and make index.html "Standards Compliant".
+
+ pyme-web/default.css | 2 +-
+ pyme-web/index.html | 7 ++++---
+ 2 files changed, 5 insertions(+), 4 deletions(-)
+
+commit 4e049212bd214449cc0ba1ce06e00782783f328a
+Author: belyi
+Date: Thu Apr 3 12:38:42 2008 +0000
+
+ Adjust spacing between links.
+
+ pyme-web/default.css | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+commit cb2bddfbd77483b1deb14f2eab0715a03dd33fcd
+Author: belyi
+Date: Wed Apr 2 22:50:21 2008 +0000
+
+ Make style a big more IE friendly.
+
+ pyme-web/default.css | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+commit ad66f0a1bb01b46baac328e9fee439b35a60c232
+Author: belyi
+Date: Wed Apr 2 11:58:32 2008 +0000
+
+ Make GPGME documentation a bit more web friendly on the index.html page.
+
+ pyme-web/doc/gpgme/Algorithms.html | 2 +-
+ pyme-web/doc/gpgme/Concept-Index.html | 2 +-
+ pyme-web/doc/gpgme/Contexts.html | 2 +-
+ pyme-web/doc/gpgme/Error-Handling.html | 2 +-
+ pyme-web/doc/gpgme/Exchanging-Data.html | 2 +-
+ pyme-web/doc/gpgme/Function-and-Data-Index.html | 2 +-
+ pyme-web/doc/gpgme/Introduction.html | 4 +-
+ pyme-web/doc/gpgme/Library-Copying.html | 2 +-
+ pyme-web/doc/gpgme/Preparation.html | 2 +-
+ pyme-web/doc/gpgme/Protocols-and-Engines.html | 2 +-
+ pyme-web/doc/gpgme/index.html | 229 +-----------------------
+ 11 files changed, 12 insertions(+), 239 deletions(-)
+
+commit 4f57c0ccb049d4442e7732e2d1d05dabffd2a21d
+Author: belyi
+Date: Wed Apr 2 06:12:57 2008 +0000
+
+ Add missing core.set_locale() to set default locale for contexts.
+
+ pyme/debian/changelog | 2 +-
+ pyme/pyme/core.py | 4 ++++
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+commit acf7ead3dea8590cf9fe86b67bb125837ad6ed4f
+Author: belyi
+Date: Wed Apr 2 05:50:24 2008 +0000
+
+ Avoid leaks caused by keys.
+ Add set/get methods for engine info.
+
+ pyme/debian/changelog | 10 ++++++++++
+ pyme/pyme/core.py | 24 ++++++++++++++++++++++++
+ 2 files changed, 34 insertions(+)
+
+commit df4a2fb518adbb6420d95ce74af212c87abff7e7
+Author: belyi
+Date: Wed Apr 2 04:04:41 2008 +0000
+
+ Update index.html to reflect new versions on the web.
+
+ pyme-web/Makefile | 3 ++-
+ pyme-web/doc/gpgme/index.html | 4 +---
+ pyme-web/index.html | 4 ++--
+ 3 files changed, 5 insertions(+), 6 deletions(-)
+
+commit bd3ffc9bdf98d6aafde6b689c6c8215fa468612d
+Author: belyi
+Date: Wed Apr 2 04:01:04 2008 +0000
+
+ Update PyMe documentation to match 0.8.0 version of the package.
+
+ pyme-web/doc/pyme/index.html | 14 ++++-----
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 1 +
+ pyme-web/doc/pyme/pyme.constants.protocol.html | 4 ++-
+ pyme-web/doc/pyme/pyme.constants.status.html | 9 ++++++
+ pyme-web/doc/pyme/pyme.core.html | 36 ++++++++++++++++++----
+ pyme-web/doc/pyme/pyme.errors.html | 8 ++---
+ pyme-web/doc/pyme/pyme.html | 14 ++++-----
+ pyme-web/doc/pyme/pyme.util.html | 17 ++++++++--
+ pyme-web/doc/pyme/pyme.version.html | 14 ++++-----
+ 9 files changed, 82 insertions(+), 35 deletions(-)
+
+commit 6973a69a317608a0d0661590d701f4e3f3a21b32
+Author: belyi
+Date: Wed Apr 2 02:35:24 2008 +0000
+
+ Have a fix for Contents being put onto 'Function and Data Index' page.
+
+ pyme-web/doc/gpgme/Concept-Index.html | 2 +-
+ pyme-web/doc/gpgme/Function-and-Data-Index.html | 153 +----------------------
+ pyme-web/doc/gpgme/index.html | 154 +++++++++++++++++++++++-
+ 3 files changed, 155 insertions(+), 154 deletions(-)
+
+commit 086315964cbc2abad1187f306dcb9c72ac3257f3
+Author: belyi
+Date: Wed Apr 2 01:00:29 2008 +0000
+
+ Update GPGME documentation. It's for v1.1.6 now.
+
+ pyme-web/doc/gpgme/ASCII-Armor.html | 57 ++
+ pyme-web/doc/gpgme/Advanced-Key-Editing.html | 98 +++
+ pyme-web/doc/gpgme/Algorithms.html | 47 ++
+ pyme-web/doc/gpgme/Building-the-Source.html | 82 +++
+ .../doc/gpgme/Callback-Based-Data-Buffers.html | 148 +++++
+ pyme-web/doc/gpgme/Cancellation.html | 67 ++
+ pyme-web/doc/gpgme/Concept-Index.html | 186 ++++++
+ pyme-web/doc/gpgme/Context-Attributes.html | 52 ++
+ pyme-web/doc/gpgme/Contexts.html | 61 ++
+ pyme-web/doc/gpgme/Creating-Contexts.html | 49 ++
+ pyme-web/doc/gpgme/Creating-Data-Buffers.html | 47 ++
+ pyme-web/doc/gpgme/Creating-a-Signature.html | 143 +++++
+ pyme-web/doc/gpgme/Crypto-Engine.html | 79 +++
+ pyme-web/doc/gpgme/Crypto-Operations.html | 67 ++
+ .../doc/gpgme/Cryptographic-Message-Syntax.html | 42 ++
+ .../doc/gpgme/Data-Buffer-I_002fO-Operations.html | 104 ++++
+ pyme-web/doc/gpgme/Data-Buffer-Meta_002dData.html | 100 +++
+ pyme-web/doc/gpgme/Decrypt-and-Verify.html | 79 +++
+ pyme-web/doc/gpgme/Decrypt.html | 123 ++++
+ pyme-web/doc/gpgme/Deleting-Keys.html | 67 ++
+ pyme-web/doc/gpgme/Destroying-Contexts.html | 46 ++
+ pyme-web/doc/gpgme/Destroying-Data-Buffers.html | 70 +++
+ pyme-web/doc/gpgme/Encrypt.html | 45 ++
+ pyme-web/doc/gpgme/Encrypting-a-Plaintext.html | 147 +++++
+ pyme-web/doc/gpgme/Engine-Configuration.html | 65 ++
+ pyme-web/doc/gpgme/Engine-Information.html | 119 ++++
+ pyme-web/doc/gpgme/Engine-Version-Check.html | 48 ++
+ pyme-web/doc/gpgme/Error-Codes.html | 133 ++++
+ pyme-web/doc/gpgme/Error-Handling.html | 72 +++
+ pyme-web/doc/gpgme/Error-Sources.html | 89 +++
+ pyme-web/doc/gpgme/Error-Strings.html | 80 +++
+ pyme-web/doc/gpgme/Error-Values.html | 159 +++++
+ pyme-web/doc/gpgme/Exchanging-Data.html | 58 ++
+ pyme-web/doc/gpgme/Exporting-Keys.html | 101 +++
+ pyme-web/doc/gpgme/Features.html | 59 ++
+ pyme-web/doc/gpgme/File-Based-Data-Buffers.html | 74 +++
+ pyme-web/doc/gpgme/Function-and-Data-Index.html | 380 ++++++++++++
+ pyme-web/doc/gpgme/Generating-Keys.html | 144 +++++
+ pyme-web/doc/gpgme/Getting-Started.html | 55 ++
+ pyme-web/doc/gpgme/Hash-Algorithms.html | 59 ++
+ pyme-web/doc/gpgme/Header.html | 53 ++
+ .../doc/gpgme/I_002fO-Callback-Example-GDK.html | 85 +++
+ .../gpgme/I_002fO-Callback-Example-GTK_002b.html | 86 +++
+ .../doc/gpgme/I_002fO-Callback-Example-Qt.html | 99 +++
+ pyme-web/doc/gpgme/I_002fO-Callback-Example.html | 259 ++++++++
+ pyme-web/doc/gpgme/I_002fO-Callback-Interface.html | 142 +++++
+ pyme-web/doc/gpgme/Importing-Keys.html | 171 +++++
+ pyme-web/doc/gpgme/Included-Certificates.html | 70 +++
+ pyme-web/doc/gpgme/Information-About-Keys.html | 207 +++++++
+ .../doc/gpgme/Information-About-Trust-Items.html | 75 +++
+ pyme-web/doc/gpgme/Introduction.html | 53 ++
+ pyme-web/doc/gpgme/Key-Listing-Mode.html | 99 +++
+ pyme-web/doc/gpgme/Key-Management.html | 260 ++++++++
+ pyme-web/doc/gpgme/Key-Signatures.html | 130 ++++
+ .../doc/gpgme/Largefile-Support-_0028LFS_0029.html | 110 ++++
+ pyme-web/doc/gpgme/Library-Copying.html | 542 ++++++++++++++++
+ pyme-web/doc/gpgme/Library-Version-Check.html | 97 +++
+ pyme-web/doc/gpgme/Listing-Keys.html | 204 ++++++
+ pyme-web/doc/gpgme/Listing-Trust-Items.html | 88 +++
+ pyme-web/doc/gpgme/Locale.html | 69 +++
+ pyme-web/doc/gpgme/Manipulating-Data-Buffers.html | 45 ++
+ pyme-web/doc/gpgme/Manipulating-Keys.html | 63 ++
+ pyme-web/doc/gpgme/Manipulating-Trust-Items.html | 62 ++
+ pyme-web/doc/gpgme/Memory-Based-Data-Buffers.html | 107 ++++
+ pyme-web/doc/gpgme/Multi-Threading.html | 93 +++
+ pyme-web/doc/gpgme/OpenPGP.html | 44 ++
+ pyme-web/doc/gpgme/Overview.html | 57 ++
+ pyme-web/doc/gpgme/Passphrase-Callback.html | 101 +++
+ pyme-web/doc/gpgme/Preparation.html | 54 ++
+ pyme-web/doc/gpgme/Progress-Meter-Callback.html | 80 +++
+ pyme-web/doc/gpgme/Protocol-Selection.html | 60 ++
+ pyme-web/doc/gpgme/Protocols-and-Engines.html | 82 +++
+ pyme-web/doc/gpgme/Public-Key-Algorithms.html | 74 +++
+ .../doc/gpgme/Registering-I_002fO-Callbacks.html | 81 +++
+ pyme-web/doc/gpgme/Run-Control.html | 53 ++
+ pyme-web/doc/gpgme/Selecting-Signers.html | 64 ++
+ pyme-web/doc/gpgme/Sign.html | 50 ++
+ pyme-web/doc/gpgme/Signal-Handling.html | 61 ++
+ pyme-web/doc/gpgme/Signature-Notation-Data.html | 85 +++
+ pyme-web/doc/gpgme/Text-Mode.html | 63 ++
+ pyme-web/doc/gpgme/Trust-Item-Management.html | 68 ++
+ pyme-web/doc/gpgme/Using-Automake.html | 74 +++
+ pyme-web/doc/gpgme/Using-External-Event-Loops.html | 74 +++
+ pyme-web/doc/gpgme/Using-Libtool.html | 44 ++
+ pyme-web/doc/gpgme/Verify.html | 492 +++++++++++++++
+ pyme-web/doc/gpgme/Waiting-For-Completion.html | 77 +++
+ pyme-web/doc/gpgme/gpgme.html | 251 --------
+ pyme-web/doc/gpgme/gpgme_1.html | 76 ---
+ pyme-web/doc/gpgme/gpgme_10.html | 61 --
+ pyme-web/doc/gpgme/gpgme_11.html | 130 ----
+ pyme-web/doc/gpgme/gpgme_12.html | 82 ---
+ pyme-web/doc/gpgme/gpgme_13.html | 130 ----
+ pyme-web/doc/gpgme/gpgme_14.html | 108 ----
+ pyme-web/doc/gpgme/gpgme_15.html | 69 ---
+ pyme-web/doc/gpgme/gpgme_16.html | 169 -----
+ pyme-web/doc/gpgme/gpgme_17.html | 63 --
+ pyme-web/doc/gpgme/gpgme_18.html | 63 --
+ pyme-web/doc/gpgme/gpgme_19.html | 66 --
+ pyme-web/doc/gpgme/gpgme_2.html | 79 ---
+ pyme-web/doc/gpgme/gpgme_20.html | 120 ----
+ pyme-web/doc/gpgme/gpgme_21.html | 102 ---
+ pyme-web/doc/gpgme/gpgme_22.html | 108 ----
+ pyme-web/doc/gpgme/gpgme_23.html | 237 -------
+ pyme-web/doc/gpgme/gpgme_24.html | 154 -----
+ pyme-web/doc/gpgme/gpgme_25.html | 248 --------
+ pyme-web/doc/gpgme/gpgme_26.html | 107 ----
+ pyme-web/doc/gpgme/gpgme_27.html | 80 ---
+ pyme-web/doc/gpgme/gpgme_28.html | 67 --
+ pyme-web/doc/gpgme/gpgme_29.html | 164 -----
+ pyme-web/doc/gpgme/gpgme_3.html | 86 ---
+ pyme-web/doc/gpgme/gpgme_30.html | 106 ----
+ pyme-web/doc/gpgme/gpgme_31.html | 232 -------
+ pyme-web/doc/gpgme/gpgme_32.html | 85 ---
+ pyme-web/doc/gpgme/gpgme_33.html | 223 -------
+ pyme-web/doc/gpgme/gpgme_34.html | 83 ---
+ pyme-web/doc/gpgme/gpgme_35.html | 70 ---
+ pyme-web/doc/gpgme/gpgme_36.html | 63 --
+ pyme-web/doc/gpgme/gpgme_37.html | 66 --
+ pyme-web/doc/gpgme/gpgme_38.html | 86 ---
+ pyme-web/doc/gpgme/gpgme_39.html | 79 ---
+ pyme-web/doc/gpgme/gpgme_4.html | 83 ---
+ pyme-web/doc/gpgme/gpgme_40.html | 89 ---
+ pyme-web/doc/gpgme/gpgme_41.html | 99 ---
+ pyme-web/doc/gpgme/gpgme_42.html | 144 -----
+ pyme-web/doc/gpgme/gpgme_43.html | 152 -----
+ pyme-web/doc/gpgme/gpgme_44.html | 112 ----
+ pyme-web/doc/gpgme/gpgme_45.html | 101 ---
+ pyme-web/doc/gpgme/gpgme_46.html | 459 --------------
+ pyme-web/doc/gpgme/gpgme_47.html | 292 ---------
+ pyme-web/doc/gpgme/gpgme_48.html | 363 -----------
+ pyme-web/doc/gpgme/gpgme_49.html | 209 -------
+ pyme-web/doc/gpgme/gpgme_5.html | 74 ---
+ pyme-web/doc/gpgme/gpgme_50.html | 88 ---
+ pyme-web/doc/gpgme/gpgme_51.html | 208 -------
+ pyme-web/doc/gpgme/gpgme_52.html | 154 -----
+ pyme-web/doc/gpgme/gpgme_53.html | 291 ---------
+ pyme-web/doc/gpgme/gpgme_54.html | 91 ---
+ pyme-web/doc/gpgme/gpgme_55.html | 107 ----
+ pyme-web/doc/gpgme/gpgme_56.html | 140 -----
+ pyme-web/doc/gpgme/gpgme_57.html | 106 ----
+ pyme-web/doc/gpgme/gpgme_58.html | 89 ---
+ pyme-web/doc/gpgme/gpgme_59.html | 97 ---
+ pyme-web/doc/gpgme/gpgme_6.html | 77 ---
+ pyme-web/doc/gpgme/gpgme_60.html | 142 -----
+ pyme-web/doc/gpgme/gpgme_61.html | 626 -------------------
+ pyme-web/doc/gpgme/gpgme_62.html | 107 ----
+ pyme-web/doc/gpgme/gpgme_63.html | 67 --
+ pyme-web/doc/gpgme/gpgme_64.html | 95 ---
+ pyme-web/doc/gpgme/gpgme_65.html | 233 -------
+ pyme-web/doc/gpgme/gpgme_66.html | 65 --
+ pyme-web/doc/gpgme/gpgme_67.html | 220 -------
+ pyme-web/doc/gpgme/gpgme_68.html | 75 ---
+ pyme-web/doc/gpgme/gpgme_69.html | 119 ----
+ pyme-web/doc/gpgme/gpgme_7.html | 123 ----
+ pyme-web/doc/gpgme/gpgme_70.html | 107 ----
+ pyme-web/doc/gpgme/gpgme_71.html | 218 -------
+ pyme-web/doc/gpgme/gpgme_72.html | 134 ----
+ pyme-web/doc/gpgme/gpgme_73.html | 299 ---------
+ pyme-web/doc/gpgme/gpgme_74.html | 103 ----
+ pyme-web/doc/gpgme/gpgme_75.html | 104 ----
+ pyme-web/doc/gpgme/gpgme_76.html | 118 ----
+ pyme-web/doc/gpgme/gpgme_77.html | 95 ---
+ pyme-web/doc/gpgme/gpgme_78.html | 71 ---
+ pyme-web/doc/gpgme/gpgme_79.html | 686 ---------------------
+ pyme-web/doc/gpgme/gpgme_8.html | 155 -----
+ pyme-web/doc/gpgme/gpgme_80.html | 120 ----
+ pyme-web/doc/gpgme/gpgme_81.html | 278 ---------
+ pyme-web/doc/gpgme/gpgme_82.html | 272 --------
+ pyme-web/doc/gpgme/gpgme_83.html | 180 ------
+ pyme-web/doc/gpgme/gpgme_84.html | 99 ---
+ pyme-web/doc/gpgme/gpgme_9.html | 104 ----
+ pyme-web/doc/gpgme/gpgme_abt.html | 206 -------
+ pyme-web/doc/gpgme/gpgme_fot.html | 53 --
+ pyme-web/doc/gpgme/gpgme_ovr.html | 68 --
+ pyme-web/doc/gpgme/gpgme_toc.html | 247 --------
+ pyme-web/doc/gpgme/index.html | 497 ++++++++-------
+ 176 files changed, 9054 insertions(+), 13378 deletions(-)
+
+commit 163c1053dc761682f5a4231da163bdd0ff7162d7
+Author: belyi
+Date: Tue Apr 1 21:14:29 2008 +0000
+
+ Update Home page to be a bit more visitor friendly.
+
+ pyme-web/Makefile | 2 +-
+ pyme-web/default.css | 27 ++++++++++++++++++++
+ pyme-web/index.html | 70 +++++++++++++++++++++++++++++++++++-----------------
+ 3 files changed, 75 insertions(+), 24 deletions(-)
+
+commit 05db2d17d8fda0ab8c948bbdc0643dfc1466830d
+Author: belyi
+Date: Sun Mar 30 21:27:38 2008 +0000
+
+ Add a rule to build binary distribution for Windows.
+
+ pyme/Makefile | 16 ++++++++++++++--
+ 1 file changed, 14 insertions(+), 2 deletions(-)
+
+commit 57acb1089f5f8c24323ee62fc0a7f492a496b9c0
+Author: belyi
+Date: Sat Mar 29 22:50:11 2008 +0000
+
+ Switch to using central location for python files (pycentral)
+ Update docs rule to fix location of the python source files.
+
+ pyme/Makefile | 5 +++-
+ pyme/debian/changelog | 4 ++-
+ pyme/debian/control | 74 +++++------------------------------------------
+ pyme/debian/dirs | 2 --
+ pyme/debian/docs | 1 +
+ pyme/debian/postinst.ex | 48 ------------------------------
+ pyme/debian/postrm.ex | 38 ------------------------
+ pyme/debian/preinst.ex | 44 ----------------------------
+ pyme/debian/prerm.ex | 39 -------------------------
+ pyme/debian/rules | 50 ++++++--------------------------
+ pyme/debian/setup.cfg-2.2 | 8 -----
+ pyme/debian/setup.cfg-2.3 | 8 -----
+ pyme/debian/setup.cfg-2.4 | 8 -----
+ pyme/gpgme-h-clean.py | 2 +-
+ pyme/pyme/core.py | 2 +-
+ pyme/pyme/util.py | 2 +-
+ 16 files changed, 28 insertions(+), 307 deletions(-)
+
+commit 2b56fd10517cfbcffaa4ba98d8ea42f40f0d38a9
+Author: belyi
+Date: Sun Mar 23 02:01:12 2008 +0000
+
+ Turn SWIG's autodoc feature on. Ignore 'next' in the types which are lists now.
+ Use new style for class declarations. Specify None as a default value for
+ core.check_version() method. Update version.py for 0.8.0 version.
+
+ pyme/examples/pygpa.py | 2 +-
+ pyme/gpgme.i | 5 +++++
+ pyme/pyme/core.py | 2 +-
+ pyme/pyme/util.py | 5 +++--
+ pyme/pyme/version.py | 6 +++---
+ 5 files changed, 13 insertions(+), 7 deletions(-)
+
+commit df5e25d7ee4dc0aa0d429f9d009322dd8ac33bb8
+Author: belyi
+Date: Thu Mar 20 19:07:00 2008 +0000
+
+ Improve matching for DEPRECATED typedefs
+
+ pyme/gpgme-h-clean.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit 78d8fc732848ac267ec65e9069265cd500587cdf
+Author: belyi
+Date: Wed Mar 19 19:28:40 2008 +0000
+
+ Update API to use list when types containing 'next' field are return.
+ Update examples accordingly
+ Add verifydetails.py example
+ Start adding bullets for 0.8.0 version.
+
+ pyme/Makefile | 2 +-
+ pyme/debian/changelog | 14 +++++++-
+ pyme/examples/PyGtkGpgKeys.py | 53 +++++++++++++--------------
+ pyme/examples/delkey.py | 7 ++--
+ pyme/examples/encrypt-to-all.py | 7 ++--
+ pyme/examples/exportimport.py | 7 ++--
+ pyme/examples/pygpa.py | 70 ++++++++++++++++--------------------
+ pyme/examples/signverify.py | 11 +++---
+ pyme/examples/verifydetails.py | 79 +++++++++++++++++++++++++++++++++++++++++
+ pyme/gpgme.i | 19 +++++++++-
+ 10 files changed, 180 insertions(+), 89 deletions(-)
+
+commit 342d85b07475e7360bcd62804bf5facda039494f
+Author: belyi
+Date: Mon Mar 10 01:14:16 2008 +0000
+
+ Change references to source files so that they point to the WebCVS browse
+ location.
+
+ pyme-web/doc/pyme/index.html | 2 +-
+ pyme-web/doc/pyme/pyme.callbacks.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.data.encoding.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.data.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.event.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.import.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.keylist.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.md.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.pk.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.protocol.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.sig.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.sig.mode.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.sigsum.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.status.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.validity.html | 2 +-
+ pyme-web/doc/pyme/pyme.core.html | 2 +-
+ pyme-web/doc/pyme/pyme.errors.html | 2 +-
+ pyme-web/doc/pyme/pyme.html | 2 +-
+ pyme-web/doc/pyme/pyme.util.html | 2 +-
+ pyme-web/doc/pyme/pyme.version.html | 2 +-
+ 22 files changed, 22 insertions(+), 22 deletions(-)
+
+commit 4139dd1d066c1a6c892d84fe45dc3e6c4aa1b803
+Author: belyi
+Date: Sat Mar 8 18:21:08 2008 +0000
+
+ Add core.check_version(None) to all examples since this function is used by
+ Gpgme to do internal initialization. Update debian/rules to use dh_pysupport
+ instead of deprecated dh_python.
+
+ pyme/debian/rules | 8 +++-----
+ pyme/examples/PyGtkGpgKeys.py | 7 ++++++-
+ pyme/examples/delkey.py | 2 ++
+ pyme/examples/encrypt-to-all.py | 3 +++
+ pyme/examples/exportimport.py | 2 ++
+ pyme/examples/genkey.py | 1 +
+ pyme/examples/inter-edit.py | 3 +++
+ pyme/examples/pygpa.py | 5 +++++
+ pyme/examples/sign.py | 2 ++
+ pyme/examples/signverify.py | 2 ++
+ pyme/examples/simple.py | 2 ++
+ pyme/examples/t-edit.py | 3 +++
+ 12 files changed, 34 insertions(+), 6 deletions(-)
+
+commit ae76c6176457dd38e0634cbc17d794294a3a81d2
+Author: belyi
+Date: Wed Apr 12 22:20:38 2006 +0000
+
+ Change name of internal package name from 'gpgme' to 'pygpgme' to avoid
+ conflict with gpgme.dll on Windows.
+ Fix build with SWIG 1.3.28.
+ Change version to 0.7.1 in a preparation for new release.
+
+ pyme/Makefile | 3 +-
+ pyme/debian/changelog | 12 ++++
+ pyme/gpgme.i | 19 +++---
+ pyme/pyme/callbacks.py | 1 -
+ pyme/pyme/core.py | 153 +++++++++++++++++++++++++------------------------
+ pyme/pyme/errors.py | 12 ++--
+ pyme/pyme/util.py | 10 ++--
+ pyme/pyme/version.py | 2 +-
+ pyme/setup.py | 4 +-
+ 9 files changed, 116 insertions(+), 100 deletions(-)
+
+commit d644383a76e9f83bc2d426628319e3c4a989dc2d
+Author: belyi
+Date: Sat Dec 17 01:34:53 2005 +0000
+
+ Put all constants into pyme.constants package to avoid stepping on python
+ reserved words.
+ Add build rules for Mingw32 and Cygwin on Windows. Rules for Mingw under
+ Debian are still to come.
+ Fixed a small bug in pygpa.py example.
+
+ pyme/Makefile | 11 ++++++++---
+ pyme/examples/pygpa.py | 3 ++-
+ pyme/pyme/__init__.py | 2 +-
+ pyme/pyme/constants/__init__.py | 3 +++
+ pyme/setup.py | 42 ++++++++++++++++++++++++++++++++++++-----
+ 5 files changed, 51 insertions(+), 10 deletions(-)
+
+commit 89eb370fcaa8adc9d219eadbaa579dde7bf06329
+Author: belyi
+Date: Mon Aug 1 03:08:32 2005 +0000
+
+ Imported changes provided by Joost van Baal:
+ Use dh_python in debian/rules and change the Section pyme belongs to from
+ 'libs' to 'python'.
+
+ pyme/debian/control | 6 +++---
+ pyme/debian/rules | 2 ++
+ 2 files changed, 5 insertions(+), 3 deletions(-)
+
+commit ad76d10c2a77b45b7459c62131279e946b860891
+Author: belyi
+Date: Fri Jun 10 03:01:22 2005 +0000
+
+ Update 'docs' rule in Makefile to build packages first to ensure that
+ documentation is build for the current version of pyme and not for the
+ installed one.
+
+ Added 'callbacks' into the list of visible pyme modules (__all__ var.)
+
+ Slightly updated INSTALL file.
+
+ pyme/INSTALL | 11 ++++++++---
+ pyme/Makefile | 4 ++--
+ pyme/pyme/__init__.py | 2 +-
+ 3 files changed, 11 insertions(+), 6 deletions(-)
+
+commit 2fe1a81e00721698bfa6850b3db2eb85e43d1724
+Author: belyi
+Date: Wed Jun 8 16:16:18 2005 +0000
+
+ Update pyme documentation to remove dead links to pyme.gpgme.html and
+ pyme._gpgme.html
+ Added reference to the installed GPGME and PyMe documentation to the head
+ web page.
+ Updated Makefile to install all *.html files and to clean *~ files in all
+ subdirectories
+
+ pyme-web/Makefile | 10 ++++++----
+ pyme-web/doc/pyme/index.html | 8 +++-----
+ pyme-web/doc/pyme/pyme.callbacks.html | 8 --------
+ pyme-web/doc/pyme/pyme.core.html | 1 -
+ pyme-web/doc/pyme/pyme.errors.html | 8 --------
+ pyme-web/doc/pyme/pyme.html | 8 +++-----
+ pyme-web/doc/pyme/pyme.util.html | 8 --------
+ pyme-web/index.html | 9 +++++++--
+ 8 files changed, 19 insertions(+), 41 deletions(-)
+
+commit 6aa34cce4ea0099e50b4936dfee59778157b8ca8
+Author: belyi
+Date: Wed Jun 8 15:18:20 2005 +0000
+
+ Added pyme and gpgme documentation.
+
+ pyme-web/doc/gpgme/gpgme.html | 251 ++++++++
+ pyme-web/doc/gpgme/gpgme_1.html | 76 +++
+ pyme-web/doc/gpgme/gpgme_10.html | 61 ++
+ pyme-web/doc/gpgme/gpgme_11.html | 130 ++++
+ pyme-web/doc/gpgme/gpgme_12.html | 82 +++
+ pyme-web/doc/gpgme/gpgme_13.html | 130 ++++
+ pyme-web/doc/gpgme/gpgme_14.html | 108 ++++
+ pyme-web/doc/gpgme/gpgme_15.html | 69 +++
+ pyme-web/doc/gpgme/gpgme_16.html | 169 +++++
+ pyme-web/doc/gpgme/gpgme_17.html | 63 ++
+ pyme-web/doc/gpgme/gpgme_18.html | 63 ++
+ pyme-web/doc/gpgme/gpgme_19.html | 66 ++
+ pyme-web/doc/gpgme/gpgme_2.html | 79 +++
+ pyme-web/doc/gpgme/gpgme_20.html | 120 ++++
+ pyme-web/doc/gpgme/gpgme_21.html | 102 +++
+ pyme-web/doc/gpgme/gpgme_22.html | 108 ++++
+ pyme-web/doc/gpgme/gpgme_23.html | 237 +++++++
+ pyme-web/doc/gpgme/gpgme_24.html | 154 +++++
+ pyme-web/doc/gpgme/gpgme_25.html | 248 ++++++++
+ pyme-web/doc/gpgme/gpgme_26.html | 107 ++++
+ pyme-web/doc/gpgme/gpgme_27.html | 80 +++
+ pyme-web/doc/gpgme/gpgme_28.html | 67 ++
+ pyme-web/doc/gpgme/gpgme_29.html | 164 +++++
+ pyme-web/doc/gpgme/gpgme_3.html | 86 +++
+ pyme-web/doc/gpgme/gpgme_30.html | 106 ++++
+ pyme-web/doc/gpgme/gpgme_31.html | 232 +++++++
+ pyme-web/doc/gpgme/gpgme_32.html | 85 +++
+ pyme-web/doc/gpgme/gpgme_33.html | 223 +++++++
+ pyme-web/doc/gpgme/gpgme_34.html | 83 +++
+ pyme-web/doc/gpgme/gpgme_35.html | 70 +++
+ pyme-web/doc/gpgme/gpgme_36.html | 63 ++
+ pyme-web/doc/gpgme/gpgme_37.html | 66 ++
+ pyme-web/doc/gpgme/gpgme_38.html | 86 +++
+ pyme-web/doc/gpgme/gpgme_39.html | 79 +++
+ pyme-web/doc/gpgme/gpgme_4.html | 83 +++
+ pyme-web/doc/gpgme/gpgme_40.html | 89 +++
+ pyme-web/doc/gpgme/gpgme_41.html | 99 +++
+ pyme-web/doc/gpgme/gpgme_42.html | 144 +++++
+ pyme-web/doc/gpgme/gpgme_43.html | 152 +++++
+ pyme-web/doc/gpgme/gpgme_44.html | 112 ++++
+ pyme-web/doc/gpgme/gpgme_45.html | 101 +++
+ pyme-web/doc/gpgme/gpgme_46.html | 459 ++++++++++++++
+ pyme-web/doc/gpgme/gpgme_47.html | 292 +++++++++
+ pyme-web/doc/gpgme/gpgme_48.html | 363 +++++++++++
+ pyme-web/doc/gpgme/gpgme_49.html | 209 +++++++
+ pyme-web/doc/gpgme/gpgme_5.html | 74 +++
+ pyme-web/doc/gpgme/gpgme_50.html | 88 +++
+ pyme-web/doc/gpgme/gpgme_51.html | 208 +++++++
+ pyme-web/doc/gpgme/gpgme_52.html | 154 +++++
+ pyme-web/doc/gpgme/gpgme_53.html | 291 +++++++++
+ pyme-web/doc/gpgme/gpgme_54.html | 91 +++
+ pyme-web/doc/gpgme/gpgme_55.html | 107 ++++
+ pyme-web/doc/gpgme/gpgme_56.html | 140 +++++
+ pyme-web/doc/gpgme/gpgme_57.html | 106 ++++
+ pyme-web/doc/gpgme/gpgme_58.html | 89 +++
+ pyme-web/doc/gpgme/gpgme_59.html | 97 +++
+ pyme-web/doc/gpgme/gpgme_6.html | 77 +++
+ pyme-web/doc/gpgme/gpgme_60.html | 142 +++++
+ pyme-web/doc/gpgme/gpgme_61.html | 626 +++++++++++++++++++
+ pyme-web/doc/gpgme/gpgme_62.html | 107 ++++
+ pyme-web/doc/gpgme/gpgme_63.html | 67 ++
+ pyme-web/doc/gpgme/gpgme_64.html | 95 +++
+ pyme-web/doc/gpgme/gpgme_65.html | 233 +++++++
+ pyme-web/doc/gpgme/gpgme_66.html | 65 ++
+ pyme-web/doc/gpgme/gpgme_67.html | 220 +++++++
+ pyme-web/doc/gpgme/gpgme_68.html | 75 +++
+ pyme-web/doc/gpgme/gpgme_69.html | 119 ++++
+ pyme-web/doc/gpgme/gpgme_7.html | 123 ++++
+ pyme-web/doc/gpgme/gpgme_70.html | 107 ++++
+ pyme-web/doc/gpgme/gpgme_71.html | 218 +++++++
+ pyme-web/doc/gpgme/gpgme_72.html | 134 ++++
+ pyme-web/doc/gpgme/gpgme_73.html | 299 +++++++++
+ pyme-web/doc/gpgme/gpgme_74.html | 103 ++++
+ pyme-web/doc/gpgme/gpgme_75.html | 104 ++++
+ pyme-web/doc/gpgme/gpgme_76.html | 118 ++++
+ pyme-web/doc/gpgme/gpgme_77.html | 95 +++
+ pyme-web/doc/gpgme/gpgme_78.html | 71 +++
+ pyme-web/doc/gpgme/gpgme_79.html | 686 +++++++++++++++++++++
+ pyme-web/doc/gpgme/gpgme_8.html | 155 +++++
+ pyme-web/doc/gpgme/gpgme_80.html | 120 ++++
+ pyme-web/doc/gpgme/gpgme_81.html | 278 +++++++++
+ pyme-web/doc/gpgme/gpgme_82.html | 272 ++++++++
+ pyme-web/doc/gpgme/gpgme_83.html | 180 ++++++
+ pyme-web/doc/gpgme/gpgme_84.html | 99 +++
+ pyme-web/doc/gpgme/gpgme_9.html | 104 ++++
+ pyme-web/doc/gpgme/gpgme_abt.html | 206 +++++++
+ pyme-web/doc/gpgme/gpgme_fot.html | 53 ++
+ pyme-web/doc/gpgme/gpgme_ovr.html | 68 ++
+ pyme-web/doc/gpgme/gpgme_toc.html | 247 ++++++++
+ pyme-web/doc/gpgme/index.html | 251 ++++++++
+ pyme-web/doc/pyme/index.html | 166 +++++
+ pyme-web/doc/pyme/pyme.callbacks.html | 50 ++
+ .../doc/pyme/pyme.constants.data.encoding.html | 48 ++
+ pyme-web/doc/pyme/pyme.constants.data.html | 29 +
+ pyme-web/doc/pyme/pyme.constants.event.html | 48 ++
+ pyme-web/doc/pyme/pyme.constants.html | 39 ++
+ pyme-web/doc/pyme/pyme.constants.import.html | 49 ++
+ pyme-web/doc/pyme/pyme.constants.keylist.html | 29 +
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html | 48 ++
+ pyme-web/doc/pyme/pyme.constants.md.html | 58 ++
+ pyme-web/doc/pyme/pyme.constants.pk.html | 50 ++
+ pyme-web/doc/pyme/pyme.constants.protocol.html | 46 ++
+ pyme-web/doc/pyme/pyme.constants.sig.html | 29 +
+ pyme-web/doc/pyme/pyme.constants.sig.mode.html | 47 ++
+ pyme-web/doc/pyme/pyme.constants.sigsum.html | 55 ++
+ pyme-web/doc/pyme/pyme.constants.status.html | 117 ++++
+ pyme-web/doc/pyme/pyme.constants.validity.html | 50 ++
+ pyme-web/doc/pyme/pyme.core.html | 254 ++++++++
+ pyme-web/doc/pyme/pyme.errors.html | 90 +++
+ pyme-web/doc/pyme/pyme.html | 166 +++++
+ pyme-web/doc/pyme/pyme.util.html | 78 +++
+ pyme-web/doc/pyme/pyme.version.html | 37 ++
+ pyme-web/index.html | 6 +-
+ 113 files changed, 14966 insertions(+), 1 deletion(-)
+
+commit 2d6fe54479f042644f7b0f3d2fe35877d2056144
+Author: belyi
+Date: Thu May 19 02:06:09 2005 +0000
+
+ Added INSTALL file.
+
+ pyme/INSTALL | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+commit d6892fff0c3cedf41dba4c25ab8608e7f2bc039c
+Author: belyi
+Date: Tue May 17 16:49:28 2005 +0000
+
+ Update copyright note on simple.py
+
+ pyme/examples/simple.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit c2cd9cdf5995843aad7b200b929db2969effc9d2
+Author: belyi
+Date: Tue May 17 15:03:58 2005 +0000
+
+ Update simple.py to catch errors.
+
+ pyme/examples/simple.py | 17 +++++++++++------
+ 1 file changed, 11 insertions(+), 6 deletions(-)
+
+commit eaedae7c6a0ea993caab067efe781a59b6769c44
+Author: belyi
+Date: Tue May 17 01:18:23 2005 +0000
+
+ Added 'PYTHON = python' into Makefile for bug #1199122
+
+ pyme/Makefile | 1 +
+ pyme/examples/signverify.py | 1 +
+ 2 files changed, 2 insertions(+)
+
+commit 56fd244bb2636a4d58629899ea3cde1d96428198
+Author: belyi
+Date: Wed Apr 27 21:37:06 2005 +0000
+
+ Added pygpa example.
+
+ pyme/debian/changelog | 3 +-
+ pyme/examples/pygpa.glade | 5546 +++++++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/pygpa.py | 1459 ++++++++++++
+ 3 files changed, 7007 insertions(+), 1 deletion(-)
+
+commit 2d9a2a91a59ac3fee5410c953b7e0859e9e7cd35
+Author: belyi
+Date: Thu Apr 21 15:17:51 2005 +0000
+
+ Change version to 0.7.0 due to the change in license.
+
+ pyme/debian/changelog | 2 +-
+ pyme/pyme/version.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+commit 94e34e38d742f145385bd235825b6ba1e30d8339
+Author: belyi
+Date: Thu Apr 21 03:53:12 2005 +0000
+
+ Changed license on PyMe from GPL to LGPL.
+ PyMe examples keep GPL license.
+
+ pyme/COPYING.LESSER | 510 +++++++++++++++++++++++++++++++++++
+ pyme/Makefile | 20 +-
+ pyme/debian/changelog | 4 +-
+ pyme/debian/copyright | 22 +-
+ pyme/gpgme-h-clean.py | 16 ++
+ pyme/gpgme.i | 20 +-
+ pyme/helpers.c | 20 +-
+ pyme/helpers.h | 20 +-
+ pyme/pyme/__init__.py | 20 +-
+ pyme/pyme/callbacks.py | 20 +-
+ pyme/pyme/constants/data/encoding.py | 20 +-
+ pyme/pyme/constants/event.py | 20 +-
+ pyme/pyme/constants/import.py | 20 +-
+ pyme/pyme/constants/keylist/mode.py | 20 +-
+ pyme/pyme/constants/md.py | 20 +-
+ pyme/pyme/constants/pk.py | 20 +-
+ pyme/pyme/constants/protocol.py | 20 +-
+ pyme/pyme/constants/sig/mode.py | 20 +-
+ pyme/pyme/constants/sigsum.py | 20 +-
+ pyme/pyme/constants/status.py | 20 +-
+ pyme/pyme/constants/validity.py | 20 +-
+ pyme/pyme/core.py | 20 +-
+ pyme/pyme/errors.py | 20 +-
+ pyme/pyme/util.py | 20 +-
+ pyme/pyme/version.py | 22 +-
+ pyme/setup.py | 20 +-
+ 26 files changed, 761 insertions(+), 233 deletions(-)
+
+commit 0d8aa0f6335cb1506a37085095ed45173b099a02
+Author: belyi
+Date: Tue Apr 19 01:46:06 2005 +0000
+
+ Added __hash__ and __eq__ methods to GpgmeWrapper to allow both Context()
+ and Data() to be used as a dictionary key.
+ Changed core.wait() function to always return a tuple. On timeout now it
+ returns (0, None) instead of just None. Plus, return context is now a
+ Context() object instead of a wrapper return by underlying gpgme.
+
+ pyme/helpers.c | 1 -
+ pyme/pyme/core.py | 25 +++++++++++++++----------
+ pyme/pyme/util.py | 9 +++++++++
+ 3 files changed, 24 insertions(+), 11 deletions(-)
+
+commit 63ff6d10637be1dcbcd78c939ac1ef1ac30b1024
+Author: belyi
+Date: Wed Apr 6 04:58:40 2005 +0000
+
+ Made hook parameter optional in passphrase_cb and progress_cb.
+ Allowed None for callbacks to unset ones set previously.
+ Removed cleanup of exception in callbacks - now just retrieve the error code.
+ Added prev_bad parameter in passphrase_cb since it can be used in
+ change password protocols.
+ Updated examples to follow new sets of arguments in callbacks
+ Updated op_edit to check if passed key is None (otherwise gpgme dumps core)
+ God rid of annoying warning "function declaration isn't a prototype" in
+ helpers.c and helpers.h by changing from () to (void) list of arguments.
+
+ pyme/debian/changelog | 10 +++++---
+ pyme/examples/signverify.py | 2 +-
+ pyme/examples/t-edit.py | 2 +-
+ pyme/gpgme.i | 18 +++++++++-----
+ pyme/helpers.c | 60 ++++++++++++++++++++++++++++++---------------
+ pyme/helpers.h | 4 +--
+ pyme/pyme/callbacks.py | 6 +++--
+ pyme/pyme/core.py | 47 +++++++++++++++++++++--------------
+ pyme/pyme/errors.py | 2 +-
+ 9 files changed, 96 insertions(+), 55 deletions(-)
+
+commit 8f0ab8138c7aa190936376ccbbf33bb09c64d6f1
+Author: belyi
+Date: Thu Mar 31 23:50:59 2005 +0000
+
+ Added exception handling in passphrase_cb and edit_cb. If GPGMEError
+ exception is thrown in those callbacks it will be converted into its
+ core representation and return as an error code to the caller.
+ On all other exceptions error code will be GPG_ERR_GENERAL.
+
+ pyme/Makefile | 1 +
+ pyme/debian/changelog | 8 ++++++++
+ pyme/gpgme.i | 20 ++++++++++++++------
+ pyme/helpers.c | 51 +++++++++++++++++++++++++++++++++++++++++++++------
+ pyme/helpers.h | 3 +++
+ 5 files changed, 71 insertions(+), 12 deletions(-)
+
+commit 9903d1fb11231e7e3d920e58d1ecb674c5988b07
+Author: belyi
+Date: Thu Mar 31 05:12:15 2005 +0000
+
+ Remove workaround from Context.wait() method since the bug report and
+ patch fixing gpgme_wait's behavior is sent to GPMGE developers already.
+ Added errorcheck into op_edit() so that it can report an error.
+
+ pyme/pyme/core.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+commit 45e8a5f4e13d3ca797ec3b0037242874a6be5562
+Author: belyi
+Date: Sat Mar 26 19:44:18 2005 +0000
+
+ Updated verion number to 0.6.2 in version.py
+ Added examples/*.glade files into documentation package.
+
+ pyme/debian/examples | 1 +
+ pyme/pyme/version.py | 2 +-
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+commit 270b87bb40e180cb6e8f1de9a0e8161525ffa4ab
+Author: belyi
+Date: Sat Mar 26 19:31:14 2005 +0000
+
+ Updated debian/changelog regarding PyGtkGpgKeys example and a fix in errors.
+
+ pyme/debian/changelog | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+commit ea4682009a506db91e5174ffd038fe7e4406b591
+Author: belyi
+Date: Sat Mar 26 19:25:36 2005 +0000
+
+ Added handling of right mouse button click.
+ Changed reporting a string instead of a number on key generation failure.
+
+ pyme/examples/PyGtkGpgKeys.glade | 2 ++
+ pyme/examples/PyGtkGpgKeys.py | 30 +++++++++++++++++++++++++++---
+ 2 files changed, 29 insertions(+), 3 deletions(-)
+
+commit f65ad1a703d0098a3204fb8527a54d253e5847e7
+Author: belyi
+Date: Sat Mar 26 18:11:11 2005 +0000
+
+ Added another column indicating if a key has a secret part.
+ Automated generation of the View menu from the view field of the KeyColumn
+ class.
+
+ pyme/examples/PyGtkGpgKeys.glade | 93 ++--------------------------------------
+ pyme/examples/PyGtkGpgKeys.py | 74 +++++++++++++++++---------------
+ 2 files changed, 44 insertions(+), 123 deletions(-)
+
+commit b54e83a7a7a5785502f3c7e8b95f15e23b40e65a
+Author: belyi
+Date: Sat Mar 26 16:45:13 2005 +0000
+
+ Small change to the way gtk.TreeModel object is used.
+
+ pyme/examples/PyGtkGpgKeys.py | 21 ++++++++++-----------
+ 1 file changed, 10 insertions(+), 11 deletions(-)
+
+commit 7078db75cef4c1fd70cf03e37172bdb4f933fd1b
+Author: belyi
+Date: Fri Mar 25 23:33:06 2005 +0000
+
+ Use more comprehansible error reporting since gpgme_strerror_r returns None
+ all the time.
+
+ pyme/pyme/errors.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+commit 151213f4344d9984975721440af07de09e3df61c
+Author: belyi
+Date: Fri Mar 25 04:30:17 2005 +0000
+
+ Improved PyGtkGpgKeys example to manage owner_trust on keys.
+ Added another example inter-edit.py which is just a hepler to write
+ scripts for Context.op_edit() command.
+
+ pyme/examples/PyGtkGpgKeys.glade | 78 ++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/PyGtkGpgKeys.py | 68 +++++++++++++++++++++++++++++++----
+ pyme/examples/inter-edit.py | 54 ++++++++++++++++++++++++++++
+ pyme/examples/t-edit.py | 18 ++++++++++
+ 4 files changed, 212 insertions(+), 6 deletions(-)
+
+commit fc7235af217bcee5231ce7fbd7f234712d5ad3b0
+Author: belyi
+Date: Fri Mar 25 00:30:39 2005 +0000
+
+ Updated PyGtkGpgKeys example to include import, export and reload
+ functionality. Also added ability to remove number of keys simultanously.
+ Rearanged how KeyColumn is used to avoid unnecessary sorts and duplication
+ of information in different parts of the code.
+
+ pyme/examples/PyGtkGpgKeys.glade | 86 +++++++++-
+ pyme/examples/PyGtkGpgKeys.py | 332 ++++++++++++++++++++++++++++-----------
+ 2 files changed, 325 insertions(+), 93 deletions(-)
+
+commit 9f65749ccb1b7cab562e19c03f4371d5f7d94912
+Author: belyi
+Date: Thu Mar 24 05:51:03 2005 +0000
+
+ Added example of PyGTK+ and PyMe integration.
+ For now it does only simple things - listing, deleting, and generating keys.
+
+ pyme/examples/PyGtkGpgKeys.glade | 1321 +++++++++++++++++++++++++++++++++++++
+ pyme/examples/PyGtkGpgKeys.gladep | 8 +
+ pyme/examples/PyGtkGpgKeys.py | 424 ++++++++++++
+ 3 files changed, 1753 insertions(+)
+
+commit 59e23f32c3b46413c9ec09e23e1a385a110fb103
+Author: belyi
+Date: Thu Mar 24 05:44:58 2005 +0000
+
+ Added wait method Context class which handles asynchornous calls a little
+ bit better than the one generated by SWIG.
+
+ pyme/debian/changelog | 7 +++++++
+ pyme/gpgme.i | 1 +
+ pyme/pyme/core.py | 40 ++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 48 insertions(+)
+
+commit 4c1b5259e4985df2cba0ae4fc09f12cd94603a75
+Author: belyi
+Date: Tue Mar 22 18:29:31 2005 +0000
+
+ Added correct handling of Context.op_edit() method.
+ Added example/t-edit.py showing usage for this method.
+ Output of this example should match output of the tests/gpg/t-edit
+ from the GPGME test suite.
+ Remove unused static function from helpers.c
+
+ pyme/examples/t-edit.py | 38 ++++++++++++++++++++++++++++++++++++++
+ pyme/gpgme.i | 36 ++++++++++++++++++++++++++++++++++++
+ pyme/helpers.c | 36 ------------------------------------
+ pyme/pyme/core.py | 5 ++++-
+ 4 files changed, 78 insertions(+), 37 deletions(-)
+
+commit dc587e215283bfef2dd594f86a7b2945f74f5155
+Author: belyi
+Date: Sat Mar 19 01:43:59 2005 +0000
+
+ Update changelog to include note about deprecated function in 0.6.1 release
+
+ pyme/debian/changelog | 3 ++-
+ pyme/examples/encrypt-to-all.py | 3 +--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+commit 86de4b3ad777f980ccf7ba3462c85bbe1787d1fd
+Author: belyi
+Date: Sat Mar 19 01:40:07 2005 +0000
+
+ Remove deprecated functions from helpers.[ch]
+ Use gpgme-h-clean.py to remove deprecated functions and typedefs from
+ the GPGME header file. This will reduce the number of unused methods.
+
+ pyme/Makefile | 4 ++--
+ pyme/gpgme-h-clean.py | 26 ++++++++++++++++++++++++++
+ pyme/helpers.c | 8 --------
+ pyme/helpers.h | 2 --
+ 4 files changed, 28 insertions(+), 12 deletions(-)
+
+commit 2483efcbd0d73c628c4d7717928a766c3b58f0aa
+Author: belyi
+Date: Fri Mar 18 22:15:52 2005 +0000
+
+ Update copyright and author values in pyme/version.py
+ Create rules to build distribution files - one full and one without
+ debian bits.
+
+ pyme/Makefile | 28 ++++++++++++++++++++++------
+ pyme/pyme/version.py | 12 ++++++------
+ 2 files changed, 28 insertions(+), 12 deletions(-)
+
+commit 168593285380f5a7805f3dd08657d429a72d3621
+Author: belyi
+Date: Fri Mar 18 19:09:33 2005 +0000
+
+ Added package building for python2.4
+
+ Updated copyright notes to include myslef and avoid confusion who's the
+ maintainer. In John's own words: "I'd prefer to just step out of the picture".
+ Jonh's copyright notice left intact.
+
+ pyme/Makefile | 6 +++---
+ pyme/debian/changelog | 7 +++++++
+ pyme/debian/control | 30 +++++++++++++++++++++++++++---
+ pyme/debian/copyright | 10 ++++------
+ pyme/debian/rules | 4 ++++
+ pyme/debian/setup.cfg-2.4 | 8 ++++++++
+ pyme/examples/genkey.py | 4 ++--
+ pyme/gpgme.i | 4 ++--
+ pyme/helpers.c | 4 ++--
+ pyme/helpers.h | 4 ++--
+ pyme/pyme/__init__.py | 4 ++--
+ pyme/pyme/callbacks.py | 4 ++--
+ pyme/pyme/constants/data/encoding.py | 4 ++--
+ pyme/pyme/constants/event.py | 4 ++--
+ pyme/pyme/constants/import.py | 4 ++--
+ pyme/pyme/constants/keylist/mode.py | 4 ++--
+ pyme/pyme/constants/md.py | 4 ++--
+ pyme/pyme/constants/pk.py | 4 ++--
+ pyme/pyme/constants/protocol.py | 4 ++--
+ pyme/pyme/constants/sig/mode.py | 4 ++--
+ pyme/pyme/constants/sigsum.py | 4 ++--
+ pyme/pyme/constants/status.py | 4 ++--
+ pyme/pyme/constants/validity.py | 4 ++--
+ pyme/pyme/core.py | 4 ++--
+ pyme/pyme/errors.py | 4 ++--
+ pyme/pyme/util.py | 4 ++--
+ pyme/pyme/version.py | 2 +-
+ pyme/setup.py | 3 ++-
+ 28 files changed, 96 insertions(+), 54 deletions(-)
+
+commit 6dbbb252771133724b2879ed6d767cd708196dae
+Author: belyi
+Date: Fri Mar 18 18:04:35 2005 +0000
+
+ Remove the note about gpgme.i to be generated - it's been the primary source
+ for some time.
+
+ pyme/gpgme.i | 6 ------
+ 1 file changed, 6 deletions(-)
+
+commit 9d449fa4889c6bda6d14583c0625b8d5c4ffe759
+Author: belyi
+Date: Fri May 7 18:31:22 2004 +0000
+
+ Added my copyright in genkey.py since there's enough changes made.
+ Updated signverify to use only keys generated by genkey.py, to check
+ that keys added to singers are able to sign and to check that the
+ list of signers is not empty. The last check is necessary to prevent
+ signing with the key of the user running signverify.py script.
+ Added delkey.py script to delete keys generated by genkey.py
+ Added exportimport.py example for key export/import.
+
+ pyme/examples/delkey.py | 29 +++++++++++++++++
+ pyme/examples/exportimport.py | 76 +++++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/genkey.py | 6 ++--
+ pyme/examples/signverify.py | 18 ++++++----
+ 4 files changed, 119 insertions(+), 10 deletions(-)
+
+commit df98c8d28245ad2c14b0ab50fc8f8932853bec8b
+Author: belyi
+Date: Tue May 4 17:34:15 2004 +0000
+
+ Added examples/signverify.py for unattended sing/verify.
+ Updated examples/genkey.py to work correctly.
+ Updated gpgme.i to allow None as a value for gpgme_data_t
+
+ pyme/examples/genkey.py | 14 ++-------
+ pyme/examples/signverify.py | 72 +++++++++++++++++++++++++++++++++++++++++++++
+ pyme/gpgme.i | 21 ++++++++-----
+ 3 files changed, 87 insertions(+), 20 deletions(-)
+
+commit ba45931abf530ab89ead46d7233ff1b62b629a18
+Author: belyi
+Date: Thu Apr 8 16:15:09 2004 +0000
+
+ Ensure that we support only python2.2 and up. :-)
+ Use generators in core.Context class which makes pyme.aux obsolete
+ Remove importing future nested_scopes since they are standart starting
+ with python2.2
+
+ pyme/pyme/__init__.py | 5 ++---
+ pyme/pyme/aux.py | 56 ---------------------------------------------------
+ pyme/pyme/core.py | 15 +++++++++++---
+ pyme/pyme/errors.py | 1 -
+ pyme/pyme/util.py | 2 +-
+ 5 files changed, 15 insertions(+), 64 deletions(-)
+
+commit 4e9be5a55ecffa4da7ad5c192cc892eddaaa9586
+Author: belyi
+Date: Sun Mar 21 03:53:30 2004 +0000
+
+ Small change to index.html
+ Added clean: rule to the Makefile
+
+ pyme-web/Makefile | 3 +++
+ pyme-web/index.html | 6 +++---
+ 2 files changed, 6 insertions(+), 3 deletions(-)
+
+commit 2efb95176f4edf56ed61c9ac0c3aa09c56534df0
+Author: belyi
+Date: Sun Mar 21 03:00:32 2004 +0000
+
+ Added Makefile rules for pyme module installation.
+
+ pyme/Makefile | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+commit 2b83d5d8b513029cc3e54f2fa502ccc85618104b
+Author: belyi
+Date: Sun Mar 21 02:29:54 2004 +0000
+
+ Decorative change.
+
+ pyme/pyme/aux.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit e3478015d763a036c1d806ae01433fce59712204
+Author: belyi
+Date: Sun Mar 21 02:25:55 2004 +0000
+
+ Added RCS Id: tags
+
+ pyme/Makefile | 1 +
+ pyme/examples/encrypt-to-all.py | 3 ++-
+ pyme/examples/genkey.py | 3 ++-
+ pyme/examples/sign.py | 3 ++-
+ pyme/examples/simple.py | 3 ++-
+ pyme/gpgme.i | 1 +
+ pyme/helpers.c | 1 +
+ pyme/helpers.h | 1 +
+ pyme/pyme/__init__.py | 1 +
+ pyme/pyme/aux.py | 1 +
+ pyme/pyme/callbacks.py | 1 +
+ pyme/pyme/constants/__init__.py | 2 ++
+ pyme/pyme/constants/data/__init__.py | 2 ++
+ pyme/pyme/constants/data/encoding.py | 1 +
+ pyme/pyme/constants/event.py | 1 +
+ pyme/pyme/constants/import.py | 1 +
+ pyme/pyme/constants/keylist/__init__.py | 2 ++
+ pyme/pyme/constants/keylist/mode.py | 1 +
+ pyme/pyme/constants/md.py | 1 +
+ pyme/pyme/constants/pk.py | 1 +
+ pyme/pyme/constants/protocol.py | 1 +
+ pyme/pyme/constants/sig/__init__.py | 2 ++
+ pyme/pyme/constants/sig/mode.py | 1 +
+ pyme/pyme/constants/sigsum.py | 1 +
+ pyme/pyme/constants/status.py | 1 +
+ pyme/pyme/constants/validity.py | 1 +
+ pyme/pyme/core.py | 1 +
+ pyme/pyme/errors.py | 1 +
+ pyme/pyme/util.py | 1 +
+ pyme/pyme/version.py | 2 ++
+ 30 files changed, 39 insertions(+), 4 deletions(-)
+
+commit b3b3712645332c5bc3e8d9d557aab21d48ff0f86
+Author: belyi
+Date: Sun Mar 21 02:07:36 2004 +0000
+
+ Added Id: RCS tags to all files.
+
+ pyme-web/Makefile | 2 ++
+ pyme-web/index.html | 3 ++-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+commit 6aea2426beaaa8c43e6f2310a37a2737c0c3a1b5
+Author: belyi
+Date: Sun Mar 21 01:50:55 2004 +0000
+
+ Update example on the init pyme.html page to match simple.py example.
+ Fix core.py to use getcode() instead of getvalue() method of the exception.
+
+ pyme/pyme/__init__.py | 22 ++++++++++++++--------
+ pyme/pyme/core.py | 4 ++--
+ 2 files changed, 16 insertions(+), 10 deletions(-)
+
+commit dee337455ffd624d3f83e1c159c4bb2cefc692c9
+Author: belyi
+Date: Sat Mar 20 20:32:29 2004 +0000
+
+ Added Makefile to simplify publishing web files.
+
+ pyme-web/Makefile | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+commit af7129baa8260697d85c2ddb434562e8a80b62d8
+Author: belyi
+Date: Sat Mar 20 20:15:53 2004 +0000
+
+ Added minimum of formating and SF icon.
+
+ pyme-web/index.html | 18 +++++++++++-------
+ 1 file changed, 11 insertions(+), 7 deletions(-)
+
+commit 2e64dcbf99cee796b51667b04d8961e390edde87
+Author: belyi
+Date: Sat Mar 20 18:30:09 2004 +0000
+
+ Initial revision
+
+ pyme-web/index.html | 33 +++++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+commit 1c51644b3d0b6611422d971758e35f303d2ad5df
+Author: belyi
+Date: Sat Mar 20 05:10:46 2004 +0000
+
+ Update examples and package information on the initial pyme doc page.
+
+ pyme/pyme/__init__.py | 27 ++++++++++++---------------
+ 1 file changed, 12 insertions(+), 15 deletions(-)
+
+commit b2d31b0bfbffdff5247d6db4e3c95140cc1b1f19
+Author: belyi
+Date: Sat Mar 20 04:47:42 2004 +0000
+
+ Deleted unnecessary files.
+ Updated debian/control to remove dependency on python-xml package since there's
+ none now.
+ Move example files from 'doc' into separate control file.
+ Update debian/rules to build documentation from *.py files and to exclude
+ CVS directories from the installation.
+
+ pyme/Makefile | 26 ++-----
+ pyme/debian/control | 8 +--
+ pyme/debian/docs | 1 -
+ pyme/debian/ex.package.doc-base | 22 ------
+ pyme/debian/examples | 1 +
+ pyme/debian/manpage.1.ex | 60 ----------------
+ pyme/debian/manpage.sgml.ex | 152 ----------------------------------------
+ pyme/debian/rules | 12 ++--
+ 8 files changed, 15 insertions(+), 267 deletions(-)
+
+commit 1b517dd9b82a433499b4696b06d94d756cd36e53
+Author: belyi
+Date: Sat Mar 20 02:59:15 2004 +0000
+
+ Remove doc/gpgme directory containing GPGME documentation since this belongs
+ to a different project. Need to add reference in our documentation.
+
+ pyme/doc/gpgme/fdl.texi | 402 ------
+ pyme/doc/gpgme/gpgme.texi | 3372 -------------------------------------------
+ pyme/doc/gpgme/gpl.texi | 397 -----
+ pyme/doc/gpgme/version.texi | 4 -
+ 4 files changed, 4175 deletions(-)
+
+commit 95d7d171da115a0fedfe2a4a7e5acc8aa408f673
+Author: belyi
+Date: Sat Mar 20 02:45:03 2004 +0000
+
+ Change debian/rules to generate files by swig during build and to cleanup
+ those files on 'clean' rule.
+ Plus, leave generated gpgme_wrap.c in the root directory instead of moving
+ it into subdirectory 'generated'.
+
+ pyme/Makefile | 8 +++-----
+ pyme/debian/rules | 3 ++-
+ pyme/setup.py | 2 +-
+ 3 files changed, 6 insertions(+), 7 deletions(-)
+
+commit 545b3d90d445c5c78e8d72b2c1780863e02c789a
+Author: belyi
+Date: Sat Mar 20 02:18:01 2004 +0000
+
+ Initial revision
+
+ pyme/COPYING | 340 ++++
+ pyme/ChangeLog | 802 ++++++++
+ pyme/Makefile | 79 +
+ pyme/debian/README.Debian | 6 +
+ pyme/debian/changelog | 19 +
+ pyme/debian/control | 68 +
+ pyme/debian/copyright | 27 +
+ pyme/debian/dirs | 2 +
+ pyme/debian/docs | 2 +
+ pyme/debian/ex.package.doc-base | 22 +
+ pyme/debian/manpage.1.ex | 60 +
+ pyme/debian/manpage.sgml.ex | 152 ++
+ pyme/debian/postinst.ex | 48 +
+ pyme/debian/postrm.ex | 38 +
+ pyme/debian/preinst.ex | 44 +
+ pyme/debian/prerm.ex | 39 +
+ pyme/debian/rules | 130 ++
+ pyme/debian/setup.cfg-2.2 | 8 +
+ pyme/debian/setup.cfg-2.3 | 8 +
+ pyme/doc/gpgme/fdl.texi | 402 ++++
+ pyme/doc/gpgme/gpgme.texi | 3372 +++++++++++++++++++++++++++++++
+ pyme/doc/gpgme/gpl.texi | 397 ++++
+ pyme/doc/gpgme/version.texi | 4 +
+ pyme/examples/encrypt-to-all.py | 63 +
+ pyme/examples/genkey.py | 55 +
+ pyme/examples/sign.py | 28 +
+ pyme/examples/simple.py | 44 +
+ pyme/gpgme.i | 191 ++
+ pyme/helpers.c | 139 ++
+ pyme/helpers.h | 29 +
+ pyme/pyme/__init__.py | 134 ++
+ pyme/pyme/aux.py | 55 +
+ pyme/pyme/callbacks.py | 45 +
+ pyme/pyme/constants/__init__.py | 2 +
+ pyme/pyme/constants/data/__init__.py | 2 +
+ pyme/pyme/constants/data/encoding.py | 19 +
+ pyme/pyme/constants/event.py | 19 +
+ pyme/pyme/constants/import.py | 19 +
+ pyme/pyme/constants/keylist/__init__.py | 2 +
+ pyme/pyme/constants/keylist/mode.py | 19 +
+ pyme/pyme/constants/md.py | 19 +
+ pyme/pyme/constants/pk.py | 19 +
+ pyme/pyme/constants/protocol.py | 19 +
+ pyme/pyme/constants/sig/__init__.py | 2 +
+ pyme/pyme/constants/sig/mode.py | 19 +
+ pyme/pyme/constants/sigsum.py | 19 +
+ pyme/pyme/constants/status.py | 19 +
+ pyme/pyme/constants/validity.py | 19 +
+ pyme/pyme/core.py | 367 ++++
+ pyme/pyme/errors.py | 46 +
+ pyme/pyme/util.py | 61 +
+ pyme/pyme/version.py | 39 +
+ pyme/setup.py | 60 +
+ 53 files changed, 7642 insertions(+)
+
+commit a3d5a442dc713b6c4d6fc4134db5b47e379dc41d
+Author: root
+Date: Fri Mar 19 14:12:30 2004 +0000
+
+ initial checkin
+
+ CVSROOT/checkoutlist | 13 +++++++++++++
+ CVSROOT/commitinfo | 15 +++++++++++++++
+ CVSROOT/config | 21 +++++++++++++++++++++
+ CVSROOT/cvswrappers | 19 +++++++++++++++++++
+ CVSROOT/editinfo | 21 +++++++++++++++++++++
+ CVSROOT/loginfo | 26 ++++++++++++++++++++++++++
+ CVSROOT/modules | 26 ++++++++++++++++++++++++++
+ CVSROOT/notify | 12 ++++++++++++
+ CVSROOT/rcsinfo | 13 +++++++++++++
+ CVSROOT/taginfo | 20 ++++++++++++++++++++
+ CVSROOT/verifymsg | 21 +++++++++++++++++++++
+ 11 files changed, 207 insertions(+)
diff --git a/lang/python/doc/rst/gpgme-python-howto.rst b/lang/python/doc/rst/gpgme-python-howto.rst
new file mode 100644
index 0000000..9181491
--- /dev/null
+++ b/lang/python/doc/rst/gpgme-python-howto.rst
@@ -0,0 +1,2998 @@
+.. _intro:
+
+Introduction
+============
+
++-----------------------------------+-----------------------------------+
+| Version: | 0.1.4 |
++-----------------------------------+-----------------------------------+
+| GPGME Version: | 1.12.0 |
++-----------------------------------+-----------------------------------+
+| Author: | `Ben |
+| | McGinnes `__ |
+| | |
++-----------------------------------+-----------------------------------+
+| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E237 |
+| | 3590E5D |
++-----------------------------------+-----------------------------------+
+| Language: | Australian English, British |
+| | English |
++-----------------------------------+-----------------------------------+
+| xml:lang: | en-AU, en-GB, en |
++-----------------------------------+-----------------------------------+
+
+This document provides basic instruction in how to use the GPGME Python
+bindings to programmatically leverage the GPGME library.
+
+.. _py2-vs-py3:
+
+Python 2 versus Python 3
+------------------------
+
+Though the GPGME Python bindings themselves provide support for both
+Python 2 and 3, the focus is unequivocally on Python 3 and specifically
+from Python 3.4 and above. As a consequence all the examples and
+instructions in this guide use Python 3 code.
+
+Much of it will work with Python 2, but much of it also deals with
+Python 3 byte literals, particularly when reading and writing data.
+Developers concentrating on Python 2.7, and possibly even 2.6, will need
+to make the appropriate modifications to support the older string and
+unicode types as opposed to bytes.
+
+There are multiple reasons for concentrating on Python 3; some of which
+relate to the immediate integration of these bindings, some of which
+relate to longer term plans for both GPGME and the python bindings and
+some of which relate to the impending EOL period for Python 2.7.
+Essentially, though, there is little value in tying the bindings to a
+version of the language which is a dead end and the advantages offered
+by Python 3 over Python 2 make handling the data types with which GPGME
+deals considerably easier.
+
+.. _howto-python3-examples:
+
+Examples
+--------
+
+All of the examples found in this document can be found as Python 3
+scripts in the ``lang/python/examples/howto`` directory.
+
+Unofficial Drafts
+-----------------
+
+In addition to shipping with each release of GPGME, there is a section
+on locations to read or download `draft editions <#draft-editions>`__ of
+this document from at the end of it. These are unofficial versions
+produced in between major releases.
+
+.. _new-stuff:
+
+What\'s New
+-----------
+
+The most obviously new point for those reading this guide is this
+section on other new things, but that\'s hardly important. Not given all
+the other things which spurred the need for adding this section and its
+subsections.
+
+.. _new-stuff-1-12-0:
+
+New in GPGME 1·12·0
+~~~~~~~~~~~~~~~~~~~
+
+There have been quite a number of additions to GPGME and the Python
+bindings to it since the last release of GPGME with versions 1.11.0 and
+1.11.1 in April, 2018.
+
+The bullet points of new additiions are:
+
+- an expanded section on `installing <#installation>`__ and
+ `troubleshooting <#snafu>`__ the Python bindings.
+- The release of Python 3.7.0; which appears to be working just fine
+ with our bindings, in spite of intermittent reports of problems for
+ many other Python projects with that new release.
+- Python 3.7 has been moved to the head of the specified python
+ versions list in the build process.
+- In order to fix some other issues, there are certain underlying
+ functions which are more exposed through the
+ `gpg.Context() <#howto-get-context>`__, but ongoing documentation
+ ought to clarify that or otherwise provide the best means of using
+ the bindings. Some additions to ``gpg.core`` and the ``Context()``,
+ however, were intended (see below).
+- Continuing work in identifying and confirming the cause of
+ oft-reported `problems installing the Python bindings on
+ Windows <#snafu-runtime-not-funtime>`__.
+- GSOC: Google\'s Surreptitiously Ordered Conscription ... erm ... oh,
+ right; Google\'s Summer of Code. Though there were two hopeful
+ candidates this year; only one ended up involved with the GnuPG
+ Project directly, the other concentrated on an unrelated third party
+ project with closer ties to one of the GNU/Linux distributions than
+ to the GnuPG Project. Thus the Python bindings benefited from GSOC
+ participant Jacob Adams, who added the key\ :sub:`import` function;
+ building on prior work by Tobias Mueller.
+- Several new methods functions were added to the gpg.Context(),
+ including: `key\ import <#howto-import-key>`__,
+ `key\ export <#howto-export-key>`__,
+ `key\ exportminimal <#howto-export-public-key>`__ and
+ `key\ exportsecret <#howto-export-secret-key>`__.
+- Importing and exporting examples include versions integrated with
+ Marcel Fest\'s recently released `HKP for
+ Python `__ module. Some
+ `additional notes on this module <#hkp4py>`__ are included at the end
+ of the HOWTO.
+- Instructions for dealing with semi-walled garden implementations like
+ ProtonMail are also included. This is intended to make things a
+ little easier when communicating with users of ProtonMail\'s services
+ and should not be construed as an endorsement of said service. The
+ GnuPG Project neither favours, nor disfavours ProtonMail and the
+ majority of this deals with interacting with the ProtonMail
+ keyserver.
+- Semi-formalised the location where `draft
+ versions <#draft-editions>`__ of this HOWTO may periodically be
+ accessible. This is both for the reference of others and testing the
+ publishing of the document itself. Renamed this file at around the
+ same time.
+- The Texinfo documentation build configuration has been replicated
+ from the parent project in order to make to maintain consistency with
+ that project (and actually ship with each release).
+- a reStructuredText (``.rst``) version is also generated for Python
+ developers more used to and comfortable with that format as it is the
+ standard Python documentation format and Python developers may wish
+ to use it with Sphinx. Please note that there has been no testing of
+ the reStructuredText version with Sphinx at all. The reST file was
+ generated by the simple expedient of using
+ `Pandoc `__.
+- Added a new section for `advanced or experimental
+ use <#advanced-use>`__.
+- Began the advanced use cases with `a section <#cython>`__ on using
+ the module with `Cython `__.
+- Added a number of new scripts to the ``example/howto/`` directory;
+ some of which may be in advance of their planned sections of the
+ HOWTO (and some are just there because it seemed like a good idea at
+ the time).
+- Cleaned up a lot of things under the hood.
+
+GPGME Concepts
+==============
+
+.. _gpgme-c-api:
+
+A C API
+-------
+
+Unlike many modern APIs with which programmers will be more familiar
+with these days, the GPGME API is a C API. The API is intended for use
+by C coders who would be able to access its features by including the
+``gpgme.h`` header file with their own C source code and then access its
+functions just as they would any other C headers.
+
+This is a very effective method of gaining complete access to the API
+and in the most efficient manner possible. It does, however, have the
+drawback that it cannot be directly used by other languages without some
+means of providing an interface to those languages. This is where the
+need for bindings in various languages stems.
+
+.. _gpgme-python-bindings:
+
+Python bindings
+---------------
+
+The Python bindings for GPGME provide a higher level means of accessing
+the complete feature set of GPGME itself. It also provides a more
+pythonic means of calling these API functions.
+
+The bindings are generated dynamically with SWIG and the copy of
+``gpgme.h`` generated when GPGME is compiled.
+
+This means that a version of the Python bindings is fundamentally tied
+to the exact same version of GPGME used to generate that copy of
+``gpgme.h``.
+
+.. _gpgme-python-bindings-diffs:
+
+Difference between the Python bindings and other GnuPG Python packages
+----------------------------------------------------------------------
+
+There have been numerous attempts to add GnuPG support to Python over
+the years. Some of the most well known are listed here, along with what
+differentiates them.
+
+.. _diffs-python-gnupg:
+
+The python-gnupg package maintained by Vinay Sajip
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is arguably the most popular means of integrating GPG with Python.
+The package utilises the ``subprocess`` module to implement wrappers for
+the ``gpg`` and ``gpg2`` executables normally invoked on the command
+line (``gpg.exe`` and ``gpg2.exe`` on Windows).
+
+The popularity of this package stemmed from its ease of use and
+capability in providing the most commonly required features.
+
+Unfortunately it has been beset by a number of security issues in the
+past; most of which stemmed from using unsafe methods of accessing the
+command line via the ``subprocess`` calls. While some effort has been
+made over the last two to three years (as of 2018) to mitigate this,
+particularly by no longer providing shell access through those
+subprocess calls, the wrapper is still somewhat limited in the scope of
+its GnuPG features coverage.
+
+The python-gnupg package is available under the MIT license.
+
+.. _diffs-isis-gnupg:
+
+The gnupg package created and maintained by Isis Lovecruft
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In 2015 Isis Lovecruft from the Tor Project forked and then
+re-implemented the python-gnupg package as just gnupg. This new package
+also relied on subprocess to call the ``gpg`` or ``gpg2`` binaries, but
+did so somewhat more securely.
+
+The naming and version numbering selected for this package, however,
+resulted in conflicts with the original python-gnupg and since its
+functions were called in a different manner to python-gnupg, the release
+of this package also resulted in a great deal of consternation when
+people installed what they thought was an upgrade that subsequently
+broke the code relying on it.
+
+The gnupg package is available under the GNU General Public License
+version 3.0 (or any later version).
+
+.. _diffs-pyme:
+
+The PyME package maintained by Martin Albrecht
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This package is the origin of these bindings, though they are somewhat
+different now. For details of when and how the PyME package was folded
+back into GPGME itself see the `Short History `__
+document. [1]_
+
+The PyME package was first released in 2002 and was also the first
+attempt to implement a low level binding to GPGME. In doing so it
+provided access to considerably more functionality than either the
+``python-gnupg`` or ``gnupg`` packages.
+
+The PyME package is only available for Python 2.6 and 2.7.
+
+Porting the PyME package to Python 3.4 in 2015 is what resulted in it
+being folded into the GPGME project and the current bindings are the end
+result of that effort.
+
+The PyME package is available under the same dual licensing as GPGME
+itself: the GNU General Public License version 2.0 (or any later
+version) and the GNU Lesser General Public License version 2.1 (or any
+later version).
+
+.. _gpgme-python-install:
+
+GPGME Python bindings installation
+==================================
+
+.. _do-not-use-pypi:
+
+No PyPI
+-------
+
+Most third-party Python packages and modules are available and
+distributed through the Python Package Installer, known as PyPI.
+
+Due to the nature of what these bindings are and how they work, it is
+infeasible to install the GPGME Python bindings in the same way.
+
+This is because the bindings use SWIG to dynamically generate C bindings
+against ``gpgme.h`` and ``gpgme.h`` is generated from ``gpgme.h.in`` at
+compile time when GPGME is built from source. Thus to include a package
+in PyPI which actually built correctly would require either statically
+built libraries for every architecture bundled with it or a full
+implementation of C for each architecture.
+
+See the additional notes regarding `CFFI and SWIG <#snafu-cffi>`__ at
+the end of this section for further details.
+
+.. _gpgme-python-requirements:
+
+Requirements
+------------
+
+The GPGME Python bindings only have three requirements:
+
+#. A suitable version of Python 2 or Python 3. With Python 2 that means
+ CPython 2.7 and with Python 3 that means CPython 3.4 or higher.
+#. `SWIG `__.
+#. GPGME itself. Which also means that all of GPGME\'s dependencies must
+ be installed too.
+
+.. _gpgme-python-recommendations:
+
+Recommended Additions
+~~~~~~~~~~~~~~~~~~~~~
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings. In some cases these
+recommendations refer to which version(s) of CPython to use the bindings
+with, while others refer to third party modules which provide a
+significant advantage in some way.
+
+#. If possible, use Python 3 instead of 2.
+#. Favour a more recent version of Python since even 3.4 is due to reach
+ EOL soon. In production systems and services, Python 3.6 should be
+ robust enough to be relied on.
+#. If possible add the following Python modules which are not part of
+ the standard library:
+ `Requests `__,
+ `Cython `__ and
+ `hkp4py `__. Chances are quite
+ high that at least the first one and maybe two of those will already
+ be installed.
+
+Note that, as with Cython, some of the planned additions to the
+`Advanced <#advanced-use>`__ section, will bring with them additional
+requirements. Most of these will be fairly well known and commonly
+installed ones, however, which are in many cases likely to have already
+been installed on many systems or be familiar to Python programmers.
+
+Installation
+------------
+
+Installing the Python bindings is effectively achieved by compiling and
+installing GPGME itself.
+
+Once SWIG is installed with Python and all the dependencies for GPGME
+are installed you only need to confirm that the version(s) of Python you
+want the bindings installed for are in your ``$PATH``.
+
+By default GPGME will attempt to install the bindings for the most
+recent or highest version number of Python 2 and Python 3 it detects in
+``$PATH``. It specifically checks for the ``python`` and ``python3``
+executables first and then checks for specific version numbers.
+
+For Python 2 it checks for these executables in this order: ``python``,
+``python2`` and ``python2.7``.
+
+For Python 3 it checks for these executables in this order: ``python3``,
+``python3.7``, ``python3.6``, ``python3.5`` and ``python3.4``. [2]_
+
+On systems where ``python`` is actually ``python3`` and not ``python2``
+it may be possible that ``python2`` may be overlooked, but there have
+been no reports of that actually occurring as yet.
+
+In the three months or so since the release of Python 3.7.0 there has
+been extensive testing and work with these bindings with no issues
+specifically relating to the new version of Python or any of the new
+features of either the language or the bindings. This has also been the
+case with Python 3.7.1rc1. With that in mind and given the release of
+Python 3.7.1 is scheduled for around the same time as GPGME 1.12.0, the
+order of preferred Python versions has been changed to move Python 3.7
+ahead of Python 3.6.
+
+.. _install-gpgme:
+
+Installing GPGME
+~~~~~~~~~~~~~~~~
+
+See the GPGME ``README`` file for details of how to install GPGME from
+source.
+
+.. _snafu:
+
+Known Issues
+------------
+
+There are a few known issues with the current build process and the
+Python bindings. For the most part these are easily addressed should
+they be encountered.
+
+.. _snafu-a-swig-of-this-builds-character:
+
+Breaking Builds
+~~~~~~~~~~~~~~~
+
+Occasionally when installing GPGME with the Python bindings included it
+may be observed that the ``make`` portion of that process induces a
+large very number of warnings and, eventually errors which end that part
+of the build process. Yet following that with ``make check`` and
+``make install`` appears to work seamlessly.
+
+The cause of this is related to the way SWIG needs to be called to
+dynamically generate the C bindings for GPGME in the first place. So the
+entire process will always produce ``lang/python/python2-gpg/`` and
+``lang/python/python3-gpg/`` directories. These should contain the build
+output generated during compilation, including the complete bindings and
+module installed into ``site-packages``.
+
+Occasionally the errors in the early part or some other conflict (e.g.
+not installing as **root** or **su**) may result in nothing being
+installed to the relevant ``site-packages`` directory and the build
+directory missing a lot of expected files. Even when this occurs, the
+solution is actually quite simple and will always work.
+
+That solution is simply to run the following commands as either the
+**root** user or prepended with ``sudo -H``\ [3]_ in the
+``lang/python/`` directory:
+
+.. code:: shell
+
+ /path/to/pythonX.Y setup.py build
+ /path/to/pythonX.Y setup.py build
+ /path/to/pythonX.Y setup.py install
+
+Yes, the build command does need to be run twice. Yes, you still need to
+run the potentially failing or incomplete steps during the
+``configure``, ``make`` and ``make install`` steps with installing
+GPGME. This is because those steps generate a lot of essential files
+needed, both by and in order to create, the bindings (including both the
+``setup.py`` and ``gpgme.h`` files).
+
+#. IMPORTANT Note
+
+ If specifying a selected number of languages to create bindings for,
+ try to leave Python last. Currently the majority of the other
+ language bindings are also preceding Python of either version when
+ listed alphabetically and so that just happens by default currently.
+
+ If Python is set to precede one of the other languages then it is
+ possible that the errors described here may interrupt the build
+ process before generating bindings for those other languages. In
+ these cases it may be preferable to configure all preferred language
+ bindings separately with alternative ``configure`` steps for GPGME
+ using the ``--enable-languages=$LANGUAGE`` option.
+
+.. _snafu-lessons-for-the-lazy:
+
+Reinstalling Responsibly
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regardless of whether you\'re installing for one version of Python or
+several, there will come a point where reinstallation is required. With
+most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about. Then the
+module is upgraded, the new files are copied over the old and that\'s
+the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant ``site-packages`` directory for the version of Python in
+question and removing the ``gpg/`` directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on the
+system, but the same is true of installing the module in the first
+place.
+
+.. _snafu-the-full-monty:
+
+Multiple installations
+~~~~~~~~~~~~~~~~~~~~~~
+
+For a veriety of reasons it may be either necessary or just preferable
+to install the bindings to alternative installed Python versions which
+meet the requirements of these bindings.
+
+On POSIX systems this will generally be most simply achieved by running
+the manual installation commands (build, build, install) as described in
+the previous section for each Python installation the bindings need to
+be installed to.
+
+As per the SWIG documentation: the compilers, libraries and runtime used
+to build GPGME and the Python Bindings **must** match those used to
+compile Python itself, including the version number(s) (at least going
+by major version numbers and probably minor numbers too).
+
+On most POSIX systems, including OS X, this will very likely be the case
+in most, if not all, cases.
+
+.. _snafu-runtime-not-funtime:
+
+Won\'t Work With Windows
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are semi-regular reports of Windows users having considerable
+difficulty in installing and using the Python bindings at all. Very
+often, possibly even always, these reports come from Cygwin users and/or
+MinGW users and/or Msys2 users. Though not all of them have been
+confirmed, it appears that these reports have also come from people who
+installed Python using the Windows installer files from the `Python
+website `__ (i.e. mostly MSI installers, sometimes
+self-extracting ``.exe`` files).
+
+The Windows versions of Python are not built using Cygwin, MinGW or
+Msys2; they\'re built using Microsoft Visual Studio. Furthermore the
+version used is *considerably* more advanced than the version which
+MinGW obtained a small number of files from many years ago in order to
+be able to compile anything at all. Not only that, but there are changes
+to the version of Visual Studio between some micro releases, though that
+is is particularly the case with Python 2.7, since it has been kept
+around far longer than it should have been.
+
+There are two theoretical solutions to this issue:
+
+#. Compile and install the GnuPG stack, including GPGME and the Python
+ bibdings using the same version of Microsoft Visual Studio used by
+ the Python Foundation to compile the version of Python installed.
+
+ If there are multiple versions of Python then this will need to be
+ done with each different version of Visual Studio used.
+
+#. Compile and install Python using the same tools used by choice, such
+ as MinGW or Msys2.
+
+Do **not** use the official Windows installer for Python unless
+following the first method.
+
+In this type of situation it may even be for the best to accept that
+there are less limitations on permissive software than free software and
+simply opt to use a recent version of the Community Edition of Microsoft
+Visual Studio to compile and build all of it, no matter what.
+
+Investigations into the extent or the limitations of this issue are
+ongoing.
+
+.. _snafu-cffi:
+
+CFFI is the Best⢠and GPGME should use it instead of SWIG
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are many reasons for favouring
+`CFFI `__ and
+proponents of it are quite happy to repeat these things as if all it
+would take to switch from SWIG to CFFI is repeating that list as if it
+were a new concept.
+
+The fact is that there are things which Python\'s CFFI implementation
+cannot handle in the GPGME C code. Beyond that there are features of
+SWIG which are simply not available with CFFI at all. SWIG generates the
+bindings to Python using the ``gpgme.h`` file, but that file is not a
+single version shipped with each release, it too is generated when GPGME
+is compiled.
+
+CFFI is currently unable to adapt to such a potentially mutable
+codebase. If there were some means of applying SWIG\'s dynamic code
+generation to produce the Python/CFFI API modes of accessing the GPGME
+libraries (or the source source code directly), but such a thing does
+not exist yet either and it currently appears that work is needed in at
+least one of CFFI\'s dependencies before any of this can be addressed.
+
+So if you\'re a massive fan of CFFI; that\'s great, but if you want this
+project to switch to CFFI then rather than just insisting that it
+should, I\'d suggest you volunteer to bring CFFI up to the level this
+project needs.
+
+If you\'re actually seriously considering doing so, then I\'d suggest
+taking the ``gpgme-tool.c`` file in the GPGME ``src/`` directory and
+getting that to work with any of the CFFI API methods (not the ABI
+methods, they\'ll work with pretty much anything). When you start
+running into trouble with \"ifdefs\" then you\'ll know what sort of
+things are lacking. That doesn\'t even take into account the amount of
+work saved via SWIG\'s code generation techniques either.
+
+.. _snafu-venv:
+
+Virtualised Environments
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is fairly common practice amongst Python developers to, as much as
+possible, use packages like virtualenv to keep various things that are
+to be installed from interfering with each other. Given how much of the
+GPGME bindings is often at odds with the usual pythonic way of doing
+things, it stands to reason that this would be called into question too.
+
+As it happens the answer as to whether or not the bindings can be used
+with virtualenv, the answer is both yes and no.
+
+In general we recommend installing to the relevant path and matching
+prefix of GPGME itself. Which means that when GPGME, and ideally the
+rest of the GnuPG stack, is installed to a prefix like ``/usr/local`` or
+``/opt/local`` then the bindings would need to be installed to the main
+Python installation and not a virtualised abstraction. Attempts to
+separate the two in the past have been known to cause weird and
+intermittent errors ranging from minor annoyances to complete failures
+in the build process.
+
+As a consequence we only recommend building with and installing to the
+main Python installations within the same prefix as GPGME is installed
+to or which are found by GPGME\'s configuration stage immediately prior
+to running the make commands. Which is exactly what the compiling and
+installing process of GPGME does by default.
+
+Once that is done, however, it appears that a copy the compiled module
+may be installed into a virtualenv of the same major and minor version
+matching the build. Alternatively it is possible to utilise a
+``sites.pth`` file in the ``site-packages/`` directory of a viertualenv
+installation, which links back to the system installations corresponding
+directory in order to import anything installed system wide. This may or
+may not be appropriate on a case by case basis.
+
+Though extensive testing of either of these options is not yet complete,
+preliminary testing of them indicates that both are viable as long as
+the main installation is complete. Which means that certain other
+options normally restricted to virtual environments are also available,
+including integration with pythonic test suites (e.g.
+`pytest `__) and other
+large projects.
+
+That said, it is worth reiterating the warning regarding non-standard
+installations. If one were to attempt to install the bindings only to a
+virtual environment without somehow also including the full GnuPG stack
+(or enough of it as to include GPGME) then it is highly likely that
+errors would be encountered at some point and more than a little likely
+that the build process itself would break.
+
+If a degree of separation from the main operating system is still
+required in spite of these warnings, then consider other forms of
+virtualisation. Either a virtual machine (e.g.
+`VirtualBox `__), a hardware emulation
+layer (e.g. `QEMU `__) or an application
+container (e.g. `Docker `__).
+
+Finally it should be noted that the limited tests conducted thus far
+have been using the ``virtualenv`` command in a new directory to create
+the virtual python environment. As opposed to the standard ``python3
+-m venv`` and it is possible that this will make a difference depending
+on the system and version of Python in use. Another option is to run the
+command ``python3 -m virtualenv /path/to/install/virtual/thingy``
+instead.
+
+.. _howto-fund-a-mental:
+
+Fundamentals
+============
+
+Before we can get to the fun stuff, there are a few matters regarding
+GPGME\'s design which hold true whether you\'re dealing with the C code
+directly or these Python bindings.
+
+.. _no-rest-for-the-wicked:
+
+No REST
+-------
+
+The first part of which is or will be fairly blatantly obvious upon
+viewing the first example, but it\'s worth reiterating anyway. That
+being that this API is **not** a REST API. Nor indeed could it ever be
+one.
+
+Most, if not all, Python programmers (and not just Python programmers)
+know how easy it is to work with a RESTful API. In fact they\'ve become
+so popular that many other APIs attempt to emulate REST-like behaviour
+as much as they are able. Right down to the use of JSON formatted output
+to facilitate the use of their API without having to retrain developers.
+
+This API does not do that. It would not be able to do that and also
+provide access to the entire C API on which it\'s built. It does,
+however, provide a very pythonic interface on top of the direct bindings
+and it\'s this pythonic layer that this HOWTO deals with.
+
+.. _howto-get-context:
+
+Context
+-------
+
+One of the reasons which prevents this API from being RESTful is that
+most operations require more than one instruction to the API to perform
+the task. Sure, there are certain functions which can be performed
+simultaneously, particularly if the result known or strongly anticipated
+(e.g. selecting and encrypting to a key known to be in the public
+keybox).
+
+There are many more, however, which cannot be manipulated so readily:
+they must be performed in a specific sequence and the result of one
+operation has a direct bearing on the outcome of subsequent operations.
+Not merely by generating an error either.
+
+When dealing with this type of persistent state on the web, full of both
+the RESTful and REST-like, it\'s most commonly referred to as a session.
+In GPGME, however, it is called a context and every operation type has
+one.
+
+.. _howto-keys:
+
+Working with keys
+=================
+
+.. _howto-keys-selection:
+
+Key selection
+-------------
+
+Selecting keys to encrypt to or to sign with will be a common occurrence
+when working with GPGMe and the means available for doing so are quite
+simple.
+
+They do depend on utilising a Context; however once the data is recorded
+in another variable, that Context does not need to be the same one which
+subsequent operations are performed.
+
+The easiest way to select a specific key is by searching for that key\'s
+key ID or fingerprint, preferably the full fingerprint without any
+spaces in it. A long key ID will probably be okay, but is not advised
+and short key IDs are already a problem with some being generated to
+match specific patterns. It does not matter whether the pattern is upper
+or lower case.
+
+So this is the best method:
+
+.. code:: python
+
+ import gpg
+
+ k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+ keys = list(k)
+
+This is passable and very likely to be common:
+
+.. code:: python
+
+ import gpg
+
+ k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+ keys = list(k)
+
+And this is a really bad idea:
+
+.. code:: python
+
+ import gpg
+
+ k = gpg.Context().keylist(pattern="0xDEADBEEF")
+ keys = list(k)
+
+Alternatively it may be that the intention is to create a list of keys
+which all match a particular search string. For instance all the
+addresses at a particular domain, like this:
+
+.. code:: python
+
+ import gpg
+
+ ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+ nsa = list(ncsc)
+
+.. _howto-keys-counting:
+
+Counting keys
+~~~~~~~~~~~~~
+
+Counting the number of keys in your public keybox (``pubring.kbx``), the
+format which has superseded the old keyring format (``pubring.gpg`` and
+``secring.gpg``), or the number of secret keys is a very simple task.
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+ seckeys = c.keylist(pattern=None, secret=True)
+ pubkeys = c.keylist(pattern=None, secret=False)
+
+ seclist = list(seckeys)
+ secnum = len(seclist)
+
+ publist = list(pubkeys)
+ pubnum = len(publist)
+
+ print("""
+ Number of secret keys: {0}
+ Number of public keys: {1}
+ """.format(secnum, pubnum))
+
+NOTE: The `Cython <#cython>`__ introduction in the `Advanced and
+Experimental <#advanced-use>`__ section uses this same key counting code
+with Cython to demonstrate some areas where Cython can improve
+performance even with the bindings. Users with large public keyrings or
+keyboxes, for instance, should consider these options if they are
+comfortable with using Cython.
+
+.. _howto-get-key:
+
+Get key
+-------
+
+An alternative method of getting a single key via its fingerprint is
+available directly within a Context with ``Context().get_key``. This is
+the preferred method of selecting a key in order to modify it, sign or
+certify it and for obtaining relevant data about a single key as a part
+of other functions; when verifying a signature made by that key, for
+instance.
+
+By default this method will select public keys, but it can select secret
+keys as well.
+
+This first example demonstrates selecting the current key of Werner
+Koch, which is due to expire at the end of 2018:
+
+.. code:: python
+
+ import gpg
+
+ fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+ key = gpg.Context().get_key(fingerprint)
+
+Whereas this example demonstrates selecting the author\'s current key
+with the ``secret`` key word argument set to ``True``:
+
+.. code:: python
+
+ import gpg
+
+ fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+ key = gpg.Context().get_key(fingerprint, secret=True)
+
+It is, of course, quite possible to select expired, disabled and revoked
+keys with this function, but only to effectively display information
+about those keys.
+
+It is also possible to use both unicode or string literals and byte
+literals with the fingerprint when getting a key in this way.
+
+.. _howto-import-key:
+
+Importing keys
+--------------
+
+Importing keys is possible with the ``key_import()`` method and takes
+one argument which is a bytes literal object containing either the
+binary or ASCII armoured key data for one or more keys.
+
+The following example retrieves one or more keys from the SKS keyservers
+via the web using the requests module. Since requests returns the
+content as a bytes literal object, we can then use that directly to
+import the resulting data into our keybox.
+
+.. code:: python
+
+ import gpg
+ import os.path
+ import requests
+
+ c = gpg.Context()
+ url = "https://sks-keyservers.net/pks/lookup"
+ pattern = input("Enter the pattern to search for key or user IDs: ")
+ payload = {"op": "get", "search": pattern}
+
+ r = requests.get(url, verify=True, params=payload)
+ result = c.key_import(r.content)
+
+ if result is not None and hasattr(result, "considered") is False:
+ print(result)
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ Number of keys revoked: {1}
+ Number of new signatures: {2}
+ Number of new subkeys: {3}
+ Number of new user IDs: {4}
+ Number of new secret keys: {5}
+ Number of unchanged keys: {6}
+
+ The key IDs for all considered keys were:
+ """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print("{0}\n".format(result.imports[i].fpr))
+ else:
+ pass
+
+NOTE: When searching for a key ID of any length or a fingerprint
+(without spaces), the SKS servers require the the leading ``0x``
+indicative of hexadecimal be included. Also note that the old short key
+IDs (e.g. ``0xDEADBEEF``) should no longer be used due to the relative
+ease by which such key IDs can be reproduced, as demonstrated by the
+Evil32 Project in 2014 (which was subsequently exploited in 2016).
+
+.. _import-protonmail:
+
+Working with ProtonMail
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+.. code:: python
+
+ import gpg
+ import requests
+ import sys
+
+ print("""
+ This script searches the ProtonMail key server for the specified key and
+ imports it.
+ """)
+
+ c = gpg.Context(armor=True)
+ url = "https://api.protonmail.ch/pks/lookup"
+ ksearch = []
+
+ if len(sys.argv) >= 2:
+ keyterm = sys.argv[1]
+ else:
+ keyterm = input("Enter the key ID, UID or search string: ")
+
+ if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+ ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+ ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+ ksearch.append("{0}@pm.me".format(keyterm[1:]))
+ elif keyterm.count("@") == 0:
+ ksearch.append("{0}@protonmail.com".format(keyterm))
+ ksearch.append("{0}@protonmail.ch".format(keyterm))
+ ksearch.append("{0}@pm.me".format(keyterm))
+ elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ elif keyterm.count("@") > 2:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ else:
+ ksearch.append(keyterm)
+
+ for k in ksearch:
+ payload = {"op": "get", "search": k}
+ try:
+ r = requests.get(url, verify=True, params=payload)
+ if r.ok is True:
+ result = c.key_import(r.content)
+ elif r.ok is False:
+ result = r.content
+ except Exception as e:
+ result = None
+
+ if result is not None and hasattr(result, "considered") is False:
+ print("{0} for {1}".format(result.decode(), k))
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ With UIDs wholely or partially matching the following string:
+
+ {1}
+
+ Number of keys revoked: {2}
+ Number of new signatures: {3}
+ Number of new subkeys: {4}
+ Number of new user IDs: {5}
+ Number of new secret keys: {6}
+ Number of unchanged keys: {7}
+
+ The key IDs for all considered keys were:
+ """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ elif result is None:
+ print(e)
+
+Both the above example,
+`pmkey-import.py <../examples/howto/pmkey-import.py>`__, and a version
+which prompts for an alternative GnuPG home directory,
+`pmkey-import-alt.py <../examples/howto/pmkey-import-alt.py>`__, are
+available with the other examples and are executable scripts.
+
+Note that while the ProtonMail servers are based on the SKS servers,
+their server is related more to their API and is not feature complete by
+comparison to the servers in the SKS pool. One notable difference being
+that the ProtonMail server does not permit non ProtonMail users to
+update their own keys, which could be a vector for attacking ProtonMail
+users who may not receive a key\'s revocation if it had been
+compromised.
+
+.. _import-hkp4py:
+
+Importing with HKP for Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Performing the same tasks with the `hkp4py
+module `__ (available via PyPI) is
+not too much different, but does provide a number of options of benefit
+to end users. Not least of which being the ability to perform some
+checks on a key before importing it or not. For instance it may be the
+policy of a site or project to only import keys which have not been
+revoked. The hkp4py module permits such checks prior to the importing of
+the keys found.
+
+.. code:: python
+
+ import gpg
+ import hkp4py
+ import sys
+
+ c = gpg.Context()
+ server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+ results = []
+
+ if len(sys.argv) > 2:
+ pattern = " ".join(sys.argv[1:])
+ elif len(sys.argv) == 2:
+ pattern = sys.argv[1]
+ else:
+ pattern = input("Enter the pattern to search for keys or user IDs: ")
+
+ try:
+ keys = server.search(pattern)
+ print("Found {0} key(s).".format(len(keys)))
+ except Exception as e:
+ keys = []
+ for logrus in pattern.split():
+ if logrus.startswith("0x") is True:
+ key = server.search(logrus)
+ else:
+ key = server.search("0x{0}".format(logrus))
+ keys.append(key[0])
+ print("Found {0} key(s).".format(len(keys)))
+
+ for key in keys:
+ import_result = c.key_import(key.key_blob)
+ results.append(import_result)
+
+ for result in results:
+ if result is not None and hasattr(result, "considered") is False:
+ print(result)
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ Number of keys revoked: {1}
+ Number of new signatures: {2}
+ Number of new subkeys: {3}
+ Number of new user IDs: {4}
+ Number of new secret keys: {5}
+ Number of unchanged keys: {6}
+
+ The key IDs for all considered keys were:
+ """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ else:
+ pass
+
+Since the hkp4py module handles multiple keys just as effectively as one
+(``keys`` is a list of responses per matching key), the example above is
+able to do a little bit more with the returned data before anything is
+actually imported.
+
+.. _import-protonmail-hkp4py:
+
+Importing from ProtonMail with HKP for Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Though this can provide certain benefits even when working with
+ProtonMail, the scope is somewhat constrained there due to the
+limitations of the ProtonMail keyserver.
+
+For instance, searching the SKS keyserver pool for the term \"gnupg\"
+produces hundreds of results from any time the word appears in any part
+of a user ID. Performing the same search on the ProtonMail keyserver
+returns zero results, even though there are at least two test accounts
+which include it as part of the username.
+
+The cause of this discrepancy is the deliberate configuration of that
+server by ProtonMail to require an exact match of the full email address
+of the ProtonMail user whose key is being requested. Presumably this is
+intended to reduce breaches of privacy of their users as an email
+address must already be known before a key for that address can be
+obtained.
+
+#. Import from ProtonMail via HKP for Python Example no. 1
+
+ The following script is avalable with the rest of the examples under
+ the somewhat less than original name, ``pmkey-import-hkp.py``.
+
+ .. code:: python
+
+ import gpg
+ import hkp4py
+ import os.path
+ import sys
+
+ print("""
+ This script searches the ProtonMail key server for the specified key and
+ imports it.
+
+ Usage: pmkey-import-hkp.py [search strings]
+ """)
+
+ c = gpg.Context(armor=True)
+ server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+ keyterms = []
+ ksearch = []
+ allkeys = []
+ results = []
+ paradox = []
+ homeless = None
+
+ if len(sys.argv) > 2:
+ keyterms = sys.argv[1:]
+ elif len(sys.argv) == 2:
+ keyterm = sys.argv[1]
+ keyterms.append(keyterm)
+ else:
+ key_term = input("Enter the key ID, UID or search string: ")
+ keyterms = key_term.split()
+
+ for keyterm in keyterms:
+ if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+ ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+ ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+ ksearch.append("{0}@pm.me".format(keyterm[1:]))
+ elif keyterm.count("@") == 0:
+ ksearch.append("{0}@protonmail.com".format(keyterm))
+ ksearch.append("{0}@protonmail.ch".format(keyterm))
+ ksearch.append("{0}@pm.me".format(keyterm))
+ elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ elif keyterm.count("@") > 2:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ else:
+ ksearch.append(keyterm)
+
+ for k in ksearch:
+ print("Checking for key for: {0}".format(k))
+ try:
+ keys = server.search(k)
+ if isinstance(keys, list) is True:
+ for key in keys:
+ allkeys.append(key)
+ try:
+ import_result = c.key_import(key.key_blob)
+ except Exception as e:
+ import_result = c.key_import(key.key)
+ else:
+ paradox.append(keys)
+ import_result = None
+ except Exception as e:
+ import_result = None
+ results.append(import_result)
+
+ for result in results:
+ if result is not None and hasattr(result, "considered") is False:
+ print("{0} for {1}".format(result.decode(), k))
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ With UIDs wholely or partially matching the following string:
+
+ {1}
+
+ Number of keys revoked: {2}
+ Number of new signatures: {3}
+ Number of new subkeys: {4}
+ Number of new user IDs: {5}
+ Number of new secret keys: {6}
+ Number of unchanged keys: {7}
+
+ The key IDs for all considered keys were:
+ """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ elif result is None:
+ pass
+
+#. Import from ProtonMail via HKP for Python Example no. 2
+
+ Like its counterpart above, this script can also be found with the
+ rest of the examples, by the name pmkey-import-hkp-alt.py.
+
+ With this script a modicum of effort has been made to treat anything
+ passed as a ``homedir`` which either does not exist or which is not a
+ directory, as also being a pssible user ID to check for. It\'s not
+ guaranteed to pick up on all such cases, but it should cover most of
+ them.
+
+ .. code:: python
+
+ import gpg
+ import hkp4py
+ import os.path
+ import sys
+
+ print("""
+ This script searches the ProtonMail key server for the specified key and
+ imports it. Optionally enables specifying a different GnuPG home directory.
+
+ Usage: pmkey-import-hkp.py [homedir] [search string]
+ or: pmkey-import-hkp.py [search string]
+ """)
+
+ c = gpg.Context(armor=True)
+ server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+ keyterms = []
+ ksearch = []
+ allkeys = []
+ results = []
+ paradox = []
+ homeless = None
+
+ if len(sys.argv) > 3:
+ homedir = sys.argv[1]
+ keyterms = sys.argv[2:]
+ elif len(sys.argv) == 3:
+ homedir = sys.argv[1]
+ keyterm = sys.argv[2]
+ keyterms.append(keyterm)
+ elif len(sys.argv) == 2:
+ homedir = ""
+ keyterm = sys.argv[1]
+ keyterms.append(keyterm)
+ else:
+ keyterm = input("Enter the key ID, UID or search string: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ keyterms.append(keyterm)
+
+ if len(homedir) == 0:
+ homedir = None
+ homeless = False
+
+ if homedir is not None:
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ if os.path.isdir(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.realpath(os.path.expanduser(homedir))
+ else:
+ homeless = True
+ else:
+ homeless = True
+ elif os.path.exists(os.path.realpath(homedir)) is True:
+ if os.path.isdir(os.path.realpath(homedir)) is True:
+ c.home_dir = os.path.realpath(homedir)
+ else:
+ homeless = True
+ else:
+ homeless = True
+
+ # First check to see if the homedir really is a homedir and if not, treat it as
+ # a search string.
+ if homeless is True:
+ keyterms.append(homedir)
+ c.home_dir = None
+ else:
+ pass
+
+ for keyterm in keyterms:
+ if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+ ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+ ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+ ksearch.append("{0}@pm.me".format(keyterm[1:]))
+ elif keyterm.count("@") == 0:
+ ksearch.append("{0}@protonmail.com".format(keyterm))
+ ksearch.append("{0}@protonmail.ch".format(keyterm))
+ ksearch.append("{0}@pm.me".format(keyterm))
+ elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ elif keyterm.count("@") > 2:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ else:
+ ksearch.append(keyterm)
+
+ for k in ksearch:
+ print("Checking for key for: {0}".format(k))
+ try:
+ keys = server.search(k)
+ if isinstance(keys, list) is True:
+ for key in keys:
+ allkeys.append(key)
+ try:
+ import_result = c.key_import(key.key_blob)
+ except Exception as e:
+ import_result = c.key_import(key.key)
+ else:
+ paradox.append(keys)
+ import_result = None
+ except Exception as e:
+ import_result = None
+ results.append(import_result)
+
+ for result in results:
+ if result is not None and hasattr(result, "considered") is False:
+ print("{0} for {1}".format(result.decode(), k))
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ With UIDs wholely or partially matching the following string:
+
+ {1}
+
+ Number of keys revoked: {2}
+ Number of new signatures: {3}
+ Number of new subkeys: {4}
+ Number of new user IDs: {5}
+ Number of new secret keys: {6}
+ Number of unchanged keys: {7}
+
+ The key IDs for all considered keys were:
+ """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ elif result is None:
+ pass
+
+.. _howto-export-key:
+
+Exporting keys
+--------------
+
+Exporting keys remains a reasonably simple task, but has been separated
+into three different functions for the OpenPGP cryptographic engine. Two
+of those functions are for exporting public keys and the third is for
+exporting secret keys.
+
+.. _howto-export-public-key:
+
+Exporting public keys
+~~~~~~~~~~~~~~~~~~~~~
+
+There are two methods of exporting public keys, both of which are very
+similar to the other. The default method, ``key_export()``, will export
+a public key or keys matching a specified pattern as normal. The
+alternative, the ``key_export_minimal()`` method, will do the same thing
+except producing a minimised output with extra signatures and third
+party signatures or certifications removed.
+
+.. code:: python
+
+ import gpg
+ import os.path
+ import sys
+
+ print("""
+ This script exports one or more public keys.
+ """)
+
+ c = gpg.Context(armor=True)
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
+ else:
+ pass
+ elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+ else:
+ pass
+
+ try:
+ result = c.key_export(pattern=logrus)
+ except:
+ result = c.key_export(pattern=None)
+
+ if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ else:
+ pass
+
+It should be noted that the result will only return ``None`` when a
+search pattern has been entered, but has not matched any keys. When the
+search pattern itself is set to ``None`` this triggers the exporting of
+the entire public keybox.
+
+.. code:: python
+
+ import gpg
+ import os.path
+ import sys
+
+ print("""
+ This script exports one or more public keys in minimised form.
+ """)
+
+ c = gpg.Context(armor=True)
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
+ else:
+ pass
+ elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+ else:
+ pass
+
+ try:
+ result = c.key_export_minimal(pattern=logrus)
+ except:
+ result = c.key_export_minimal(pattern=None)
+
+ if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ else:
+ pass
+
+.. _howto-export-secret-key:
+
+Exporting secret keys
+~~~~~~~~~~~~~~~~~~~~~
+
+Exporting secret keys is, functionally, very similar to exporting public
+keys; save for the invocation of ``pinentry`` via ``gpg-agent`` in order
+to securely enter the key\'s passphrase and authorise the export.
+
+The following example exports the secret key to a file which is then set
+with the same permissions as the output files created by the command
+line secret key export options.
+
+.. code:: python
+
+ import gpg
+ import os
+ import os.path
+ import sys
+
+ print("""
+ This script exports one or more secret keys.
+
+ The gpg-agent and pinentry are invoked to authorise the export.
+ """)
+
+ c = gpg.Context(armor=True)
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if len(homedir) == 0:
+ homedir = None
+ elif homedir.startswith("~"):
+ userdir = os.path.expanduser(homedir)
+ if os.path.exists(userdir) is True:
+ homedir = os.path.realpath(userdir)
+ else:
+ homedir = None
+ else:
+ homedir = os.path.realpath(homedir)
+
+ if os.path.exists(homedir) is False:
+ homedir = None
+ else:
+ if os.path.isdir(homedir) is False:
+ homedir = None
+ else:
+ pass
+
+ if homedir is not None:
+ c.home_dir = homedir
+ else:
+ pass
+
+ try:
+ result = c.key_export_secret(pattern=logrus)
+ except:
+ result = c.key_export_secret(pattern=None)
+
+ if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ os.chmod(keyfile, 0o600)
+ else:
+ pass
+
+Alternatively the approach of the following script can be used. This
+longer example saves the exported secret key(s) in files in the GnuPG
+home directory, in addition to setting the file permissions as only
+readable and writable by the user. It also exports the secret key(s)
+twice in order to output both GPG binary (``.gpg``) and ASCII armoured
+(``.asc``) files.
+
+.. code:: python
+
+ import gpg
+ import os
+ import os.path
+ import subprocess
+ import sys
+
+ print("""
+ This script exports one or more secret keys as both ASCII armored and binary
+ file formats, saved in files within the user's GPG home directory.
+
+ The gpg-agent and pinentry are invoked to authorise the export.
+ """)
+
+ if sys.platform == "win32":
+ gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+ else:
+ gpgconfcmd = "gpgconf --list-dirs homedir"
+
+ a = gpg.Context(armor=True)
+ b = gpg.Context()
+ c = gpg.Context()
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if len(homedir) == 0:
+ homedir = None
+ elif homedir.startswith("~"):
+ userdir = os.path.expanduser(homedir)
+ if os.path.exists(userdir) is True:
+ homedir = os.path.realpath(userdir)
+ else:
+ homedir = None
+ else:
+ homedir = os.path.realpath(homedir)
+
+ if os.path.exists(homedir) is False:
+ homedir = None
+ else:
+ if os.path.isdir(homedir) is False:
+ homedir = None
+ else:
+ pass
+
+ if homedir is not None:
+ c.home_dir = homedir
+ else:
+ pass
+
+ if c.home_dir is not None:
+ if c.home_dir.endswith("/"):
+ gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
+ ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
+ else:
+ gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
+ ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
+ else:
+ if os.path.exists(os.environ["GNUPGHOME"]) is True:
+ hd = os.environ["GNUPGHOME"]
+ else:
+ try:
+ hd = subprocess.getoutput(gpgconfcmd)
+ except:
+ process = subprocess.Popen(gpgconfcmd.split(),
+ stdout=subprocess.PIPE)
+ procom = process.communicate()
+ if sys.version_info[0] == 2:
+ hd = procom[0].strip()
+ else:
+ hd = procom[0].decode().strip()
+ gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
+ ascfile = "{0}/{1}.asc".format(hd, keyfile)
+
+ try:
+ a_result = a.key_export_secret(pattern=logrus)
+ b_result = b.key_export_secret(pattern=logrus)
+ except:
+ a_result = a.key_export_secret(pattern=None)
+ b_result = b.key_export_secret(pattern=None)
+
+ if a_result is not None:
+ with open(ascfile, "wb") as f:
+ f.write(a_result)
+ os.chmod(ascfile, 0o600)
+ else:
+ pass
+
+ if b_result is not None:
+ with open(gpgfile, "wb") as f:
+ f.write(b_result)
+ os.chmod(gpgfile, 0o600)
+ else:
+ pass
+
+.. _howto-send-public-key:
+
+Sending public keys to the SKS Keyservers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As with the previous section on importing keys, the ``hkp4py`` module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+.. code:: python
+
+ import gpg
+ import hkp4py
+ import os.path
+ import sys
+
+ print("""
+ This script sends one or more public keys to the SKS keyservers and is
+ essentially a slight variation on the export-key.py script.
+ """)
+
+ c = gpg.Context(armor=True)
+ server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+ if len(sys.argv) > 2:
+ logrus = " ".join(sys.argv[1:])
+ elif len(sys.argv) == 2:
+ logrus = sys.argv[1]
+ else:
+ logrus = input("Enter the UID matching the key(s) to send: ")
+
+ if len(logrus) > 0:
+ try:
+ export_result = c.key_export(pattern=logrus)
+ except Exception as e:
+ print(e)
+ export_result = None
+ else:
+ export_result = c.key_export(pattern=None)
+
+ if export_result is not None:
+ try:
+ try:
+ send_result = server.add(export_result)
+ except:
+ send_result = server.add(export_result.decode())
+ if send_result is not None:
+ print(send_result)
+ else:
+ pass
+ except Exception as e:
+ print(e)
+ else:
+ pass
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples directory
+as ``send-key-to-keyserver.py``.
+
+The ``hkp4py`` module appears to handle both string and byte literal
+text data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
+.. _howto-the-basics:
+
+Basic Functions
+===============
+
+The most frequently called features of any cryptographic library will be
+the most fundamental tasks for encryption software. In this section we
+will look at how to programmatically encrypt data, decrypt it, sign it
+and verify signatures.
+
+.. _howto-basic-encryption:
+
+Encryption
+----------
+
+Encrypting is very straight forward. In the first example below the
+message, ``text``, is encrypted to a single recipient\'s key. In the
+second example the message will be encrypted to multiple recipients.
+
+.. _howto-basic-encryption-single:
+
+Encrypting to one key
+~~~~~~~~~~~~~~~~~~~~~
+
+Once the the Context is set the main issues with encrypting data is
+essentially reduced to key selection and the keyword arguments specified
+in the ``gpg.Context().encrypt()`` method.
+
+Those keyword arguments are: ``recipients``, a list of keys encrypted to
+(covered in greater detail in the following section); ``sign``, whether
+or not to sign the plaintext data, see subsequent sections on signing
+and verifying signatures below (defaults to ``True``); ``sink``, to
+write results or partial results to a secure sink instead of returning
+it (defaults to ``None``); ``passphrase``, only used when utilising
+symmetric encryption (defaults to ``None``); ``always_trust``, used to
+override the trust model settings for recipient keys (defaults to
+``False``); ``add_encrypt_to``, utilises any preconfigured
+``encrypt-to`` or ``default-key`` settings in the user\'s ``gpg.conf``
+file (defaults to ``False``); ``prepare``, prepare for encryption
+(defaults to ``False``); ``expect_sign``, prepare for signing (defaults
+to ``False``); ``compress``, compresses the plaintext prior to
+encryption (defaults to ``True``).
+
+.. code:: python
+
+ import gpg
+
+ a_key = "0x12345678DEADBEEF"
+ text = b"""Some text to test with.
+
+ Since the text in this case must be bytes, it is most likely that
+ the input form will be a separate file which is opened with "rb"
+ as this is the simplest method of obtaining the correct data format.
+ """
+
+ c = gpg.Context(armor=True)
+ rkey = list(c.keylist(pattern=a_key, secret=False))
+ ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+
+Though this is even more likely to be used like this; with the plaintext
+input read from a file, the recipient keys used for encryption
+regardless of key trust status and the encrypted output also encrypted
+to any preconfigured keys set in the ``gpg.conf`` file:
+
+.. code:: python
+
+ import gpg
+
+ a_key = "0x12345678DEADBEEF"
+
+ with open("secret_plans.txt", "rb") as afile:
+ text = afile.read()
+
+ c = gpg.Context(armor=True)
+ rkey = list(c.keylist(pattern=a_key, secret=False))
+ ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
+ always_trust=True,
+ add_encrypt_to=True)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+
+If the ``recipients`` paramater is empty then the plaintext is encrypted
+symmetrically. If no ``passphrase`` is supplied as a parameter or via a
+callback registered with the ``Context()`` then an out-of-band prompt
+for the passphrase via pinentry will be invoked.
+
+.. _howto-basic-encryption-multiple:
+
+Encrypting to multiple keys
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Encrypting to multiple keys essentially just expands upon the key
+selection process and the recipients from the previous examples.
+
+The following example encrypts a message (``text``) to everyone with an
+email address on the ``gnupg.org`` domain, [4]_ but does *not* encrypt
+to a default key or other key which is configured to normally encrypt
+to.
+
+.. code:: python
+
+ import gpg
+
+ text = b"""Oh look, another test message.
+
+ The same rules apply as with the previous example and more likely
+ than not, the message will actually be drawn from reading the
+ contents of a file or, maybe, from entering data at an input()
+ prompt.
+
+ Since the text in this case must be bytes, it is most likely that
+ the input form will be a separate file which is opened with "rb"
+ as this is the simplest method of obtaining the correct data
+ format.
+ """
+
+ c = gpg.Context(armor=True)
+ rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+ logrus = []
+
+ for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ sign=False, always_trust=True)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+
+All it would take to change the above example to sign the message and
+also encrypt the message to any configured default keys would be to
+change the ``c.encrypt`` line to this:
+
+.. code:: python
+
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ always_trust=True,
+ add_encrypt_to=True)
+
+The only keyword arguments requiring modification are those for which
+the default values are changing. The default value of ``sign`` is
+``True``, the default of ``always_trust`` is ``False``, the default of
+``add_encrypt_to`` is ``False``.
+
+If ``always_trust`` is not set to ``True`` and any of the recipient keys
+are not trusted (e.g. not signed or locally signed) then the encryption
+will raise an error. It is possible to mitigate this somewhat with
+something more like this:
+
+.. code:: python
+
+ import gpg
+
+ with open("secret_plans.txt.asc", "rb") as afile:
+ text = afile.read()
+
+ c = gpg.Context(armor=True)
+ rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+ logrus = []
+
+ for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ try:
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ add_encrypt_to=True)
+ except gpg.errors.InvalidRecipients as e:
+ for i in range(len(e.recipients)):
+ for n in range(len(logrus)):
+ if logrus[n].fpr == e.recipients[i].fpr:
+ logrus.remove(logrus[n])
+ else:
+ pass
+ try:
+ ciphertext, result, sign_result = c.encrypt(text,
+ recipients=logrus,
+ add_encrypt_to=True)
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ except:
+ pass
+
+This will attempt to encrypt to all the keys searched for, then remove
+invalid recipients if it fails and try again.
+
+.. _howto-basic-decryption:
+
+Decryption
+----------
+
+Decrypting something encrypted to a key in one\'s secret keyring is
+fairly straight forward.
+
+In this example code, however, preconfiguring either ``gpg.Context()``
+or ``gpg.core.Context()`` as ``c`` is unnecessary because there is no
+need to modify the Context prior to conducting the decryption and since
+the Context is only used once, setting it to ``c`` simply adds lines for
+no gain.
+
+.. code:: python
+
+ import gpg
+
+ ciphertext = input("Enter path and filename of encrypted file: ")
+ newfile = input("Enter path and filename of file to save decrypted data to: ")
+
+ with open(ciphertext, "rb") as cfile:
+ try:
+ plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+ except gpg.errors.GPGMEError as e:
+ plaintext = None
+ print(e)
+
+ if plaintext is not None:
+ with open(newfile, "wb") as nfile:
+ nfile.write(plaintext)
+ else:
+ pass
+
+The data available in ``plaintext`` in this example is the decrypted
+content as a byte object, the recipient key IDs and algorithms in
+``result`` and the results of verifying any signatures of the data in
+``verify_result``.
+
+.. _howto-basic-signing:
+
+Signing text and files
+----------------------
+
+The following sections demonstrate how to specify keys to sign with.
+
+.. _howto-basic-signing-signers:
+
+Signing key selection
+~~~~~~~~~~~~~~~~~~~~~
+
+By default GPGME and the Python bindings will use the default key
+configured for the user invoking the GPGME API. If there is no default
+key specified and there is more than one secret key available it may be
+necessary to specify the key or keys with which to sign messages and
+files.
+
+.. code:: python
+
+ import gpg
+
+ logrus = input("Enter the email address or string to match signing keys to: ")
+ hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+ sig_src = list(hancock)
+
+The signing examples in the following sections include the explicitly
+designated ``signers`` parameter in two of the five examples; once where
+the resulting signature would be ASCII armoured and once where it would
+not be armoured.
+
+While it would be possible to enter a key ID or fingerprint here to
+match a specific key, it is not possible to enter two fingerprints and
+match two keys since the patten expects a string, bytes or None and not
+a list. A string with two fingerprints won\'t match any single key.
+
+.. _howto-basic-signing-normal:
+
+Normal or default signing messages or files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The normal or default signing process is essentially the same as is most
+often invoked when also encrypting a message or file. So when the
+encryption component is not utilised, the result is to produce an
+encoded and signed output which may or may not be ASCII armoured and
+which may or may not also be compressed.
+
+By default compression will be used unless GnuPG detects that the
+plaintext is already compressed. ASCII armouring will be determined
+according to the value of ``gpg.Context().armor``.
+
+The compression algorithm is selected in much the same way as the
+symmetric encryption algorithm or the hash digest algorithm is when
+multiple keys are involved; from the preferences saved into the key
+itself or by comparison with the preferences with all other keys
+involved.
+
+.. code:: python
+
+ import gpg
+
+ text0 = """Declaration of ... something.
+
+ """
+ text = text0.encode()
+
+ c = gpg.Context(armor=True, signers=sig_src)
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+ with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+
+Though everything in this example is accurate, it is more likely that
+reading the input data from another file and writing the result to a new
+file will be performed more like the way it is done in the next example.
+Even if the output format is ASCII armoured.
+
+.. code:: python
+
+ import gpg
+
+ with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+ c = gpg.Context()
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+ with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+
+.. _howto-basic-signing-detached:
+
+Detached signing messages and files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Detached signatures will often be needed in programmatic uses of GPGME,
+either for signing files (e.g. tarballs of code releases) or as a
+component of message signing (e.g. PGP/MIME encoded email).
+
+.. code:: python
+
+ import gpg
+
+ text0 = """Declaration of ... something.
+
+ """
+ text = text0.encode()
+
+ c = gpg.Context(armor=True)
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+ with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+
+As with normal signatures, detached signatures are best handled as byte
+literals, even when the output is ASCII armoured.
+
+.. code:: python
+
+ import gpg
+
+ with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+ c = gpg.Context(signers=sig_src)
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+ with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+
+.. _howto-basic-signing-clear:
+
+Clearsigning messages or text
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Though PGP/in-line messages are no longer encouraged in favour of
+PGP/MIME, there is still sometimes value in utilising in-line
+signatures. This is where clear-signed messages or text is of value.
+
+.. code:: python
+
+ import gpg
+
+ text0 = """Declaration of ... something.
+
+ """
+ text = text0.encode()
+
+ c = gpg.Context()
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+ with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+
+In spite of the appearance of a clear-signed message, the data handled
+by GPGME in signing it must still be byte literals.
+
+.. code:: python
+
+ import gpg
+
+ with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+ c = gpg.Context()
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+ with open("/path/to/statement.txt.asc", "wb") as afile:
+ afile.write(signed_data)
+
+.. _howto-basic-verification:
+
+Signature verification
+----------------------
+
+Essentially there are two principal methods of verification of a
+signature. The first of these is for use with the normal or default
+signing method and for clear-signed messages. The second is for use with
+files and data with detached signatures.
+
+The following example is intended for use with the default signing
+method where the file was not ASCII armoured:
+
+.. code:: python
+
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ gpg_file = "statement.txt.gpg"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(gpg_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+
+Whereas this next example, which is almost identical would work with
+normal ASCII armoured files and with clear-signed files:
+
+.. code:: python
+
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ asc_file = "statement.txt.asc"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(asc_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+
+In both of the previous examples it is also possible to compare the
+original data that was signed against the signed data in ``data`` to see
+if it matches with something like this:
+
+.. code:: python
+
+ with open(filename, "rb") as afile:
+ text = afile.read()
+
+ if text == data:
+ print("Good signature.")
+ else:
+ pass
+
+The following two examples, however, deal with detached signatures. With
+his method of verification the data that was signed does not get
+returned since it is already being explicitly referenced in the first
+argument of ``c.verify``. So ``data`` is ``None`` and only the
+information in ``result`` is available.
+
+.. code:: python
+
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ sig_file = "statement.txt.sig"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(filename), open(sig_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+
+.. code:: python
+
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ asc_file = "statement.txt.asc"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(filename), open(asc_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+
+.. _key-generation:
+
+Creating keys and subkeys
+=========================
+
+The one thing, aside from GnuPG itself, that GPGME depends on, of
+course, is the keys themselves. So it is necessary to be able to
+generate them and modify them by adding subkeys, revoking or disabling
+them, sometimes deleting them and doing the same for user IDs.
+
+In the following examples a key will be created for the world\'s
+greatest secret agent, Danger Mouse. Since Danger Mouse is a secret
+agent he needs to be able to protect information to ``SECRET`` level
+clearance, so his keys will be 3072-bit keys.
+
+The pre-configured ``gpg.conf`` file which sets cipher, digest and other
+preferences contains the following configuration parameters:
+
+.. code:: conf
+
+ expert
+ allow-freeform-uid
+ allow-secret-key-import
+ trust-model tofu+pgp
+ tofu-default-policy unknown
+ enable-large-rsa
+ enable-dsa2
+ cert-digest-algo SHA512
+ default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+ personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+ personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+ personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+
+.. _keygen-primary:
+
+Primary key
+-----------
+
+Generating a primary key uses the ``create_key`` method in a Context. It
+contains multiple arguments and keyword arguments, including:
+``userid``, ``algorithm``, ``expires_in``, ``expires``, ``sign``,
+``encrypt``, ``certify``, ``authenticate``, ``passphrase`` and
+``force``. The defaults for all of those except ``userid``,
+``algorithm``, ``expires_in``, ``expires`` and ``passphrase`` is
+``False``. The defaults for ``algorithm`` and ``passphrase`` is
+``None``. The default for ``expires_in`` is ``0``. The default for
+``expires`` is ``True``. There is no default for ``userid``.
+
+If ``passphrase`` is left as ``None`` then the key will not be generated
+with a passphrase, if ``passphrase`` is set to a string then that will
+be the passphrase and if ``passphrase`` is set to ``True`` then
+gpg-agent will launch pinentry to prompt for a passphrase. For the sake
+of convenience, these examples will keep ``passphrase`` set to ``None``.
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+
+ c.home_dir = "~/.gnupg-dm"
+ userid = "Danger Mouse "
+
+ dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
+ sign=True, certify=True)
+
+One thing to note here is the use of setting the ``c.home_dir``
+parameter. This enables generating the key or keys in a different
+location. In this case to keep the new key data created for this example
+in a separate location rather than adding it to existing and active key
+store data. As with the default directory, ``~/.gnupg``, any temporary
+or separate directory needs the permissions set to only permit access by
+the directory owner. On posix systems this means setting the directory
+permissions to 700.
+
+The ``temp-homedir-config.py`` script in the HOWTO examples directory
+will create an alternative homedir with these configuration options
+already set and the correct directory and file permissions.
+
+The successful generation of the key can be confirmed via the returned
+``GenkeyResult`` object, which includes the following data:
+
+.. code:: python
+
+ print("""
+ Fingerprint: {0}
+ Primary Key: {1}
+ Public Key: {2}
+ Secret Key: {3}
+ Sub Key: {4}
+ User IDs: {5}
+ """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
+ dmkey.uid))
+
+Alternatively the information can be confirmed using the command line
+program:
+
+.. code:: shell
+
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse
+
+ bash-4.4$
+
+As with generating keys manually, to preconfigure expanded preferences
+for the cipher, digest and compression algorithms, the ``gpg.conf`` file
+must contain those details in the home directory in which the new key is
+being generated. I used a cut down version of my own ``gpg.conf`` file
+in order to be able to generate this:
+
+.. code:: shell
+
+ bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
+ Secret key is available.
+
+ sec rsa3072/026D2F19E99E63AA
+ created: 2018-03-15 expires: 2019-03-15 usage: SC
+ trust: ultimate validity: ultimate
+ [ultimate] (1). Danger Mouse
+
+ [ultimate] (1). Danger Mouse
+ Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
+ Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
+ Compression: ZLIB, BZIP2, ZIP, Uncompressed
+ Features: MDC, Keyserver no-modify
+
+ bash-4.4$
+
+.. _keygen-subkeys:
+
+Subkeys
+-------
+
+Adding subkeys to a primary key is fairly similar to creating the
+primary key with the ``create_subkey`` method. Most of the arguments are
+the same, but not quite all. Instead of the ``userid`` argument there is
+now a ``key`` argument for selecting which primary key to add the subkey
+to.
+
+In the following example an encryption subkey will be added to the
+primary key. Since Danger Mouse is a security conscious secret agent,
+this subkey will only be valid for about six months, half the length of
+the primary key.
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
+
+ key = c.get_key(dmkey.fpr, secret=True)
+ dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
+ encrypt=True)
+
+As with the primary key, the results here can be checked with:
+
+.. code:: python
+
+ print("""
+ Fingerprint: {0}
+ Primary Key: {1}
+ Public Key: {2}
+ Secret Key: {3}
+ Sub Key: {4}
+ User IDs: {5}
+ """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
+ dmsub.uid))
+
+As well as on the command line with:
+
+.. code:: shell
+
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse
+ ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+ bash-4.4$
+
+.. _keygen-uids:
+
+User IDs
+--------
+
+.. _keygen-uids-add:
+
+Adding User IDs
+~~~~~~~~~~~~~~~
+
+By comparison to creating primary keys and subkeys, adding a new user ID
+to an existing key is much simpler. The method used to do this is
+``key_add_uid`` and the only arguments it takes are for the ``key`` and
+the new ``uid``.
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
+
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret=True)
+ uid = "Danger Mouse "
+
+ c.key_add_uid(key, uid)
+
+Unsurprisingly the result of this is:
+
+.. code:: shell
+
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse
+ uid [ultimate] Danger Mouse
+ ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+ bash-4.4$
+
+.. _keygen-uids-revoke:
+
+Revokinging User IDs
+~~~~~~~~~~~~~~~~~~~~
+
+Revoking a user ID is a fairly similar process, except that it uses the
+``key_revoke_uid`` method.
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
+
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret=True)
+ uid = "Danger Mouse "
+
+ c.key_revoke_uid(key, uid)
+
+.. _key-sign:
+
+Key certification
+-----------------
+
+Since key certification is more frequently referred to as key signing,
+the method used to perform this function is ``key_sign``.
+
+The ``key_sign`` method takes four arguments: ``key``, ``uids``,
+``expires_in`` and ``local``. The default value of ``uids`` is ``None``
+and which results in all user IDs being selected. The default value of
+both ``expires_in`` and ``local`` is ``False``; which results in the
+signature never expiring and being able to be exported.
+
+The ``key`` is the key being signed rather than the key doing the
+signing. To change the key doing the signing refer to the signing key
+selection above for signing messages and files.
+
+If the ``uids`` value is not ``None`` then it must either be a string to
+match a single user ID or a list of strings to match multiple user IDs.
+In this case the matching of those strings must be precise and it is
+case sensitive.
+
+To sign Danger Mouse\'s key for just the initial user ID with a
+signature which will last a little over a month, do this:
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+ uid = "Danger Mouse "
+
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret=True)
+ c.key_sign(key, uids=uid, expires_in=2764800)
+
+.. _advanced-use:
+
+Advanced or Experimental Use Cases
+==================================
+
+.. _cython:
+
+C plus Python plus SWIG plus Cython
+-----------------------------------
+
+In spite of the apparent incongruence of using Python bindings to a C
+interface only to generate more C from the Python; it is in fact quite
+possible to use the GPGME bindings with
+`Cython `__. Though in many
+cases the benefits may not be obvious since the most computationally
+intensive work never leaves the level of the C code with which GPGME
+itself is interacting with.
+
+Nevertheless, there are some situations where the benefits are
+demonstrable. One of the better and easier examples being the one of the
+early examples in this HOWTO, the `key
+counting <#howto-keys-counting>`__ code. Running that example as an
+executable Python script, ``keycount.py`` (available in the
+``examples/howto/`` directory), will take a noticable amount of time to
+run on most systems where the public keybox or keyring contains a few
+thousand public keys.
+
+Earlier in the evening, prior to starting this section, I ran that
+script on my laptop; as I tend to do periodically and timed it using
+``time`` utility, with the following results:
+
+.. code:: shell
+
+ bash-4.4$ time keycount.py
+
+ Number of secret keys: 23
+ Number of public keys: 12112
+
+
+ real 11m52.945s
+ user 0m0.913s
+ sys 0m0.752s
+
+ bash-4.4$
+
+Sometime after that I imported another key and followed it with a little
+test of Cython. This test was kept fairly basic, essentially lifting the
+material from the `Cython Basic
+Tutorial `__
+to demonstrate compiling Python code to C. The first step was to take
+the example key counting code quoted previously, essentially from the
+importing of the ``gpg`` module to the end of the script:
+
+.. code:: python
+
+ import gpg
+
+ c = gpg.Context()
+ seckeys = c.keylist(pattern=None, secret=True)
+ pubkeys = c.keylist(pattern=None, secret=False)
+
+ seclist = list(seckeys)
+ secnum = len(seclist)
+
+ publist = list(pubkeys)
+ pubnum = len(publist)
+
+ print("""
+ Number of secret keys: {0}
+ Number of public keys: {1}
+
+ """.format(secnum, pubnum))
+
+Save that into a file called ``keycount.pyx`` and then create a
+``setup.py`` file which contains this:
+
+.. code:: python
+
+ from distutils.core import setup
+ from Cython.Build import cythonize
+
+ setup(
+ ext_modules = cythonize("keycount.pyx")
+ )
+
+Compile it:
+
+.. code:: shell
+
+ bash-4.4$ python setup.py build_ext --inplace
+ bash-4.4$
+
+Then run it in a similar manner to ``keycount.py``:
+
+.. code:: shell
+
+ bash-4.4$ time python3.7 -c "import keycount"
+
+ Number of secret keys: 23
+ Number of public keys: 12113
+
+
+ real 6m47.905s
+ user 0m0.785s
+ sys 0m0.331s
+
+ bash-4.4$
+
+Cython turned ``keycount.pyx`` into an 81KB ``keycount.o`` file in the
+``build/`` directory, a 24KB ``keycount.cpython-37m-darwin.so`` file to
+be imported into Python 3.7 and a 113KB ``keycount.c`` generated C
+source code file of nearly three thousand lines. Quite a bit bigger than
+the 314 bytes of the ``keycount.pyx`` file or the full 1,452 bytes of
+the full executable ``keycount.py`` example script.
+
+On the other hand it ran in nearly half the time; taking 6 minutes and
+47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds
+which the CPython script alone took.
+
+The ``keycount.pyx`` and ``setup.py`` files used to generate this
+example have been added to the ``examples/howto/advanced/cython/``
+directory The example versions include some additional options to
+annotate the existing code and to detect Cython\'s use. The latter comes
+from the `Magic
+Attributes `__
+section of the Cython documentation.
+
+.. _cheats-and-hacks:
+
+Miscellaneous extras and work-arounds
+=====================================
+
+Most of the things in the following sections are here simply because
+there was no better place to put them, even though some are only
+peripherally related to the GPGME Python bindings. Some are also
+workarounds for functions not integrated with GPGME as yet. This is
+especially true of the first of these, dealing with `group
+lines <#group-lines>`__.
+
+Group lines
+-----------
+
+There is not yet an easy way to access groups configured in the gpg.conf
+file from within GPGME. As a consequence these central groupings of keys
+cannot be shared amongst multiple programs, such as MUAs readily.
+
+The following code, however, provides a work-around for obtaining this
+information in Python.
+
+.. code:: python
+
+ import subprocess
+ import sys
+
+ if sys.platform == "win32":
+ gpgconfcmd = "gpgconf.exe --list-options gpg"
+ else:
+ gpgconfcmd = "gpgconf --list-options gpg"
+
+ try:
+ lines = subprocess.getoutput(gpgconfcmd).splitlines()
+ except:
+ process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+ procom = process.communicate()
+ if sys.version_info[0] == 2:
+ lines = procom[0].splitlines()
+ else:
+ lines = procom[0].decode().splitlines()
+
+ for i in range(len(lines)):
+ if lines[i].startswith("group") is True:
+ line = lines[i]
+ else:
+ pass
+
+ groups = line.split(":")[-1].replace('"', '').split(',')
+
+ group_lines = []
+ group_lists = []
+
+ for i in range(len(groups)):
+ group_lines.append(groups[i].split("="))
+ group_lists.append(groups[i].split("="))
+
+ for i in range(len(group_lists)):
+ group_lists[i][1] = group_lists[i][1].split()
+
+The result of that code is that ``group_lines`` is a list of lists where
+``group_lines[i][0]`` is the name of the group and ``group_lines[i][1]``
+is the key IDs of the group as a string.
+
+The ``group_lists`` result is very similar in that it is a list of
+lists. The first part, ``group_lists[i][0]`` matches
+``group_lines[i][0]`` as the name of the group, but
+``group_lists[i][1]`` is the key IDs of the group as a string.
+
+A demonstration of using the ``groups.py`` module is also available in
+the form of the executable ``mutt-groups.py`` script. This second script
+reads all the group entries in a user\'s ``gpg.conf`` file and converts
+them into crypt-hooks suitable for use with the Mutt and Neomutt mail
+clients.
+
+.. _hkp4py:
+
+Keyserver access for Python
+---------------------------
+
+The `hkp4py `__ module by Marcel Fest
+was originally a port of the old
+`python-hkp `__ module from
+Python 2 to Python 3 and updated to use the
+`requests `__
+module instead. It has since been modified to provide support for Python
+2.7 as well and is available via PyPI.
+
+Since it rewrites the ``hkp`` protocol prefix as ``http`` and ``hkps``
+as ``https``, the module is able to be used even with servers which do
+not support the full scope of keyserver functions. [5]_ It also works
+quite readily when incorporated into a `Cython <#cython>`__ generated
+and compiled version of any code.
+
+.. _hkp4py-strings:
+
+Key import format
+~~~~~~~~~~~~~~~~~
+
+The hkp4py module returns key data via requests as string literals
+(``r.text``) instead of byte literals (``r.content``). This means that
+the retrurned key data must be encoded to UTF-8 when importing that key
+material using a ``gpg.Context().key_import()`` method.
+
+For this reason an alternative method has been added to the ``search``
+function of ``hkp4py.KeyServer()`` which returns the key in the correct
+format as expected by ``key_import``. When importing using this module,
+it is now possible to import with this:
+
+.. code:: python
+
+ for key in keys:
+ if key.revoked is False:
+ gpg.Context().key_import(key.key_blob)
+ else:
+ pass
+
+Without that recent addition it would have been necessary to encode the
+contents of each ``hkp4py.KeyServer().search()[i].key`` in
+``hkp4py.KeyServer().search()`` before trying to import it.
+
+An example of this is included in the `Importing
+Keys <#howto-import-key>`__ section of this HOWTO and the corresponding
+executable version of that example is available in the
+``lang/python/examples/howto`` directory as normal; the executable
+version is the ``import-keys-hkp.py`` file.
+
+.. _copyright-and-license:
+
+Copyright and Licensing
+=======================
+
+Copyright
+---------
+
+Copyright © The GnuPG Project, 2018.
+
+Copyright (C) The GnuPG Project, 2018.
+
+.. _draft-editions:
+
+Draft Editions of this HOWTO
+----------------------------
+
+Draft editions of this HOWTO may be periodically available directly from
+the author at any of the following URLs:
+
+- `GPGME Python Bindings HOWTO draft (XHTML AWS S3
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (XHTML AWS S3 no
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (Texinfo file AWS S3
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (Info file AWS S3
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (Info file AWS S3 no
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (reST file AWS S3
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (reST file AWS S3 no
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3
+ SSL) `__
+- `GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no
+ SSL) `__
+
+All of these draft versions except for one have been generated from this
+document via Emacs `Org mode `__ and `GNU
+Texinfo `__. Though it is likely
+that the specific
+`file `__
+`version `__
+used will be on the same server with the generated output formats.
+
+The one exception is the reStructuredText version, which was converted
+using the latest version of Pandoc from the Org mode source file using
+the following command:
+
+.. code:: shell
+
+ pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org
+
+In addition to these there is a significantly less frequently updated
+version as a HTML `WebHelp
+site `__
+(AWS S3 SSL); generated from DITA XML source files, which can be found
+in `an alternative
+branch `__
+of the GPGME git repository.
+
+These draft editions are not official documents and the version of
+documentation in the master branch or which ships with released versions
+is the only official documentation. Nevertheless, these draft editions
+may occasionally be of use by providing more accessible web versions
+which are updated between releases. They are provided on the
+understanding that they may contain errors or may contain content
+subject to change prior to an official release.
+
+.. _license:
+
+License GPL compatible
+----------------------
+
+This file is free software; as a special exception the author gives
+unlimited permission to copy and/or distribute it, with or without
+modifications, as long as this notice is preserved.
+
+This file is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY, to the extent permitted by law; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+Footnotes
+=========
+
+.. [1]
+ ``short-history.org`` and/or ``short-history.html``.
+
+.. [2]
+ With no issues reported specific to Python 3.7, the release of Python
+ 3.7.1 at around the same time as GPGME 1.12.0 and the testing with
+ Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of 3.6
+ now. Production environments with more conservative requirements will
+ always enforce their own policies anyway and installation to each
+ supported minor release is quite possible too.
+
+.. [3]
+ Yes, even if you use virtualenv with everything you do in Python. If
+ you want to install this module as just your user account then you
+ will need to manually configure, compile and install the *entire*
+ GnuPG stack as that user as well. This includes libraries which are
+ not often installed that way. It can be done and there are
+ circumstances under which it is worthwhile, but generally only on
+ POSIX systems which utilise single user mode (some even require it).
+
+.. [4]
+ You probably don\'t really want to do this. Searching the keyservers
+ for \"gnupg.org\" produces over 400 results, the majority of which
+ aren\'t actually at the gnupg.org domain, but just included a comment
+ regarding the project in their key somewhere.
+
+.. [5]
+ Such as with ProtonMail servers. This also means that restricted
+ servers which only advertise either HTTP or HTTPS end points and not
+ HKP or HKPS end points must still be identified as as HKP or HKPS
+ within the Python Code. The ``hkp4py`` module will rewrite these
+ appropriately when the connection is made to the server.
diff --git a/lang/python/doc/rst/index.rst b/lang/python/doc/rst/index.rst
new file mode 100644
index 0000000..31dc146
--- /dev/null
+++ b/lang/python/doc/rst/index.rst
@@ -0,0 +1,12 @@
+.. _index:
+
+GPGME Python Bindings
+=====================
+
+.. _index-contents:
+
+Contents
+--------
+
+- `A short history of the project `__
+- `GPGME Python Bindings HOWTO `__
diff --git a/lang/python/doc/rst/short-history.rst b/lang/python/doc/rst/short-history.rst
new file mode 100644
index 0000000..8cf604f
--- /dev/null
+++ b/lang/python/doc/rst/short-history.rst
@@ -0,0 +1,152 @@
+Overview
+========
+
+The GPGME Python bindings passed through many hands and numerous phases
+before, after a fifteen year journey, coming full circle to return to
+the source. This is a short explanation of that journey.
+
+.. _in-the-begining:
+
+In the beginning
+----------------
+
+In 2002 John Goerzen released PyME; Python bindings for the GPGME module
+which utilised the current release of Python of the time and SWIG. [1]_
+Shortly after creating it and ensuring it worked he stopped supporting
+it, though he left his work available on his Gopher site.
+
+Keeping the flame alive
+-----------------------
+
+A couple of years later the project was picked up by Igor Belyi and
+actively developed and maintained by him from 2004 to 2008. Igor's
+whereabouts at the time of this document's creation are unknown, but the
+current authors do hope he is well. We're assuming (or hoping) that life
+did what life does and made continuing untenable.
+
+Passing the torch
+-----------------
+
+In 2014 Martin Albrecht wanted to patch a bug in the PyME code and
+discovered the absence of Igor. Following a discussion on the PyME
+mailing list he became the new maintainer for PyME, releasing version
+0.9.0 in May of that year. He remains the maintainer of the original
+PyME release in Python 2.6 and 2.7 (available via PyPI).
+
+.. _ouroboros:
+
+Coming full circle
+------------------
+
+In 2015 Ben McGinnes approached Martin about a Python 3 version, while
+investigating how complex a task this would be the task ended up being
+completed. A subsequent discussion with Werner Koch led to the decision
+to fold the Python 3 port back into the original GPGME release in the
+languages subdirectory for non-C bindings under the module name of
+``pyme3``.
+
+In 2016 this PyME module was integrated back into the GPGME project by
+Justus Winter. During the course of this work Justus adjusted the port
+to restore limited support for Python 2, but not as many minor point
+releases as the original PyME package supports. During the course of
+this integration the package was renamed to more accurately reflect its
+status as a component of GPGME. The ``pyme3`` module was renamed to
+``gpg`` and adopted by the upstream GnuPG team.
+
+In 2017 Justus departed G10code and the GnuPG team. Following this Ben
+returned to maintain of gpgme Python bindings and continue building them
+from that point.
+
+.. _relics-past:
+
+Relics of the past
+==================
+
+There are a few things, in addition to code specific factors, such as
+SWIG itself, which are worth noting here.
+
+The Annoyances of Git
+---------------------
+
+As anyone who has ever worked with git knows, submodules are horrible
+way to deal with pretty much anything. In the interests of avoiding
+migraines, that was skipped with addition of the PyME code to GPGME.
+
+Instead the files were added to a subdirectory of the ``lang/``
+directory, along with a copy of the entire git log up to that point as a
+separate file within the ``lang/python/docs/`` directory. [2]_ As the
+log for PyME is nearly 100KB and the log for GPGME is approximately 1MB,
+this would cause considerable bloat, as well as some confusion, should
+the two be merged.
+
+Hence the unfortunate, but necessary, step to simply move the files. A
+regular repository version has been maintained should it be possible to
+implement this better in the future.
+
+The Perils of PyPI
+------------------
+
+The early port of the Python 2 ``pyme`` module as ``pyme3`` was never
+added to PyPI while the focus remained on development and testing during
+2015 and early 2016. Later in 2016, however, when Justus completed his
+major integration work and subsequently renamed the module from
+``pyme3`` to ``gpg``, some prior releases were also provided through
+PyPI.
+
+Since these bindings require a matching release of the GPGME libraries
+in order to function, it was determined that there was little benefit in
+also providing a copy through PyPI since anyone obtaining the GPGME
+source code would obtain the Python bindings source code at the same
+time. Whereas there was the potential to sew confusion amongst Python
+users installing the module from PyPI, only to discover that without the
+relevant C files, header files or SWIG compiled binaries, the Python
+module did them little good.
+
+There are only two files on PyPI which might turn up in a search for
+this module or a sample of its content:
+
+#. gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library
+#. pyme (0.9.0) - Python support for GPGME GnuPG cryptography library
+
+.. _pypi-gpgme-180:
+
+GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the most recent version to reach PyPI and is the version of the
+official Pyhon bindings which shipped with GPGME 1.8.0. If you have
+GPGME 1.8.0 installed and *only* 1.8.0 installed, then it is probably
+safe to use this copy from PyPI.
+
+As there have been a lot of changes since the release of GPGME 1.8.0,
+the GnuPG Project recommends not using this version of the module and
+instead installing the current version of GPGME along with the Python
+bindings included with that package.
+
+.. _pypi-gpgme-90:
+
+PyME 0·9·0 - Python support for GPGME GnuPG cryptography library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the last release of the PyME bindings maintained by Martin
+Albrecht and is only compatible with Python 2, it will not work with
+Python 3. This is the version of the software from which the port from
+Python 2 to Python 3 code was made in 2015.
+
+Users of the more recent Python bindings will recognise numerous points
+of similarity, but also significant differences. It is likely that the
+more recent official bindings will feel "more pythonic."
+
+For those using Python 2, there is essentially no harm in using this
+module, but it may lack a number of more recent features added to GPGME.
+
+Footnotes
+=========
+
+.. [1]
+ In all likelihood thos would have been Python 2.2 or possibly Python
+ 2.3.
+
+.. [2]
+ The entire PyME git log and other preceding VCS logs are located in
+ the ``gpgme/lang/python/docs/old-commits.log`` file.
diff --git a/lang/python/doc/src/gpgme-python-howto.org b/lang/python/doc/src/gpgme-python-howto.org
new file mode 100644
index 0000000..caa8e2f
--- /dev/null
+++ b/lang/python/doc/src/gpgme-python-howto.org
@@ -0,0 +1,3043 @@
+#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
+#+AUTHOR: Ben McGinnes
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman}
+#+LATEX_HEADER: \author{Ben McGinnes }
+
+
+* Introduction
+ :PROPERTIES:
+ :CUSTOM_ID: intro
+ :END:
+
+| Version: | 0.1.4 |
+| GPGME Version: | 1.12.0 |
+| Author: | [[https://gnupg.org/people/index.html#sec-1-5][Ben McGinnes]] |
+| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
+| Language: | Australian English, British English |
+| xml:lang: | en-AU, en-GB, en |
+
+This document provides basic instruction in how to use the GPGME
+Python bindings to programmatically leverage the GPGME library.
+
+
+** Python 2 versus Python 3
+ :PROPERTIES:
+ :CUSTOM_ID: py2-vs-py3
+ :END:
+
+Though the GPGME Python bindings themselves provide support for both
+Python 2 and 3, the focus is unequivocally on Python 3 and
+specifically from Python 3.4 and above. As a consequence all the
+examples and instructions in this guide use Python 3 code.
+
+Much of it will work with Python 2, but much of it also deals with
+Python 3 byte literals, particularly when reading and writing data.
+Developers concentrating on Python 2.7, and possibly even 2.6, will
+need to make the appropriate modifications to support the older string
+and unicode types as opposed to bytes.
+
+There are multiple reasons for concentrating on Python 3; some of
+which relate to the immediate integration of these bindings, some of
+which relate to longer term plans for both GPGME and the python
+bindings and some of which relate to the impending EOL period for
+Python 2.7. Essentially, though, there is little value in tying the
+bindings to a version of the language which is a dead end and the
+advantages offered by Python 3 over Python 2 make handling the data
+types with which GPGME deals considerably easier.
+
+
+** Examples
+ :PROPERTIES:
+ :CUSTOM_ID: howto-python3-examples
+ :END:
+
+All of the examples found in this document can be found as Python 3
+scripts in the =lang/python/examples/howto= directory.
+
+
+** Unofficial Drafts
+ :PROPERTIES:
+ :CUSTOM_ID: unofficial-drafts
+ :END:
+
+In addition to shipping with each release of GPGME, there is a section
+on locations to read or download [[#draft-editions][draft editions]] of this document from
+at the end of it. These are unofficial versions produced in between
+major releases.
+
+
+** What's New
+ :PROPERTIES:
+ :CUSTOM_ID: new-stuff
+ :END:
+
+The most obviously new point for those reading this guide is this
+section on other new things, but that's hardly important. Not given
+all the other things which spurred the need for adding this section
+and its subsections.
+
+*** New in GPGME 1·12·0
+ :PROPERTIES:
+ :CUSTOM_ID: new-stuff-1-12-0
+ :END:
+
+There have been quite a number of additions to GPGME and the Python
+bindings to it since the last release of GPGME with versions 1.11.0
+and 1.11.1 in April, 2018.
+
+The bullet points of new additiions are:
+
+- an expanded section on [[#installation][installing]] and [[#snafu][troubleshooting]] the Python
+ bindings.
+- The release of Python 3.7.0; which appears to be working just fine
+ with our bindings, in spite of intermittent reports of problems for
+ many other Python projects with that new release.
+- Python 3.7 has been moved to the head of the specified python
+ versions list in the build process.
+- In order to fix some other issues, there are certain underlying
+ functions which are more exposed through the [[#howto-get-context][gpg.Context()]], but
+ ongoing documentation ought to clarify that or otherwise provide the
+ best means of using the bindings. Some additions to =gpg.core= and
+ the =Context()=, however, were intended (see below).
+- Continuing work in identifying and confirming the cause of
+ oft-reported [[#snafu-runtime-not-funtime][problems installing the Python bindings on Windows]].
+- GSOC: Google's Surreptitiously Ordered Conscription ... erm ... oh,
+ right; Google's Summer of Code. Though there were two hopeful
+ candidates this year; only one ended up involved with the GnuPG
+ Project directly, the other concentrated on an unrelated third party
+ project with closer ties to one of the GNU/Linux distributions than
+ to the GnuPG Project. Thus the Python bindings benefited from GSOC
+ participant Jacob Adams, who added the key_import function; building
+ on prior work by Tobias Mueller.
+- Several new methods functions were added to the gpg.Context(),
+ including: [[#howto-import-key][key_import]], [[#howto-export-key][key_export]], [[#howto-export-public-key][key_export_minimal]] and
+ [[#howto-export-secret-key][key_export_secret]].
+- Importing and exporting examples include versions integrated with
+ Marcel Fest's recently released [[https://github.com/Selfnet/hkp4py][HKP for Python]] module. Some
+ [[#hkp4py][additional notes on this module]] are included at the end of the HOWTO.
+- Instructions for dealing with semi-walled garden implementations
+ like ProtonMail are also included. This is intended to make things
+ a little easier when communicating with users of ProtonMail's
+ services and should not be construed as an endorsement of said
+ service. The GnuPG Project neither favours, nor disfavours
+ ProtonMail and the majority of this deals with interacting with the
+ ProtonMail keyserver.
+- Semi-formalised the location where [[#draft-editions][draft versions]] of this HOWTO may
+ periodically be accessible. This is both for the reference of
+ others and testing the publishing of the document itself. Renamed
+ this file at around the same time.
+- The Texinfo documentation build configuration has been replicated
+ from the parent project in order to make to maintain consistency
+ with that project (and actually ship with each release).
+- a reStructuredText (=.rst=) version is also generated for Python
+ developers more used to and comfortable with that format as it is
+ the standard Python documentation format and Python developers may
+ wish to use it with Sphinx. Please note that there has been no
+ testing of the reStructuredText version with Sphinx at all. The
+ reST file was generated by the simple expedient of using [[https://pandoc.org/][Pandoc]].
+- Added a new section for [[#advanced-use][advanced or experimental use]].
+- Began the advanced use cases with [[#cython][a section]] on using the module with
+ [[http://cython.org/][Cython]].
+- Added a number of new scripts to the =example/howto/= directory;
+ some of which may be in advance of their planned sections of the
+ HOWTO (and some are just there because it seemed like a good idea at
+ the time).
+- Cleaned up a lot of things under the hood.
+
+
+* GPGME Concepts
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-concepts
+ :END:
+
+
+** A C API
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-c-api
+ :END:
+
+Unlike many modern APIs with which programmers will be more familiar
+with these days, the GPGME API is a C API. The API is intended for
+use by C coders who would be able to access its features by including
+the =gpgme.h= header file with their own C source code and then access
+its functions just as they would any other C headers.
+
+This is a very effective method of gaining complete access to the API
+and in the most efficient manner possible. It does, however, have the
+drawback that it cannot be directly used by other languages without
+some means of providing an interface to those languages. This is
+where the need for bindings in various languages stems.
+
+
+** Python bindings
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-bindings
+ :END:
+
+The Python bindings for GPGME provide a higher level means of
+accessing the complete feature set of GPGME itself. It also provides
+a more pythonic means of calling these API functions.
+
+The bindings are generated dynamically with SWIG and the copy of
+=gpgme.h= generated when GPGME is compiled.
+
+This means that a version of the Python bindings is fundamentally tied
+to the exact same version of GPGME used to generate that copy of
+=gpgme.h=.
+
+
+** Difference between the Python bindings and other GnuPG Python packages
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-bindings-diffs
+ :END:
+
+There have been numerous attempts to add GnuPG support to Python over
+the years. Some of the most well known are listed here, along with
+what differentiates them.
+
+
+*** The python-gnupg package maintained by Vinay Sajip
+ :PROPERTIES:
+ :CUSTOM_ID: diffs-python-gnupg
+ :END:
+
+This is arguably the most popular means of integrating GPG with
+Python. The package utilises the =subprocess= module to implement
+wrappers for the =gpg= and =gpg2= executables normally invoked on the
+command line (=gpg.exe= and =gpg2.exe= on Windows).
+
+The popularity of this package stemmed from its ease of use and
+capability in providing the most commonly required features.
+
+Unfortunately it has been beset by a number of security issues in the
+past; most of which stemmed from using unsafe methods of accessing the
+command line via the =subprocess= calls. While some effort has been
+made over the last two to three years (as of 2018) to mitigate this,
+particularly by no longer providing shell access through those
+subprocess calls, the wrapper is still somewhat limited in the scope
+of its GnuPG features coverage.
+
+The python-gnupg package is available under the MIT license.
+
+
+*** The gnupg package created and maintained by Isis Lovecruft
+ :PROPERTIES:
+ :CUSTOM_ID: diffs-isis-gnupg
+ :END:
+
+In 2015 Isis Lovecruft from the Tor Project forked and then
+re-implemented the python-gnupg package as just gnupg. This new
+package also relied on subprocess to call the =gpg= or =gpg2=
+binaries, but did so somewhat more securely.
+
+The naming and version numbering selected for this package, however,
+resulted in conflicts with the original python-gnupg and since its
+functions were called in a different manner to python-gnupg, the
+release of this package also resulted in a great deal of consternation
+when people installed what they thought was an upgrade that
+subsequently broke the code relying on it.
+
+The gnupg package is available under the GNU General Public License
+version 3.0 (or any later version).
+
+
+*** The PyME package maintained by Martin Albrecht
+ :PROPERTIES:
+ :CUSTOM_ID: diffs-pyme
+ :END:
+
+This package is the origin of these bindings, though they are somewhat
+different now. For details of when and how the PyME package was
+folded back into GPGME itself see the [[file:short-history.org][Short History]] document.[fn:1]
+
+The PyME package was first released in 2002 and was also the first
+attempt to implement a low level binding to GPGME. In doing so it
+provided access to considerably more functionality than either the
+=python-gnupg= or =gnupg= packages.
+
+The PyME package is only available for Python 2.6 and 2.7.
+
+Porting the PyME package to Python 3.4 in 2015 is what resulted in it
+being folded into the GPGME project and the current bindings are the
+end result of that effort.
+
+The PyME package is available under the same dual licensing as GPGME
+itself: the GNU General Public License version 2.0 (or any later
+version) and the GNU Lesser General Public License version 2.1 (or any
+later version).
+
+
+* GPGME Python bindings installation
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-install
+ :END:
+
+
+** No PyPI
+ :PROPERTIES:
+ :CUSTOM_ID: do-not-use-pypi
+ :END:
+
+Most third-party Python packages and modules are available and
+distributed through the Python Package Installer, known as PyPI.
+
+Due to the nature of what these bindings are and how they work, it is
+infeasible to install the GPGME Python bindings in the same way.
+
+This is because the bindings use SWIG to dynamically generate C
+bindings against =gpgme.h= and =gpgme.h= is generated from
+=gpgme.h.in= at compile time when GPGME is built from source. Thus to
+include a package in PyPI which actually built correctly would require
+either statically built libraries for every architecture bundled with
+it or a full implementation of C for each architecture.
+
+See the additional notes regarding [[#snafu-cffi][CFFI and SWIG]] at the end of this
+section for further details.
+
+
+** Requirements
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-requirements
+ :END:
+
+The GPGME Python bindings only have three requirements:
+
+1. A suitable version of Python 2 or Python 3. With Python 2 that
+ means CPython 2.7 and with Python 3 that means CPython 3.4 or
+ higher.
+2. [[https://www.swig.org][SWIG]].
+3. GPGME itself. Which also means that all of GPGME's dependencies
+ must be installed too.
+
+
+*** Recommended Additions
+ :PROPERTIES:
+ :CUSTOM_ID: gpgme-python-recommendations
+ :END:
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings. In some cases these
+recommendations refer to which version(s) of CPython to use the
+bindings with, while others refer to third party modules which provide
+a significant advantage in some way.
+
+1. If possible, use Python 3 instead of 2.
+2. Favour a more recent version of Python since even 3.4 is due to
+ reach EOL soon. In production systems and services, Python 3.6
+ should be robust enough to be relied on.
+3. If possible add the following Python modules which are not part of
+ the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[http://cython.org/][Cython]] and [[https://github.com/Selfnet/hkp4py][hkp4py]]. Chances are
+ quite high that at least the first one and maybe two of those will
+ already be installed.
+
+Note that, as with Cython, some of the planned additions to the
+[[#advanced-use][Advanced]] section, will bring with them additional requirements. Most
+of these will be fairly well known and commonly installed ones,
+however, which are in many cases likely to have already been installed
+on many systems or be familiar to Python programmers.
+
+
+** Installation
+ :PROPERTIES:
+ :CUSTOM_ID: installation
+ :END:
+
+Installing the Python bindings is effectively achieved by compiling
+and installing GPGME itself.
+
+Once SWIG is installed with Python and all the dependencies for GPGME
+are installed you only need to confirm that the version(s) of Python
+you want the bindings installed for are in your =$PATH=.
+
+By default GPGME will attempt to install the bindings for the most
+recent or highest version number of Python 2 and Python 3 it detects
+in =$PATH=. It specifically checks for the =python= and =python3=
+executables first and then checks for specific version numbers.
+
+For Python 2 it checks for these executables in this order: =python=,
+=python2= and =python2.7=.
+
+For Python 3 it checks for these executables in this order: =python3=,
+ =python3.7=, =python3.6=, =python3.5= and =python3.4=.[fn:2]
+
+On systems where =python= is actually =python3= and not =python2= it
+may be possible that =python2= may be overlooked, but there have been
+no reports of that actually occurring as yet.
+
+In the three months or so since the release of Python 3.7.0 there has
+been extensive testing and work with these bindings with no issues
+specifically relating to the new version of Python or any of the new
+features of either the language or the bindings. This has also been
+the case with Python 3.7.1rc1. With that in mind and given the
+release of Python 3.7.1 is scheduled for around the same time as GPGME
+1.12.0, the order of preferred Python versions has been changed to
+move Python 3.7 ahead of Python 3.6.
+
+
+*** Installing GPGME
+ :PROPERTIES:
+ :CUSTOM_ID: install-gpgme
+ :END:
+
+See the GPGME =README= file for details of how to install GPGME from
+source.
+
+
+** Known Issues
+ :PROPERTIES:
+ :CUSTOM_ID: snafu
+ :END:
+
+There are a few known issues with the current build process and the
+Python bindings. For the most part these are easily addressed should
+they be encountered.
+
+
+*** Breaking Builds
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-a-swig-of-this-builds-character
+ :END:
+
+Occasionally when installing GPGME with the Python bindings included
+it may be observed that the =make= portion of that process induces a
+large very number of warnings and, eventually errors which end that
+part of the build process. Yet following that with =make check= and
+=make install= appears to work seamlessly.
+
+The cause of this is related to the way SWIG needs to be called to
+dynamically generate the C bindings for GPGME in the first place. So
+the entire process will always produce =lang/python/python2-gpg/= and
+=lang/python/python3-gpg/= directories. These should contain the
+build output generated during compilation, including the complete
+bindings and module installed into =site-packages=.
+
+Occasionally the errors in the early part or some other conflict
+(e.g. not installing as */root/* or */su/*) may result in nothing
+being installed to the relevant =site-packages= directory and the
+build directory missing a lot of expected files. Even when this
+occurs, the solution is actually quite simple and will always work.
+
+That solution is simply to run the following commands as either the
+*root* user or prepended with =sudo -H=[fn:3] in the =lang/python/=
+directory:
+
+#+BEGIN_SRC shell
+ /path/to/pythonX.Y setup.py build
+ /path/to/pythonX.Y setup.py build
+ /path/to/pythonX.Y setup.py install
+#+END_SRC
+
+Yes, the build command does need to be run twice. Yes, you still need
+to run the potentially failing or incomplete steps during the
+=configure=, =make= and =make install= steps with installing GPGME.
+This is because those steps generate a lot of essential files needed,
+both by and in order to create, the bindings (including both the
+=setup.py= and =gpgme.h= files).
+
+
+**** IMPORTANT Note
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-swig-build-note
+ :END:
+
+If specifying a selected number of languages to create bindings for,
+try to leave Python last. Currently the majority of the other
+language bindings are also preceding Python of either version when
+listed alphabetically and so that just happens by default currently.
+
+If Python is set to precede one of the other languages then it is
+possible that the errors described here may interrupt the build
+process before generating bindings for those other languages. In
+these cases it may be preferable to configure all preferred language
+bindings separately with alternative =configure= steps for GPGME using
+the =--enable-languages=$LANGUAGE= option.
+
+
+*** Reinstalling Responsibly
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-lessons-for-the-lazy
+ :END:
+
+Regardless of whether you're installing for one version of Python or
+several, there will come a point where reinstallation is required.
+With most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about. Then
+the module is upgraded, the new files are copied over the old and
+that's the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant =site-packages= directory for the version of Python in
+question and removing the =gpg/= directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on
+the system, but the same is true of installing the module in the first
+place.
+
+
+*** Multiple installations
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-the-full-monty
+ :END:
+
+For a veriety of reasons it may be either necessary or just preferable
+to install the bindings to alternative installed Python versions which
+meet the requirements of these bindings.
+
+On POSIX systems this will generally be most simply achieved by
+running the manual installation commands (build, build, install) as
+described in the previous section for each Python installation the
+bindings need to be installed to.
+
+As per the SWIG documentation: the compilers, libraries and runtime
+used to build GPGME and the Python Bindings *must* match those used to
+compile Python itself, including the version number(s) (at least going
+by major version numbers and probably minor numbers too).
+
+On most POSIX systems, including OS X, this will very likely be the
+case in most, if not all, cases.
+
+
+*** Won't Work With Windows
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-runtime-not-funtime
+ :END:
+
+There are semi-regular reports of Windows users having considerable
+difficulty in installing and using the Python bindings at all. Very
+often, possibly even always, these reports come from Cygwin users
+and/or MinGW users and/or Msys2 users. Though not all of them have
+been confirmed, it appears that these reports have also come from
+people who installed Python using the Windows installer files from the
+[[https://python.org][Python website]] (i.e. mostly MSI installers, sometimes self-extracting
+=.exe= files).
+
+The Windows versions of Python are not built using Cygwin, MinGW or
+Msys2; they're built using Microsoft Visual Studio. Furthermore the
+version used is /considerably/ more advanced than the version which
+MinGW obtained a small number of files from many years ago in order to
+be able to compile anything at all. Not only that, but there are
+changes to the version of Visual Studio between some micro releases,
+though that is is particularly the case with Python 2.7, since it has
+been kept around far longer than it should have been.
+
+There are two theoretical solutions to this issue:
+
+ 1. Compile and install the GnuPG stack, including GPGME and the
+ Python bibdings using the same version of Microsoft Visual Studio
+ used by the Python Foundation to compile the version of Python
+ installed.
+
+ If there are multiple versions of Python then this will need to be
+ done with each different version of Visual Studio used.
+
+ 2. Compile and install Python using the same tools used by choice,
+ such as MinGW or Msys2.
+
+Do *not* use the official Windows installer for Python unless
+following the first method.
+
+In this type of situation it may even be for the best to accept that
+there are less limitations on permissive software than free software
+and simply opt to use a recent version of the Community Edition of
+Microsoft Visual Studio to compile and build all of it, no matter
+what.
+
+Investigations into the extent or the limitations of this issue are
+ongoing.
+
+
+*** CFFI is the Best⢠and GPGME should use it instead of SWIG
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-cffi
+ :END:
+
+There are many reasons for favouring [[https://cffi.readthedocs.io/en/latest/overview.html][CFFI]] and proponents of it are
+quite happy to repeat these things as if all it would take to switch
+from SWIG to CFFI is repeating that list as if it were a new concept.
+
+The fact is that there are things which Python's CFFI implementation
+cannot handle in the GPGME C code. Beyond that there are features of
+SWIG which are simply not available with CFFI at all. SWIG generates
+the bindings to Python using the =gpgme.h= file, but that file is not
+a single version shipped with each release, it too is generated when
+GPGME is compiled.
+
+CFFI is currently unable to adapt to such a potentially mutable
+codebase. If there were some means of applying SWIG's dynamic code
+generation to produce the Python/CFFI API modes of accessing the GPGME
+libraries (or the source source code directly), but such a thing does
+not exist yet either and it currently appears that work is needed in
+at least one of CFFI's dependencies before any of this can be
+addressed.
+
+So if you're a massive fan of CFFI; that's great, but if you want this
+project to switch to CFFI then rather than just insisting that it
+should, I'd suggest you volunteer to bring CFFI up to the level this
+project needs.
+
+If you're actually seriously considering doing so, then I'd suggest
+taking the =gpgme-tool.c= file in the GPGME =src/= directory and
+getting that to work with any of the CFFI API methods (not the ABI
+methods, they'll work with pretty much anything). When you start
+running into trouble with "ifdefs" then you'll know what sort of
+things are lacking. That doesn't even take into account the amount of
+work saved via SWIG's code generation techniques either.
+
+
+*** Virtualised Environments
+ :PROPERTIES:
+ :CUSTOM_ID: snafu-venv
+ :END:
+
+It is fairly common practice amongst Python developers to, as much as
+possible, use packages like virtualenv to keep various things that are
+to be installed from interfering with each other. Given how much of
+the GPGME bindings is often at odds with the usual pythonic way of
+doing things, it stands to reason that this would be called into
+question too.
+
+As it happens the answer as to whether or not the bindings can be used
+with virtualenv, the answer is both yes and no.
+
+In general we recommend installing to the relevant path and matching
+prefix of GPGME itself. Which means that when GPGME, and ideally the
+rest of the GnuPG stack, is installed to a prefix like =/usr/local= or
+=/opt/local= then the bindings would need to be installed to the main
+Python installation and not a virtualised abstraction. Attempts to
+separate the two in the past have been known to cause weird and
+intermittent errors ranging from minor annoyances to complete failures
+in the build process.
+
+As a consequence we only recommend building with and installing to the
+main Python installations within the same prefix as GPGME is installed
+to or which are found by GPGME's configuration stage immediately prior
+to running the make commands. Which is exactly what the compiling and
+installing process of GPGME does by default.
+
+Once that is done, however, it appears that a copy the compiled module
+may be installed into a virtualenv of the same major and minor version
+matching the build. Alternatively it is possible to utilise a
+=sites.pth= file in the =site-packages/= directory of a viertualenv
+installation, which links back to the system installations
+corresponding directory in order to import anything installed system
+wide. This may or may not be appropriate on a case by case basis.
+
+Though extensive testing of either of these options is not yet
+complete, preliminary testing of them indicates that both are viable
+as long as the main installation is complete. Which means that
+certain other options normally restricted to virtual environments are
+also available, including integration with pythonic test suites
+(e.g. [[https://docs.pytest.org/en/latest/index.html][pytest]]) and other large projects.
+
+That said, it is worth reiterating the warning regarding non-standard
+installations. If one were to attempt to install the bindings only to
+a virtual environment without somehow also including the full GnuPG
+stack (or enough of it as to include GPGME) then it is highly likely
+that errors would be encountered at some point and more than a little
+likely that the build process itself would break.
+
+If a degree of separation from the main operating system is still
+required in spite of these warnings, then consider other forms of
+virtualisation. Either a virtual machine (e.g. [[https://www.virtualbox.org/][VirtualBox]]), a
+hardware emulation layer (e.g. [[https://www.qemu.org/][QEMU]]) or an application container
+(e.g. [[https://www.docker.com/why-docker][Docker]]).
+
+Finally it should be noted that the limited tests conducted thus far
+have been using the =virtualenv= command in a new directory to create
+the virtual python environment. As opposed to the standard =python3
+-m venv= and it is possible that this will make a difference depending
+on the system and version of Python in use. Another option is to run
+the command =python3 -m virtualenv /path/to/install/virtual/thingy=
+instead.
+
+
+* Fundamentals
+ :PROPERTIES:
+ :CUSTOM_ID: howto-fund-a-mental
+ :END:
+
+Before we can get to the fun stuff, there are a few matters regarding
+GPGME's design which hold true whether you're dealing with the C code
+directly or these Python bindings.
+
+
+** No REST
+ :PROPERTIES:
+ :CUSTOM_ID: no-rest-for-the-wicked
+ :END:
+
+The first part of which is or will be fairly blatantly obvious upon
+viewing the first example, but it's worth reiterating anyway. That
+being that this API is /*not*/ a REST API. Nor indeed could it ever
+be one.
+
+Most, if not all, Python programmers (and not just Python programmers)
+know how easy it is to work with a RESTful API. In fact they've
+become so popular that many other APIs attempt to emulate REST-like
+behaviour as much as they are able. Right down to the use of JSON
+formatted output to facilitate the use of their API without having to
+retrain developers.
+
+This API does not do that. It would not be able to do that and also
+provide access to the entire C API on which it's built. It does,
+however, provide a very pythonic interface on top of the direct
+bindings and it's this pythonic layer that this HOWTO deals with.
+
+
+** Context
+ :PROPERTIES:
+ :CUSTOM_ID: howto-get-context
+ :END:
+
+One of the reasons which prevents this API from being RESTful is that
+most operations require more than one instruction to the API to
+perform the task. Sure, there are certain functions which can be
+performed simultaneously, particularly if the result known or strongly
+anticipated (e.g. selecting and encrypting to a key known to be in the
+public keybox).
+
+There are many more, however, which cannot be manipulated so readily:
+they must be performed in a specific sequence and the result of one
+operation has a direct bearing on the outcome of subsequent
+operations. Not merely by generating an error either.
+
+When dealing with this type of persistent state on the web, full of
+both the RESTful and REST-like, it's most commonly referred to as a
+session. In GPGME, however, it is called a context and every
+operation type has one.
+
+
+* Working with keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-keys
+ :END:
+
+
+** Key selection
+ :PROPERTIES:
+ :CUSTOM_ID: howto-keys-selection
+ :END:
+
+Selecting keys to encrypt to or to sign with will be a common
+occurrence when working with GPGMe and the means available for doing
+so are quite simple.
+
+They do depend on utilising a Context; however once the data is
+recorded in another variable, that Context does not need to be the
+same one which subsequent operations are performed.
+
+The easiest way to select a specific key is by searching for that
+key's key ID or fingerprint, preferably the full fingerprint without
+any spaces in it. A long key ID will probably be okay, but is not
+advised and short key IDs are already a problem with some being
+generated to match specific patterns. It does not matter whether the
+pattern is upper or lower case.
+
+So this is the best method:
+
+#+BEGIN_SRC python -i
+import gpg
+
+k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+keys = list(k)
+#+END_SRC
+
+This is passable and very likely to be common:
+
+#+BEGIN_SRC python -i
+import gpg
+
+k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+keys = list(k)
+#+END_SRC
+
+And this is a really bad idea:
+
+#+BEGIN_SRC python -i
+import gpg
+
+k = gpg.Context().keylist(pattern="0xDEADBEEF")
+keys = list(k)
+#+END_SRC
+
+Alternatively it may be that the intention is to create a list of keys
+which all match a particular search string. For instance all the
+addresses at a particular domain, like this:
+
+#+BEGIN_SRC python -i
+import gpg
+
+ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+nsa = list(ncsc)
+#+END_SRC
+
+
+*** Counting keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-keys-counting
+ :END:
+
+Counting the number of keys in your public keybox (=pubring.kbx=), the
+format which has superseded the old keyring format (=pubring.gpg= and
+=secring.gpg=), or the number of secret keys is a very simple task.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+seckeys = c.keylist(pattern=None, secret=True)
+pubkeys = c.keylist(pattern=None, secret=False)
+
+seclist = list(seckeys)
+secnum = len(seclist)
+
+publist = list(pubkeys)
+pubnum = len(publist)
+
+print("""
+ Number of secret keys: {0}
+ Number of public keys: {1}
+""".format(secnum, pubnum))
+#+END_SRC
+
+NOTE: The [[#cython][Cython]] introduction in the [[#advanced-use][Advanced and Experimental]]
+section uses this same key counting code with Cython to demonstrate
+some areas where Cython can improve performance even with the
+bindings. Users with large public keyrings or keyboxes, for instance,
+should consider these options if they are comfortable with using
+Cython.
+
+
+** Get key
+ :PROPERTIES:
+ :CUSTOM_ID: howto-get-key
+ :END:
+
+An alternative method of getting a single key via its fingerprint is
+available directly within a Context with =Context().get_key=. This is
+the preferred method of selecting a key in order to modify it, sign or
+certify it and for obtaining relevant data about a single key as a
+part of other functions; when verifying a signature made by that key,
+for instance.
+
+By default this method will select public keys, but it can select
+secret keys as well.
+
+This first example demonstrates selecting the current key of Werner
+Koch, which is due to expire at the end of 2018:
+
+#+BEGIN_SRC python -i
+import gpg
+
+fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+key = gpg.Context().get_key(fingerprint)
+#+END_SRC
+
+Whereas this example demonstrates selecting the author's current key
+with the =secret= key word argument set to =True=:
+
+#+BEGIN_SRC python -i
+import gpg
+
+fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+key = gpg.Context().get_key(fingerprint, secret=True)
+#+END_SRC
+
+It is, of course, quite possible to select expired, disabled and
+revoked keys with this function, but only to effectively display
+information about those keys.
+
+It is also possible to use both unicode or string literals and byte
+literals with the fingerprint when getting a key in this way.
+
+
+** Importing keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-import-key
+ :END:
+
+Importing keys is possible with the =key_import()= method and takes
+one argument which is a bytes literal object containing either the
+binary or ASCII armoured key data for one or more keys.
+
+The following example retrieves one or more keys from the SKS
+keyservers via the web using the requests module. Since requests
+returns the content as a bytes literal object, we can then use that
+directly to import the resulting data into our keybox.
+
+#+BEGIN_SRC python -i
+import gpg
+import os.path
+import requests
+
+c = gpg.Context()
+url = "https://sks-keyservers.net/pks/lookup"
+pattern = input("Enter the pattern to search for key or user IDs: ")
+payload = {"op": "get", "search": pattern}
+
+r = requests.get(url, verify=True, params=payload)
+result = c.key_import(r.content)
+
+if result is not None and hasattr(result, "considered") is False:
+ print(result)
+elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ Number of keys revoked: {1}
+ Number of new signatures: {2}
+ Number of new subkeys: {3}
+ Number of new user IDs: {4}
+ Number of new secret keys: {5}
+ Number of unchanged keys: {6}
+
+ The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print("{0}\n".format(result.imports[i].fpr))
+else:
+ pass
+#+END_SRC
+
+NOTE: When searching for a key ID of any length or a fingerprint
+(without spaces), the SKS servers require the the leading =0x=
+indicative of hexadecimal be included. Also note that the old short
+key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the
+relative ease by which such key IDs can be reproduced, as demonstrated
+by the Evil32 Project in 2014 (which was subsequently exploited in
+2016).
+
+
+*** Working with ProtonMail
+ :PROPERTIES:
+ :CUSTOM_ID: import-protonmail
+ :END:
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+#+BEGIN_SRC python -i
+import gpg
+import requests
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+""")
+
+c = gpg.Context(armor=True)
+url = "https://api.protonmail.ch/pks/lookup"
+ksearch = []
+
+if len(sys.argv) >= 2:
+ keyterm = sys.argv[1]
+else:
+ keyterm = input("Enter the key ID, UID or search string: ")
+
+if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+ ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+ ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+ ksearch.append("{0}@pm.me".format(keyterm[1:]))
+elif keyterm.count("@") == 0:
+ ksearch.append("{0}@protonmail.com".format(keyterm))
+ ksearch.append("{0}@protonmail.ch".format(keyterm))
+ ksearch.append("{0}@pm.me".format(keyterm))
+elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+elif keyterm.count("@") > 2:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+else:
+ ksearch.append(keyterm)
+
+for k in ksearch:
+ payload = {"op": "get", "search": k}
+ try:
+ r = requests.get(url, verify=True, params=payload)
+ if r.ok is True:
+ result = c.key_import(r.content)
+ elif r.ok is False:
+ result = r.content
+ except Exception as e:
+ result = None
+
+ if result is not None and hasattr(result, "considered") is False:
+ print("{0} for {1}".format(result.decode(), k))
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+The total number of keys considered for import was: {0}
+
+With UIDs wholely or partially matching the following string:
+
+ {1}
+
+ Number of keys revoked: {2}
+ Number of new signatures: {3}
+ Number of new subkeys: {4}
+ Number of new user IDs: {5}
+Number of new secret keys: {6}
+ Number of unchanged keys: {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ elif result is None:
+ print(e)
+#+END_SRC
+
+Both the above example, [[../examples/howto/pmkey-import.py][pmkey-import.py]], and a version which prompts
+for an alternative GnuPG home directory, [[../examples/howto/pmkey-import-alt.py][pmkey-import-alt.py]], are
+available with the other examples and are executable scripts.
+
+Note that while the ProtonMail servers are based on the SKS servers,
+their server is related more to their API and is not feature complete
+by comparison to the servers in the SKS pool. One notable difference
+being that the ProtonMail server does not permit non ProtonMail users
+to update their own keys, which could be a vector for attacking
+ProtonMail users who may not receive a key's revocation if it had been
+compromised.
+
+
+*** Importing with HKP for Python
+ :PROPERTIES:
+ :CUSTOM_ID: import-hkp4py
+ :END:
+
+Performing the same tasks with the [[https://github.com/Selfnet/hkp4py][hkp4py module]] (available via PyPI)
+is not too much different, but does provide a number of options of
+benefit to end users. Not least of which being the ability to perform
+some checks on a key before importing it or not. For instance it may
+be the policy of a site or project to only import keys which have not
+been revoked. The hkp4py module permits such checks prior to the
+importing of the keys found.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import sys
+
+c = gpg.Context()
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+results = []
+
+if len(sys.argv) > 2:
+ pattern = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+ pattern = sys.argv[1]
+else:
+ pattern = input("Enter the pattern to search for keys or user IDs: ")
+
+try:
+ keys = server.search(pattern)
+ print("Found {0} key(s).".format(len(keys)))
+except Exception as e:
+ keys = []
+ for logrus in pattern.split():
+ if logrus.startswith("0x") is True:
+ key = server.search(logrus)
+ else:
+ key = server.search("0x{0}".format(logrus))
+ keys.append(key[0])
+ print("Found {0} key(s).".format(len(keys)))
+
+for key in keys:
+ import_result = c.key_import(key.key_blob)
+ results.append(import_result)
+
+for result in results:
+ if result is not None and hasattr(result, "considered") is False:
+ print(result)
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+The total number of keys considered for import was: {0}
+
+ Number of keys revoked: {1}
+ Number of new signatures: {2}
+ Number of new subkeys: {3}
+ Number of new user IDs: {4}
+Number of new secret keys: {5}
+ Number of unchanged keys: {6}
+
+The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ else:
+ pass
+#+END_SRC
+
+Since the hkp4py module handles multiple keys just as effectively as
+one (=keys= is a list of responses per matching key), the example
+above is able to do a little bit more with the returned data before
+anything is actually imported.
+
+
+*** Importing from ProtonMail with HKP for Python
+ :PROPERTIES:
+ :CUSTOM_ID: import-protonmail-hkp4py
+ :END:
+
+Though this can provide certain benefits even when working with
+ProtonMail, the scope is somewhat constrained there due to the
+limitations of the ProtonMail keyserver.
+
+For instance, searching the SKS keyserver pool for the term "gnupg"
+produces hundreds of results from any time the word appears in any
+part of a user ID. Performing the same search on the ProtonMail
+keyserver returns zero results, even though there are at least two
+test accounts which include it as part of the username.
+
+The cause of this discrepancy is the deliberate configuration of that
+server by ProtonMail to require an exact match of the full email
+address of the ProtonMail user whose key is being requested.
+Presumably this is intended to reduce breaches of privacy of their
+users as an email address must already be known before a key for that
+address can be obtained.
+
+
+**** Import from ProtonMail via HKP for Python Example no. 1
+ :PROPERTIES:
+ :CUSTOM_ID: import-hkp4py-pm1
+ :END:
+
+The following script is avalable with the rest of the examples under
+the somewhat less than original name, =pmkey-import-hkp.py=.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+
+Usage: pmkey-import-hkp.py [search strings]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 2:
+ keyterms = sys.argv[1:]
+elif len(sys.argv) == 2:
+ keyterm = sys.argv[1]
+ keyterms.append(keyterm)
+else:
+ key_term = input("Enter the key ID, UID or search string: ")
+ keyterms = key_term.split()
+
+for keyterm in keyterms:
+ if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+ ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+ ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+ ksearch.append("{0}@pm.me".format(keyterm[1:]))
+ elif keyterm.count("@") == 0:
+ ksearch.append("{0}@protonmail.com".format(keyterm))
+ ksearch.append("{0}@protonmail.ch".format(keyterm))
+ ksearch.append("{0}@pm.me".format(keyterm))
+ elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ elif keyterm.count("@") > 2:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ else:
+ ksearch.append(keyterm)
+
+for k in ksearch:
+ print("Checking for key for: {0}".format(k))
+ try:
+ keys = server.search(k)
+ if isinstance(keys, list) is True:
+ for key in keys:
+ allkeys.append(key)
+ try:
+ import_result = c.key_import(key.key_blob)
+ except Exception as e:
+ import_result = c.key_import(key.key)
+ else:
+ paradox.append(keys)
+ import_result = None
+ except Exception as e:
+ import_result = None
+ results.append(import_result)
+
+for result in results:
+ if result is not None and hasattr(result, "considered") is False:
+ print("{0} for {1}".format(result.decode(), k))
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+The total number of keys considered for import was: {0}
+
+With UIDs wholely or partially matching the following string:
+
+ {1}
+
+ Number of keys revoked: {2}
+ Number of new signatures: {3}
+ Number of new subkeys: {4}
+ Number of new user IDs: {5}
+Number of new secret keys: {6}
+ Number of unchanged keys: {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ elif result is None:
+ pass
+#+END_SRC
+
+
+**** Import from ProtonMail via HKP for Python Example no. 2
+ :PROPERTIES:
+ :CUSTOM_ID: import-hkp4py-pm2
+ :END:
+
+Like its counterpart above, this script can also be found with the
+rest of the examples, by the name pmkey-import-hkp-alt.py.
+
+With this script a modicum of effort has been made to treat anything
+passed as a =homedir= which either does not exist or which is not a
+directory, as also being a pssible user ID to check for. It's not
+guaranteed to pick up on all such cases, but it should cover most of
+them.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it. Optionally enables specifying a different GnuPG home directory.
+
+Usage: pmkey-import-hkp.py [homedir] [search string]
+ or: pmkey-import-hkp.py [search string]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 3:
+ homedir = sys.argv[1]
+ keyterms = sys.argv[2:]
+elif len(sys.argv) == 3:
+ homedir = sys.argv[1]
+ keyterm = sys.argv[2]
+ keyterms.append(keyterm)
+elif len(sys.argv) == 2:
+ homedir = ""
+ keyterm = sys.argv[1]
+ keyterms.append(keyterm)
+else:
+ keyterm = input("Enter the key ID, UID or search string: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ keyterms.append(keyterm)
+
+if len(homedir) == 0:
+ homedir = None
+ homeless = False
+
+if homedir is not None:
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ if os.path.isdir(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.realpath(os.path.expanduser(homedir))
+ else:
+ homeless = True
+ else:
+ homeless = True
+ elif os.path.exists(os.path.realpath(homedir)) is True:
+ if os.path.isdir(os.path.realpath(homedir)) is True:
+ c.home_dir = os.path.realpath(homedir)
+ else:
+ homeless = True
+ else:
+ homeless = True
+
+# First check to see if the homedir really is a homedir and if not, treat it as
+# a search string.
+if homeless is True:
+ keyterms.append(homedir)
+ c.home_dir = None
+else:
+ pass
+
+for keyterm in keyterms:
+ if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ ksearch.append(keyterm[1:])
+ elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+ ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+ ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+ ksearch.append("{0}@pm.me".format(keyterm[1:]))
+ elif keyterm.count("@") == 0:
+ ksearch.append("{0}@protonmail.com".format(keyterm))
+ ksearch.append("{0}@protonmail.ch".format(keyterm))
+ ksearch.append("{0}@pm.me".format(keyterm))
+ elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ elif keyterm.count("@") > 2:
+ uidlist = keyterm.split("@")
+ for uid in uidlist:
+ ksearch.append("{0}@protonmail.com".format(uid))
+ ksearch.append("{0}@protonmail.ch".format(uid))
+ ksearch.append("{0}@pm.me".format(uid))
+ else:
+ ksearch.append(keyterm)
+
+for k in ksearch:
+ print("Checking for key for: {0}".format(k))
+ try:
+ keys = server.search(k)
+ if isinstance(keys, list) is True:
+ for key in keys:
+ allkeys.append(key)
+ try:
+ import_result = c.key_import(key.key_blob)
+ except Exception as e:
+ import_result = c.key_import(key.key)
+ else:
+ paradox.append(keys)
+ import_result = None
+ except Exception as e:
+ import_result = None
+ results.append(import_result)
+
+for result in results:
+ if result is not None and hasattr(result, "considered") is False:
+ print("{0} for {1}".format(result.decode(), k))
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+The total number of keys considered for import was: {0}
+
+With UIDs wholely or partially matching the following string:
+
+ {1}
+
+ Number of keys revoked: {2}
+ Number of new signatures: {3}
+ Number of new subkeys: {4}
+ Number of new user IDs: {5}
+Number of new secret keys: {6}
+ Number of unchanged keys: {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ elif result is None:
+ pass
+#+END_SRC
+
+
+** Exporting keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-export-key
+ :END:
+
+Exporting keys remains a reasonably simple task, but has been
+separated into three different functions for the OpenPGP cryptographic
+engine. Two of those functions are for exporting public keys and the
+third is for exporting secret keys.
+
+
+*** Exporting public keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-export-public-key
+ :END:
+
+There are two methods of exporting public keys, both of which are very
+similar to the other. The default method, =key_export()=, will export
+a public key or keys matching a specified pattern as normal. The
+alternative, the =key_export_minimal()= method, will do the same thing
+except producing a minimised output with extra signatures and third
+party signatures or certifications removed.
+
+#+BEGIN_SRC python -i
+import gpg
+import os.path
+import sys
+
+print("""
+This script exports one or more public keys.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
+ else:
+ pass
+elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+else:
+ pass
+
+try:
+ result = c.key_export(pattern=logrus)
+except:
+ result = c.key_export(pattern=None)
+
+if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+else:
+ pass
+#+END_SRC
+
+It should be noted that the result will only return =None= when a
+search pattern has been entered, but has not matched any keys. When
+the search pattern itself is set to =None= this triggers the exporting
+of the entire public keybox.
+
+#+BEGIN_SRC python -i
+import gpg
+import os.path
+import sys
+
+print("""
+This script exports one or more public keys in minimised form.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
+ else:
+ pass
+elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+else:
+ pass
+
+try:
+ result = c.key_export_minimal(pattern=logrus)
+except:
+ result = c.key_export_minimal(pattern=None)
+
+if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+else:
+ pass
+#+END_SRC
+
+
+*** Exporting secret keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-export-secret-key
+ :END:
+
+Exporting secret keys is, functionally, very similar to exporting
+public keys; save for the invocation of =pinentry= via =gpg-agent= in
+order to securely enter the key's passphrase and authorise the export.
+
+The following example exports the secret key to a file which is then
+set with the same permissions as the output files created by the
+command line secret key export options.
+
+#+BEGIN_SRC python -i
+import gpg
+import os
+import os.path
+import sys
+
+print("""
+This script exports one or more secret keys.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+ homedir = None
+elif homedir.startswith("~"):
+ userdir = os.path.expanduser(homedir)
+ if os.path.exists(userdir) is True:
+ homedir = os.path.realpath(userdir)
+ else:
+ homedir = None
+else:
+ homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+ homedir = None
+else:
+ if os.path.isdir(homedir) is False:
+ homedir = None
+ else:
+ pass
+
+if homedir is not None:
+ c.home_dir = homedir
+else:
+ pass
+
+try:
+ result = c.key_export_secret(pattern=logrus)
+except:
+ result = c.key_export_secret(pattern=None)
+
+if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ os.chmod(keyfile, 0o600)
+else:
+ pass
+#+END_SRC
+
+Alternatively the approach of the following script can be used. This
+longer example saves the exported secret key(s) in files in the GnuPG
+home directory, in addition to setting the file permissions as only
+readable and writable by the user. It also exports the secret key(s)
+twice in order to output both GPG binary (=.gpg=) and ASCII armoured
+(=.asc=) files.
+
+#+BEGIN_SRC python -i
+import gpg
+import os
+import os.path
+import subprocess
+import sys
+
+print("""
+This script exports one or more secret keys as both ASCII armored and binary
+file formats, saved in files within the user's GPG home directory.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+if sys.platform == "win32":
+ gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+else:
+ gpgconfcmd = "gpgconf --list-dirs homedir"
+
+a = gpg.Context(armor=True)
+b = gpg.Context()
+c = gpg.Context()
+
+if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+ keyfile = input("Enter the filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+ homedir = None
+elif homedir.startswith("~"):
+ userdir = os.path.expanduser(homedir)
+ if os.path.exists(userdir) is True:
+ homedir = os.path.realpath(userdir)
+ else:
+ homedir = None
+else:
+ homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+ homedir = None
+else:
+ if os.path.isdir(homedir) is False:
+ homedir = None
+ else:
+ pass
+
+if homedir is not None:
+ c.home_dir = homedir
+else:
+ pass
+
+if c.home_dir is not None:
+ if c.home_dir.endswith("/"):
+ gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
+ ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
+ else:
+ gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
+ ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
+else:
+ if os.path.exists(os.environ["GNUPGHOME"]) is True:
+ hd = os.environ["GNUPGHOME"]
+ else:
+ try:
+ hd = subprocess.getoutput(gpgconfcmd)
+ except:
+ process = subprocess.Popen(gpgconfcmd.split(),
+ stdout=subprocess.PIPE)
+ procom = process.communicate()
+ if sys.version_info[0] == 2:
+ hd = procom[0].strip()
+ else:
+ hd = procom[0].decode().strip()
+ gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
+ ascfile = "{0}/{1}.asc".format(hd, keyfile)
+
+try:
+ a_result = a.key_export_secret(pattern=logrus)
+ b_result = b.key_export_secret(pattern=logrus)
+except:
+ a_result = a.key_export_secret(pattern=None)
+ b_result = b.key_export_secret(pattern=None)
+
+if a_result is not None:
+ with open(ascfile, "wb") as f:
+ f.write(a_result)
+ os.chmod(ascfile, 0o600)
+else:
+ pass
+
+if b_result is not None:
+ with open(gpgfile, "wb") as f:
+ f.write(b_result)
+ os.chmod(gpgfile, 0o600)
+else:
+ pass
+#+END_SRC
+
+
+*** Sending public keys to the SKS Keyservers
+ :PROPERTIES:
+ :CUSTOM_ID: howto-send-public-key
+ :END:
+
+As with the previous section on importing keys, the =hkp4py= module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) > 2:
+ logrus = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+ logrus = sys.argv[1]
+else:
+ logrus = input("Enter the UID matching the key(s) to send: ")
+
+if len(logrus) > 0:
+ try:
+ export_result = c.key_export(pattern=logrus)
+ except Exception as e:
+ print(e)
+ export_result = None
+else:
+ export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+ try:
+ try:
+ send_result = server.add(export_result)
+ except:
+ send_result = server.add(export_result.decode())
+ if send_result is not None:
+ print(send_result)
+ else:
+ pass
+ except Exception as e:
+ print(e)
+else:
+ pass
+#+END_SRC
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples
+directory as =send-key-to-keyserver.py=.
+
+The =hkp4py= module appears to handle both string and byte literal text
+data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
+
+* Basic Functions
+ :PROPERTIES:
+ :CUSTOM_ID: howto-the-basics
+ :END:
+
+The most frequently called features of any cryptographic library will
+be the most fundamental tasks for encryption software. In this
+section we will look at how to programmatically encrypt data, decrypt
+it, sign it and verify signatures.
+
+
+** Encryption
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-encryption
+ :END:
+
+Encrypting is very straight forward. In the first example below the
+message, =text=, is encrypted to a single recipient's key. In the
+second example the message will be encrypted to multiple recipients.
+
+
+*** Encrypting to one key
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-encryption-single
+ :END:
+
+Once the the Context is set the main issues with encrypting data is
+essentially reduced to key selection and the keyword arguments
+specified in the =gpg.Context().encrypt()= method.
+
+Those keyword arguments are: =recipients=, a list of keys encrypted to
+(covered in greater detail in the following section); =sign=, whether
+or not to sign the plaintext data, see subsequent sections on signing
+and verifying signatures below (defaults to =True=); =sink=, to write
+results or partial results to a secure sink instead of returning it
+(defaults to =None=); =passphrase=, only used when utilising symmetric
+encryption (defaults to =None=); =always_trust=, used to override the
+trust model settings for recipient keys (defaults to =False=);
+=add_encrypt_to=, utilises any preconfigured =encrypt-to= or
+=default-key= settings in the user's =gpg.conf= file (defaults to
+=False=); =prepare=, prepare for encryption (defaults to =False=);
+=expect_sign=, prepare for signing (defaults to =False=); =compress=,
+compresses the plaintext prior to encryption (defaults to =True=).
+
+#+BEGIN_SRC python -i
+import gpg
+
+a_key = "0x12345678DEADBEEF"
+text = b"""Some text to test with.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data format.
+"""
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+#+END_SRC
+
+Though this is even more likely to be used like this; with the
+plaintext input read from a file, the recipient keys used for
+encryption regardless of key trust status and the encrypted output
+also encrypted to any preconfigured keys set in the =gpg.conf= file:
+
+#+BEGIN_SRC python -i
+import gpg
+
+a_key = "0x12345678DEADBEEF"
+
+with open("secret_plans.txt", "rb") as afile:
+ text = afile.read()
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
+ always_trust=True,
+ add_encrypt_to=True)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+#+END_SRC
+
+If the =recipients= paramater is empty then the plaintext is encrypted
+symmetrically. If no =passphrase= is supplied as a parameter or via a
+callback registered with the =Context()= then an out-of-band prompt
+for the passphrase via pinentry will be invoked.
+
+
+*** Encrypting to multiple keys
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-encryption-multiple
+ :END:
+
+Encrypting to multiple keys essentially just expands upon the key
+selection process and the recipients from the previous examples.
+
+The following example encrypts a message (=text=) to everyone with an
+email address on the =gnupg.org= domain,[fn:4] but does /not/ encrypt
+to a default key or other key which is configured to normally encrypt
+to.
+
+#+BEGIN_SRC python -i
+import gpg
+
+text = b"""Oh look, another test message.
+
+The same rules apply as with the previous example and more likely
+than not, the message will actually be drawn from reading the
+contents of a file or, maybe, from entering data at an input()
+prompt.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data
+format.
+"""
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ sign=False, always_trust=True)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+#+END_SRC
+
+All it would take to change the above example to sign the message
+and also encrypt the message to any configured default keys would
+be to change the =c.encrypt= line to this:
+
+#+BEGIN_SRC python -i
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ always_trust=True,
+ add_encrypt_to=True)
+#+END_SRC
+
+The only keyword arguments requiring modification are those for which
+the default values are changing. The default value of =sign= is
+=True=, the default of =always_trust= is =False=, the default of
+=add_encrypt_to= is =False=.
+
+If =always_trust= is not set to =True= and any of the recipient keys
+are not trusted (e.g. not signed or locally signed) then the
+encryption will raise an error. It is possible to mitigate this
+somewhat with something more like this:
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("secret_plans.txt.asc", "rb") as afile:
+ text = afile.read()
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
+
+ try:
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ add_encrypt_to=True)
+ except gpg.errors.InvalidRecipients as e:
+ for i in range(len(e.recipients)):
+ for n in range(len(logrus)):
+ if logrus[n].fpr == e.recipients[i].fpr:
+ logrus.remove(logrus[n])
+ else:
+ pass
+ try:
+ ciphertext, result, sign_result = c.encrypt(text,
+ recipients=logrus,
+ add_encrypt_to=True)
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ except:
+ pass
+#+END_SRC
+
+This will attempt to encrypt to all the keys searched for, then remove
+invalid recipients if it fails and try again.
+
+
+** Decryption
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-decryption
+ :END:
+
+Decrypting something encrypted to a key in one's secret keyring is
+fairly straight forward.
+
+In this example code, however, preconfiguring either =gpg.Context()=
+or =gpg.core.Context()= as =c= is unnecessary because there is no need
+to modify the Context prior to conducting the decryption and since the
+Context is only used once, setting it to =c= simply adds lines for no
+gain.
+
+#+BEGIN_SRC python -i
+import gpg
+
+ciphertext = input("Enter path and filename of encrypted file: ")
+newfile = input("Enter path and filename of file to save decrypted data to: ")
+
+with open(ciphertext, "rb") as cfile:
+ try:
+ plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+ except gpg.errors.GPGMEError as e:
+ plaintext = None
+ print(e)
+
+if plaintext is not None:
+ with open(newfile, "wb") as nfile:
+ nfile.write(plaintext)
+ else:
+ pass
+#+END_SRC
+
+The data available in =plaintext= in this example is the decrypted
+content as a byte object, the recipient key IDs and algorithms in
+=result= and the results of verifying any signatures of the data in
+=verify_result=.
+
+
+** Signing text and files
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing
+ :END:
+
+The following sections demonstrate how to specify keys to sign with.
+
+
+*** Signing key selection
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-signers
+ :END:
+
+By default GPGME and the Python bindings will use the default key
+configured for the user invoking the GPGME API. If there is no
+default key specified and there is more than one secret key available
+it may be necessary to specify the key or keys with which to sign
+messages and files.
+
+#+BEGIN_SRC python -i
+import gpg
+
+logrus = input("Enter the email address or string to match signing keys to: ")
+hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+sig_src = list(hancock)
+#+END_SRC
+
+The signing examples in the following sections include the explicitly
+designated =signers= parameter in two of the five examples; once where
+the resulting signature would be ASCII armoured and once where it
+would not be armoured.
+
+While it would be possible to enter a key ID or fingerprint here to
+match a specific key, it is not possible to enter two fingerprints and
+match two keys since the patten expects a string, bytes or None and
+not a list. A string with two fingerprints won't match any single
+key.
+
+
+*** Normal or default signing messages or files
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-normal
+ :END:
+
+The normal or default signing process is essentially the same as is
+most often invoked when also encrypting a message or file. So when
+the encryption component is not utilised, the result is to produce an
+encoded and signed output which may or may not be ASCII armoured and
+which may or may not also be compressed.
+
+By default compression will be used unless GnuPG detects that the
+plaintext is already compressed. ASCII armouring will be determined
+according to the value of =gpg.Context().armor=.
+
+The compression algorithm is selected in much the same way as the
+symmetric encryption algorithm or the hash digest algorithm is when
+multiple keys are involved; from the preferences saved into the key
+itself or by comparison with the preferences with all other keys
+involved.
+
+#+BEGIN_SRC python -i
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True, signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+#+END_SRC
+
+Though everything in this example is accurate, it is more likely that
+reading the input data from another file and writing the result to a
+new file will be performed more like the way it is done in the next
+example. Even if the output format is ASCII armoured.
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+#+END_SRC
+
+
+*** Detached signing messages and files
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-detached
+ :END:
+
+Detached signatures will often be needed in programmatic uses of
+GPGME, either for signing files (e.g. tarballs of code releases) or as
+a component of message signing (e.g. PGP/MIME encoded email).
+
+#+BEGIN_SRC python -i
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+#+END_SRC
+
+As with normal signatures, detached signatures are best handled as
+byte literals, even when the output is ASCII armoured.
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+c = gpg.Context(signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+#+END_SRC
+
+
+*** Clearsigning messages or text
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-signing-clear
+ :END:
+
+Though PGP/in-line messages are no longer encouraged in favour of
+PGP/MIME, there is still sometimes value in utilising in-line
+signatures. This is where clear-signed messages or text is of value.
+
+#+BEGIN_SRC python -i
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+#+END_SRC
+
+In spite of the appearance of a clear-signed message, the data handled
+by GPGME in signing it must still be byte literals.
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "wb") as afile:
+ afile.write(signed_data)
+#+END_SRC
+
+
+** Signature verification
+ :PROPERTIES:
+ :CUSTOM_ID: howto-basic-verification
+ :END:
+
+Essentially there are two principal methods of verification of a
+signature. The first of these is for use with the normal or default
+signing method and for clear-signed messages. The second is for use
+with files and data with detached signatures.
+
+The following example is intended for use with the default signing
+method where the file was not ASCII armoured:
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+gpg_file = "statement.txt.gpg"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(gpg_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+else:
+ pass
+#+END_SRC
+
+Whereas this next example, which is almost identical would work with
+normal ASCII armoured files and with clear-signed files:
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(asc_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+else:
+ pass
+#+END_SRC
+
+In both of the previous examples it is also possible to compare the
+original data that was signed against the signed data in =data= to see
+if it matches with something like this:
+
+#+BEGIN_SRC python -i
+with open(filename, "rb") as afile:
+ text = afile.read()
+
+if text == data:
+ print("Good signature.")
+else:
+ pass
+#+END_SRC
+
+The following two examples, however, deal with detached signatures.
+With his method of verification the data that was signed does not get
+returned since it is already being explicitly referenced in the first
+argument of =c.verify=. So =data= is =None= and only the information
+in =result= is available.
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+sig_file = "statement.txt.sig"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(filename), open(sig_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+else:
+ pass
+#+END_SRC
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+ data, result = c.verify(open(filename), open(asc_file))
+ verified = True
+except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+else:
+ pass
+#+END_SRC
+
+
+* Creating keys and subkeys
+ :PROPERTIES:
+ :CUSTOM_ID: key-generation
+ :END:
+
+The one thing, aside from GnuPG itself, that GPGME depends on, of
+course, is the keys themselves. So it is necessary to be able to
+generate them and modify them by adding subkeys, revoking or disabling
+them, sometimes deleting them and doing the same for user IDs.
+
+In the following examples a key will be created for the world's
+greatest secret agent, Danger Mouse. Since Danger Mouse is a secret
+agent he needs to be able to protect information to =SECRET= level
+clearance, so his keys will be 3072-bit keys.
+
+The pre-configured =gpg.conf= file which sets cipher, digest and other
+preferences contains the following configuration parameters:
+
+#+BEGIN_SRC conf
+ expert
+ allow-freeform-uid
+ allow-secret-key-import
+ trust-model tofu+pgp
+ tofu-default-policy unknown
+ enable-large-rsa
+ enable-dsa2
+ cert-digest-algo SHA512
+ default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+ personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+ personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+ personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+#+END_SRC
+
+
+** Primary key
+ :PROPERTIES:
+ :CUSTOM_ID: keygen-primary
+ :END:
+
+Generating a primary key uses the =create_key= method in a Context.
+It contains multiple arguments and keyword arguments, including:
+=userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=,
+=certify=, =authenticate=, =passphrase= and =force=. The defaults for
+all of those except =userid=, =algorithm=, =expires_in=, =expires= and
+=passphrase= is =False=. The defaults for =algorithm= and
+=passphrase= is =None=. The default for =expires_in= is =0=. The
+default for =expires= is =True=. There is no default for =userid=.
+
+If =passphrase= is left as =None= then the key will not be generated
+with a passphrase, if =passphrase= is set to a string then that will
+be the passphrase and if =passphrase= is set to =True= then gpg-agent
+will launch pinentry to prompt for a passphrase. For the sake of
+convenience, these examples will keep =passphrase= set to =None=.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+
+c.home_dir = "~/.gnupg-dm"
+userid = "Danger Mouse