diff options
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | doc/gpgme.texi | 2 | ||||
-rw-r--r-- | lang/cpp/src/decryptionresult.cpp | 11 | ||||
-rw-r--r-- | lang/cpp/src/decryptionresult.h | 4 | ||||
-rw-r--r-- | lang/python/docs/GPGMEpythonHOWTOen.org | 17 | ||||
-rwxr-xr-x | lang/python/examples/howto/decrypt-file.py | 13 | ||||
-rw-r--r-- | src/decrypt.c | 27 | ||||
-rw-r--r-- | src/gpgme-json.c | 395 | ||||
-rw-r--r-- | src/w32-util.c | 25 |
9 files changed, 479 insertions, 22 deletions
@@ -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) ------------------------------------------------- diff --git a/doc/gpgme.texi b/doc/gpgme.texi index c4a29d54..c745675b 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -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 diff --git a/lang/cpp/src/decryptionresult.cpp b/lang/cpp/src/decryptionresult.cpp index 1e815cbe..17524db9 100644 --- a/lang/cpp/src/decryptionresult.cpp +++ b/lang/cpp/src/decryptionresult.cpp @@ -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(), diff --git a/lang/cpp/src/decryptionresult.h b/lang/cpp/src/decryptionresult.h index 57705b48..c270223d 100644 --- a/lang/cpp/src/decryptionresult.h +++ b/lang/cpp/src/decryptionresult.h @@ -77,6 +77,10 @@ public: const char *fileName() const; + const char *sessionKey() const; + + const char *symkeyAlgo() const; + class Recipient; unsigned int numRecipients() const; diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org index cb85b61b..ef66effc 100644 --- a/lang/python/docs/GPGMEpythonHOWTOen.org +++ b/lang/python/docs/GPGMEpythonHOWTOen.org @@ -14,7 +14,7 @@ :CUSTOM_ID: intro :END: - | Version: | 0.1.0 | + | Version: | 0.1.1 | | Author: | Ben McGinnes <[email protected]> | | 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) - - with open(newfile, "wb") as nfile: - nfile.write(plaintext) + 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 diff --git a/lang/python/examples/howto/decrypt-file.py b/lang/python/examples/howto/decrypt-file.py index 60a050bd..b38acc79 100755 --- a/lang/python/examples/howto/decrypt-file.py +++ b/lang/python/examples/howto/decrypt-file.py @@ -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 diff --git a/src/decrypt.c b/src/decrypt.c index 0fc7019c..ecd9c144 100644 --- a/src/decrypt.c +++ b/src/decrypt.c @@ -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); diff --git a/src/gpgme-json.c b/src/gpgme-json.c index fb5f149b..a755500d 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -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); @@ -964,6 +1174,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" "\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 } diff --git a/src/w32-util.c b/src/w32-util.c index 5b02c7ea..30dd081a 100644 --- a/src/w32-util.c +++ b/src/w32-util.c @@ -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); |