Merge branch 'master' into javascript-binding

This commit is contained in:
Maximilian Krambach 2018-05-24 14:03:09 +02:00
commit e38b8beb20
9 changed files with 478 additions and 21 deletions

7
NEWS
View File

@ -1,6 +1,13 @@
Noteworthy changes in version 1.11.2 (unreleased)
-------------------------------------------------
* Even for old versions of gpg a missing MDC will now lead to a
decryption failure.
* Interface changes relative to the 1.11.1 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cpp: DecryptionResult::sessionKey NEW.
cpp: DecryptionResult::symkeyAlgo NEW.
Noteworthy changes in version 1.11.1 (2018-04-20)
-------------------------------------------------

View File

@ -5408,7 +5408,7 @@ or @code{gpgme_get_ctx_flag (ctx, "export-session-key")} returns true
@since{1.11.0}
A string with the symmetric encryption algorithm and mode using the
format "<algo>.<mode>". Note that old non-MDC encryption mode of
format "<algo>.<mode>". Note that the deprecated non-MDC encryption mode of
OpenPGP is given as "PGPCFB".
@end table

View File

@ -155,6 +155,16 @@ std::vector<GpgME::DecryptionResult::Recipient> GpgME::DecryptionResult::recipie
return result;
}
const char *GpgME::DecryptionResult::sessionKey() const
{
return d ? d->res.session_key : nullptr;
}
const char *GpgME::DecryptionResult::symkeyAlgo() const
{
return d ? d->res.symkey_algo : nullptr;
}
class GpgME::DecryptionResult::Recipient::Private : public _gpgme_recipient
{
public:
@ -231,6 +241,7 @@ std::ostream &GpgME::operator<<(std::ostream &os, const DecryptionResult &result
<< "\n unsupportedAlgorithm: " << protect(result.unsupportedAlgorithm())
<< "\n isWrongKeyUsage: " << result.isWrongKeyUsage()
<< "\n isDeVs " << result.isDeVs()
<< "\n symkeyAlgo: " << protect(result.symkeyAlgo())
<< "\n recipients:\n";
const std::vector<DecryptionResult::Recipient> recipients = result.recipients();
std::copy(recipients.begin(), recipients.end(),

View File

@ -77,6 +77,10 @@ public:
const char *fileName() const;
const char *sessionKey() const;
const char *symkeyAlgo() const;
class Recipient;
unsigned int numRecipients() const;

View File

@ -14,7 +14,7 @@
:CUSTOM_ID: intro
:END:
| Version: | 0.1.0 |
| Version: | 0.1.1 |
| Author: | Ben McGinnes <ben@gnupg.org> |
| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
| Language: | Australian English, British English |
@ -673,10 +673,17 @@
newfile = input("Enter path and filename of file to save decrypted data to: ")
with open(ciphertext, "rb") as cfile:
plaintext, result, verify_result = gpg.Context().decrypt(cfile)
try:
plaintext, result, verify_result = gpg.Context().decrypt(cfile)
except gpg.errors.GPGMEError as e:
plaintext = None
print(e)
with open(newfile, "wb") as nfile:
nfile.write(plaintext)
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

View File

@ -38,7 +38,14 @@ else:
newfile = input("Enter path and filename of file to save decrypted data to: ")
with open(ciphertext, "rb") as cfile:
plaintext, result, verify_result = gpg.Context().decrypt(cfile)
try:
plaintext, result, verify_result = gpg.Context().decrypt(cfile)
except gpg.errors.GPGMEError as e:
plaintext = None
print(e)
with open(newfile, "wb") as nfile:
nfile.write(plaintext)
if plaintext is not None:
with open(newfile, "wb") as nfile:
nfile.write(plaintext)
else:
pass

View File

@ -56,6 +56,11 @@ typedef struct
* message that the general DECRYPTION_FAILED. */
int any_no_seckey;
/* If the engine emits a DECRYPTION_INFO status and that does not
* indicate that an integrity proetction mode is active, this flag
* is set. */
int not_integrity_protected;
/* A pointer to the next pointer of the last recipient in the list.
This makes appending new invalid signers painless while
preserving the order. */
@ -280,7 +285,7 @@ parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol)
char *field[3];
int nfields;
char *args2;
int mdc, mode;
int mdc, aead_algo;
const char *algostr, *modestr;
if (!args)
@ -296,19 +301,22 @@ parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol)
mdc = atoi (field[0]);
algostr = _gpgme_cipher_algo_name (atoi (field[1]), protocol);
mode = nfields < 3? 0 : atoi (field[2]);
modestr = _gpgme_cipher_mode_name (mode, protocol);
aead_algo = nfields < 3? 0 : atoi (field[2]);
modestr = _gpgme_cipher_mode_name (aead_algo, protocol);
free (args2);
free (opd->result.symkey_algo);
if (!mode && mdc != 2)
if (!aead_algo && mdc != 2)
opd->result.symkey_algo = _gpgme_strconcat (algostr, ".PGPCFB", NULL);
else
opd->result.symkey_algo = _gpgme_strconcat (algostr, ".", modestr, NULL);
if (!opd->result.symkey_algo)
return gpg_error_from_syserror ();
if (!mdc && !aead_algo)
opd->not_integrity_protected = 1;
return 0;
}
@ -338,13 +346,18 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code,
break;
case GPGME_STATUS_EOF:
/* FIXME: These error values should probably be attributed to
the underlying crypto engine (as error source). */
/* We force an encryption failure if we know that integrity
* protection is missing. For modern version of gpg using
* modern cipher algorithms this is not required because gpg
* will issue a failure anyway. However older gpg versions emit
* only a warning.
* Fixme: These error values should probably be attributed to
* the underlying crypto engine (as error source). */
if (opd->failed && opd->pkdecrypt_failed)
return opd->pkdecrypt_failed;
else if (opd->failed && opd->any_no_seckey)
return gpg_error (GPG_ERR_NO_SECKEY);
else if (opd->failed)
else if (opd->failed || opd->not_integrity_protected)
return gpg_error (GPG_ERR_DECRYPT_FAILED);
else if (!opd->okay)
return gpg_error (GPG_ERR_NO_DATA);

View File

@ -147,6 +147,16 @@ xjson_CreateObject (void)
return json;
}
/* Call cJSON_CreateArray but terminate in case of an error. */
static cjson_t
xjson_CreateArray (void)
{
cjson_t json = cJSON_CreateArray ();
if (!json)
xoutofcore ("cJSON_CreateArray");
return json;
}
/* Wrapper around cJSON_AddStringToObject which returns an gpg-error
* code instead of the NULL or the new object. */
@ -490,7 +500,7 @@ _create_new_context (gpgme_protocol_t proto)
/* Return a context object for protocol PROTO. This is currently a
* statuically allocated context initialized for PROTO. Termnates
* statically allocated context initialized for PROTO. Terminates
* process on failure. */
static gpgme_ctx_t
get_context (gpgme_protocol_t proto)
@ -590,6 +600,187 @@ data_from_base64_string (gpgme_data_t *r_data, cjson_t json)
}
/* Helper for summary formatting */
static void
add_summary_to_object (cjson_t result, gpgme_sigsum_t summary)
{
cjson_t response = xjson_CreateArray ();
if ( (summary & GPGME_SIGSUM_VALID ))
cJSON_AddItemToArray (response,
cJSON_CreateString ("valid"));
if ( (summary & GPGME_SIGSUM_GREEN ))
cJSON_AddItemToArray (response,
cJSON_CreateString ("green"));
if ( (summary & GPGME_SIGSUM_RED ))
cJSON_AddItemToArray (response,
cJSON_CreateString ("red"));
if ( (summary & GPGME_SIGSUM_KEY_REVOKED))
cJSON_AddItemToArray (response,
cJSON_CreateString ("revoked"));
if ( (summary & GPGME_SIGSUM_KEY_EXPIRED))
cJSON_AddItemToArray (response,
cJSON_CreateString ("key-expired"));
if ( (summary & GPGME_SIGSUM_SIG_EXPIRED))
cJSON_AddItemToArray (response,
cJSON_CreateString ("sig-expired"));
if ( (summary & GPGME_SIGSUM_KEY_MISSING))
cJSON_AddItemToArray (response,
cJSON_CreateString ("key-missing"));
if ( (summary & GPGME_SIGSUM_CRL_MISSING))
cJSON_AddItemToArray (response,
cJSON_CreateString ("crl-missing"));
if ( (summary & GPGME_SIGSUM_CRL_TOO_OLD))
cJSON_AddItemToArray (response,
cJSON_CreateString ("crl-too-old"));
if ( (summary & GPGME_SIGSUM_BAD_POLICY ))
cJSON_AddItemToArray (response,
cJSON_CreateString ("bad-policy"));
if ( (summary & GPGME_SIGSUM_SYS_ERROR ))
cJSON_AddItemToArray (response,
cJSON_CreateString ("sys-error"));
cJSON_AddItemToObject (result, "summary", response);
}
/* Helper for summary formatting */
static const char *
validity_to_string (gpgme_validity_t val)
{
switch (val)
{
case GPGME_VALIDITY_UNDEFINED:return "undefined";
case GPGME_VALIDITY_NEVER: return "never";
case GPGME_VALIDITY_MARGINAL: return "marginal";
case GPGME_VALIDITY_FULL: return "full";
case GPGME_VALIDITY_ULTIMATE: return "ultimate";
case GPGME_VALIDITY_UNKNOWN:
default: return "unknown";
}
}
/* Add a single signature to a result */
static gpg_error_t
add_signature_to_object (cjson_t result, gpgme_signature_t sig)
{
gpg_error_t err = 0;
if (!cJSON_AddStringToObject (result, "status", gpgme_strerror (sig->status)))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!cJSON_AddNumberToObject (result, "code", sig->status))
{
err = gpg_error_from_syserror ();
goto leave;
}
add_summary_to_object (result, sig->summary);
if (!cJSON_AddStringToObject (result, "fingerprint", sig->fpr))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!cJSON_AddNumberToObject (result, "created", sig->timestamp))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!cJSON_AddNumberToObject (result, "expired", sig->exp_timestamp))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!cJSON_AddStringToObject (result, "validity",
validity_to_string (sig->validity)))
{
err = gpg_error_from_syserror ();
goto leave;
}
leave:
return err;
}
/* Add multiple signatures as an array to a result */
static gpg_error_t
add_signatures_to_object (cjson_t result, gpgme_signature_t signatures)
{
cjson_t response = xjson_CreateArray ();
gpg_error_t err = 0;
gpgme_signature_t sig;
for (sig = signatures; sig; sig = sig->next)
{
cjson_t sig_obj = xjson_CreateObject ();
err = add_signature_to_object (sig_obj, sig);
if (err)
{
cJSON_Delete (sig_obj);
sig_obj = NULL;
goto leave;
}
cJSON_AddItemToArray (response, sig_obj);
}
if (!cJSON_AddItemToObject (result, "signatures", response))
{
err = gpg_error_from_syserror ();
cJSON_Delete (response);
response = NULL;
return err;
}
response = NULL;
leave:
if (err && response)
{
cJSON_Delete (response);
response = NULL;
}
return err;
}
/* Add an array of signature informations under the name "name". */
static gpg_error_t
add_signatures_object (cjson_t result, const char *name,
gpgme_verify_result_t verify_result)
{
cjson_t response = xjson_CreateObject ();
gpg_error_t err = 0;
err = add_signatures_to_object (response, verify_result->signatures);
if (err)
{
goto leave;
}
if (!cJSON_AddItemToObject (result, name, response))
{
err = gpg_error_from_syserror ();
goto leave;
}
leave:
if (err)
{
cJSON_Delete (response);
response = NULL;
}
return err;
}
/*
* Implementation of the commands.
@ -597,11 +788,11 @@ data_from_base64_string (gpgme_data_t *r_data, cjson_t json)
/* Create a "data" object and the "type", "base64" and "more" flags
* from DATA and append them to RESULT. Ownership if DATA is
* from DATA and append them to RESULT. Ownership of DATA is
* transferred to this function. TYPE must be a fixed string.
* CHUNKSIZE is the chunksize requested from the caller. If BASE64 is
* -1 the need for base64 encoding is determined by the content of
* DATA, all other values are take as rtue or false. Note that
* DATA, all other values are taken as true or false. Note that
* op_getmore has similar code but works on PENDING_DATA which is set
* here. */
static gpg_error_t
@ -884,6 +1075,7 @@ op_decrypt (cjson_t request, cjson_t result)
gpgme_data_t input = NULL;
gpgme_data_t output = NULL;
gpgme_decrypt_result_t decrypt_result;
gpgme_verify_result_t verify_result;
if ((err = get_protocol (request, &protocol)))
goto leave;
@ -952,9 +1144,27 @@ op_decrypt (cjson_t request, cjson_t result)
if (decrypt_result->is_mime)
xjson_AddBoolToObject (result, "mime", 1);
verify_result = gpgme_op_verify_result (ctx);
if (verify_result && verify_result->signatures)
{
err = add_signatures_object (result, "info", verify_result);
}
if (err)
{
error_object (result, "Info output failed: %s", gpg_strerror (err));
goto leave;
}
err = make_data_object (result, output, chunksize, "plaintext", -1);
output = NULL;
if (err)
{
error_object (result, "Plaintext output failed: %s", gpg_strerror (err));
goto leave;
}
leave:
release_context (ctx);
gpgme_data_release (input);
@ -963,6 +1173,182 @@ op_decrypt (cjson_t request, cjson_t result)
}
static const char hlp_sign[] =
"op: \"sign\"\n"
"keys: Array of strings with the fingerprints of the signing key.\n"
" For a single key a String may be used instead of an array.\n"
"data: Input data. \n"
"\n"
"Optional parameters:\n"
"protocol: Either \"openpgp\" (default) or \"cms\".\n"
"chunksize: Max number of bytes in the resulting \"data\".\n"
"sender: The mail address of the sender.\n"
"mode: A string with the signing mode can be:\n"
" detached (default)\n"
" opaque\n"
" clearsign\n"
"\n"
"Optional boolean flags (default is false):\n"
"base64: Input data is base64 encoded.\n"
"armor: Request output in armored format.\n"
"\n"
"Response on success:\n"
"type: \"signature\"\n"
"data: Unless armor mode is used a Base64 encoded binary\n"
" signature. In armor mode a string with an armored\n"
" OpenPGP or a PEM message.\n"
"base64: Boolean indicating whether data is base64 encoded.\n"
"more: Optional boolean indicating that \"getmore\" is required.";
static gpg_error_t
op_sign (cjson_t request, cjson_t result)
{
gpg_error_t err;
gpgme_ctx_t ctx = NULL;
gpgme_protocol_t protocol;
size_t chunksize;
int opt_base64;
char *keystring = NULL;
cjson_t j_input;
gpgme_data_t input = NULL;
gpgme_data_t output = NULL;
int abool;
cjson_t j_tmp;
gpgme_sig_mode_t mode = GPGME_SIG_MODE_DETACH;
gpgme_ctx_t keylist_ctx = NULL;
gpgme_key_t key = NULL;
if ((err = get_protocol (request, &protocol)))
goto leave;
ctx = get_context (protocol);
if ((err = get_chunksize (request, &chunksize)))
goto leave;
if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
goto leave;
if ((err = get_boolean_flag (request, "armor", 0, &abool)))
goto leave;
gpgme_set_armor (ctx, abool);
j_tmp = cJSON_GetObjectItem (request, "mode");
if (j_tmp && cjson_is_string (j_tmp))
{
if (!strcmp (j_tmp->valuestring, "opaque"))
{
mode = GPGME_SIG_MODE_NORMAL;
}
else if (!strcmp (j_tmp->valuestring, "clearsign"))
{
mode = GPGME_SIG_MODE_CLEAR;
}
}
j_tmp = cJSON_GetObjectItem (request, "sender");
if (j_tmp && cjson_is_string (j_tmp))
{
gpgme_set_sender (ctx, j_tmp->valuestring);
}
/* Get the keys. */
err = get_keys (request, &keystring);
if (err)
{
/* Provide a custom error response. */
error_object (result, "Error getting keys: %s", gpg_strerror (err));
goto leave;
}
/* Do a keylisting and add the keys */
if ((err = gpgme_new (&keylist_ctx)))
goto leave;
gpgme_set_protocol (keylist_ctx, protocol);
gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL);
err = gpgme_op_keylist_start (ctx, keystring, 1);
if (err)
{
error_object (result, "Error listing keys: %s", gpg_strerror (err));
goto leave;
}
while (!(err = gpgme_op_keylist_next (ctx, &key)))
{
if ((err = gpgme_signers_add (ctx, key)))
{
error_object (result, "Error adding signer: %s", gpg_strerror (err));
goto leave;
}
gpgme_key_unref (key);
}
/* Get the data. Note that INPUT is a shallow data object with the
* storage hold in REQUEST. */
j_input = cJSON_GetObjectItem (request, "data");
if (!j_input)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
if (!cjson_is_string (j_input))
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (opt_base64)
{
err = data_from_base64_string (&input, j_input);
if (err)
{
error_object (result, "Error decoding Base-64 encoded 'data': %s",
gpg_strerror (err));
goto leave;
}
}
else
{
err = gpgme_data_new_from_mem (&input, j_input->valuestring,
strlen (j_input->valuestring), 0);
if (err)
{
error_object (result, "Error getting 'data': %s", gpg_strerror (err));
goto leave;
}
}
/* Create an output data object. */
err = gpgme_data_new (&output);
if (err)
{
error_object (result, "Error creating output data object: %s",
gpg_strerror (err));
goto leave;
}
/* Sign. */
err = gpgme_op_sign (ctx, input, output, mode);
if (err)
{
error_object (result, "Signing failed: %s", gpg_strerror (err));
goto leave;
}
gpgme_data_release (input);
input = NULL;
/* We need to base64 if armoring has not been requested. */
err = make_data_object (result, output, chunksize,
"ciphertext", !gpgme_get_armor (ctx));
output = NULL;
leave:
xfree (keystring);
release_context (ctx);
release_context (keylist_ctx);
gpgme_data_release (input);
gpgme_data_release (output);
return err;
}
static const char hlp_getmore[] =
"op: \"getmore\"\n"
@ -1058,6 +1444,8 @@ static const char hlp_help[] =
"returned. To list all operations it is allowed to leave out \"op\" in\n"
"help mode. Supported values for \"op\" are:\n\n"
" encrypt Encrypt data.\n"
" decrypt Decrypt data.\n"
" sign Sign data.\n"
" getmore Retrieve remaining data.\n"
" help Help overview.";
static gpg_error_t
@ -1098,6 +1486,7 @@ process_request (const char *request)
} optbl[] = {
{ "encrypt", op_encrypt, hlp_encrypt },
{ "decrypt", op_decrypt, hlp_decrypt },
{ "sign", op_sign, hlp_sign },
{ "getmore", op_getmore, hlp_getmore },
{ "help", op_help, hlp_help },
{ NULL }

View File

@ -72,6 +72,17 @@
# define F_OK 0
#endif
/* The Registry key used by GNUPG. */
#ifdef _WIN64
# define GNUPG_REGKEY_2 "Software\\Wow6432Node\\GNU\\GnuPG"
#else
# define GNUPG_REGKEY_2 "Software\\GNU\\GnuPG"
#endif
#ifdef _WIN64
# define GNUPG_REGKEY_3 "Software\\Wow6432Node\\GnuPG"
#else
# define GNUPG_REGKEY_3 "Software\\GnuPG"
#endif
DEFINE_STATIC_LOCK (get_path_lock);
@ -513,7 +524,7 @@ _gpgme_get_gpg_path (void)
char *dir;
dir = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
"Software\\GNU\\GnuPG",
GNUPG_REGKEY_2,
"Install Directory");
if (dir)
{
@ -568,12 +579,12 @@ _gpgme_get_gpgconf_path (void)
char *dir;
dir = read_w32_registry_string (NULL,
"Software\\GNU\\GnuPG",
GNUPG_REGKEY_2,
"Install Directory");
if (!dir)
{
char *tmp = read_w32_registry_string (NULL,
"Software\\GnuPG",
GNUPG_REGKEY_3,
"Install Directory");
if (tmp)
{
@ -596,6 +607,14 @@ _gpgme_get_gpgconf_path (void)
gpgconf = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe");
}
/* 5. Try to find gpgconf.exe relative to us. */
if (!gpgconf && inst_dir)
{
char *dir = _gpgme_strconcat (inst_dir, "\\..\\..\\GnuPG\\bin");
gpgconf = find_program_in_dir (dir, name);
free (dir);
}
/* 5. Print a debug message if not found. */
if (!gpgconf)
_gpgme_debug (DEBUG_ENGINE, "_gpgme_get_gpgconf_path: '%s' not found",name);