diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/decrypt.c | 27 | ||||
-rw-r--r-- | src/gpgme-json.c | 395 | ||||
-rw-r--r-- | src/w32-util.c | 25 |
3 files changed, 434 insertions, 13 deletions
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); |