diff --git a/src/gpgme-json.c b/src/gpgme-json.c index 90c9aeaf..635294eb 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -180,6 +180,15 @@ xjson_AddStringToObject (cjson_t object, const char *name, const char *string) } +/* Same as xjson_AddStringToObject but ignores NULL strings */ +static void +xjson_AddStringToObject0 (cjson_t object, const char *name, const char *string) +{ + if (!string) + return; + xjson_AddStringToObject (object, name, string); +} + /* Wrapper around cJSON_AddBoolToObject which terminates the process * in case of an error. */ static void @@ -200,6 +209,16 @@ xjson_AddNumberToObject (cjson_t object, const char *name, double dbl) return ; } +/* Wrapper around cJSON_AddItemToObject which terminates the process + * in case of an error. */ +static void +xjson_AddItemToObject (cjson_t object, const char *name, cjson_t item) +{ + if (!cJSON_AddItemToObject (object, name, item)) + xoutofcore ("cJSON_AddItemToObject"); + return ; +} + /* This is similar to cJSON_AddStringToObject but takes (DATA, * DATALEN) and adds it under NAME as a base 64 encoded string to * OBJECT. */ @@ -686,8 +705,232 @@ validity_to_string (gpgme_validity_t val) } } +static const char * +protocol_to_string (gpgme_protocol_t proto) +{ + switch (proto) + { + case GPGME_PROTOCOL_OpenPGP: return "OpenPGP"; + case GPGME_PROTOCOL_CMS: return "CMS"; + case GPGME_PROTOCOL_GPGCONF: return "gpgconf"; + case GPGME_PROTOCOL_ASSUAN: return "assuan"; + case GPGME_PROTOCOL_G13: return "g13"; + case GPGME_PROTOCOL_UISERVER:return "uiserver"; + case GPGME_PROTOCOL_SPAWN: return "spawn"; + default: + return "unknown"; + } +} -/* Add a single signature to a result */ +/* Create a sig_notation json object */ +static cjson_t +sig_notation_to_json (gpgme_sig_notation_t not) +{ + cjson_t result = xjson_CreateObject (); + xjson_AddBoolToObject (result, "human_readable", not->human_readable); + xjson_AddBoolToObject (result, "critical", not->critical); + + xjson_AddStringToObject0 (result, "name", not->name); + xjson_AddStringToObject0 (result, "value", not->value); + + xjson_AddNumberToObject (result, "flags", not->flags); + + return result; +} + +/* Create a key_sig json object */ +static cjson_t +key_sig_to_json (gpgme_key_sig_t sig) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", sig->revoked); + xjson_AddBoolToObject (result, "expired", sig->expired); + xjson_AddBoolToObject (result, "invalid", sig->invalid); + xjson_AddBoolToObject (result, "exportable", sig->exportable); + + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sig->pubkey_algo)); + xjson_AddStringToObject0 (result, "keyid", sig->keyid); + xjson_AddStringToObject0 (result, "status", gpgme_strerror (sig->status)); + xjson_AddStringToObject0 (result, "name", sig->name); + xjson_AddStringToObject0 (result, "email", sig->email); + xjson_AddStringToObject0 (result, "comment", sig->comment); + + xjson_AddNumberToObject (result, "pubkey_algo", sig->pubkey_algo); + xjson_AddNumberToObject (result, "timestamp", sig->timestamp); + xjson_AddNumberToObject (result, "expires", sig->expires); + xjson_AddNumberToObject (result, "status_code", sig->status); + xjson_AddNumberToObject (result, "sig_class", sig->sig_class); + + if (sig->notations) + { + gpgme_sig_notation_t not; + cjson_t array = xjson_CreateArray (); + for (not = sig->notations; not; not = not->next) + cJSON_AddItemToArray (array, sig_notation_to_json (not)); + xjson_AddItemToObject (result, "notations", array); + } + + return result; +} + +/* Create a tofu info object */ +static cjson_t +tofu_to_json (gpgme_tofu_info_t tofu) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddStringToObject0 (result, "description", tofu->description); + + xjson_AddNumberToObject (result, "validity", tofu->validity); + xjson_AddNumberToObject (result, "policy", tofu->policy); + xjson_AddNumberToObject (result, "signcount", tofu->signcount); + xjson_AddNumberToObject (result, "encrcount", tofu->encrcount); + xjson_AddNumberToObject (result, "signfirst", tofu->signfirst); + xjson_AddNumberToObject (result, "signlast", tofu->signlast); + xjson_AddNumberToObject (result, "encrfirst", tofu->encrfirst); + xjson_AddNumberToObject (result, "encrlast", tofu->encrlast); + + return result; +} + +/* Create a userid json object */ +static cjson_t +uid_to_json (gpgme_user_id_t uid) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", uid->revoked); + xjson_AddBoolToObject (result, "invalid", uid->invalid); + + xjson_AddStringToObject0 (result, "validity", + validity_to_string (uid->validity)); + xjson_AddStringToObject0 (result, "uid", uid->uid); + xjson_AddStringToObject0 (result, "name", uid->name); + xjson_AddStringToObject0 (result, "email", uid->email); + xjson_AddStringToObject0 (result, "comment", uid->comment); + xjson_AddStringToObject0 (result, "address", uid->address); + + xjson_AddNumberToObject (result, "origin", uid->origin); + xjson_AddNumberToObject (result, "last_update", uid->last_update); + + /* Key sigs */ + if (uid->signatures) + { + cjson_t sig_array = xjson_CreateArray (); + gpgme_key_sig_t sig; + + for (sig = uid->signatures; sig; sig = sig->next) + cJSON_AddItemToArray (sig_array, key_sig_to_json (sig)); + + xjson_AddItemToObject (result, "signatures", sig_array); + } + + /* TOFU info */ + if (uid->tofu) + { + gpgme_tofu_info_t tofu; + cjson_t array = xjson_CreateArray (); + for (tofu = uid->tofu; tofu; tofu = tofu->next) + cJSON_AddItemToArray (array, tofu_to_json (tofu)); + xjson_AddItemToObject (result, "tofu", array); + } + + return result; +} + +/* Create a subkey json object */ +static cjson_t +subkey_to_json (gpgme_subkey_t sub) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", sub->revoked); + xjson_AddBoolToObject (result, "expired", sub->expired); + xjson_AddBoolToObject (result, "disabled", sub->disabled); + xjson_AddBoolToObject (result, "invalid", sub->invalid); + xjson_AddBoolToObject (result, "can_encrypt", sub->can_encrypt); + xjson_AddBoolToObject (result, "can_sign", sub->can_sign); + xjson_AddBoolToObject (result, "can_certify", sub->can_certify); + xjson_AddBoolToObject (result, "can_authenticate", sub->can_authenticate); + xjson_AddBoolToObject (result, "secret", sub->secret); + xjson_AddBoolToObject (result, "is_qualified", sub->is_qualified); + xjson_AddBoolToObject (result, "is_cardkey", sub->is_cardkey); + xjson_AddBoolToObject (result, "is_de_vs", sub->is_de_vs); + + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (sub->pubkey_algo)); + xjson_AddStringToObject0 (result, "pubkey_algo_string", + gpgme_pubkey_algo_string (sub)); + xjson_AddStringToObject0 (result, "keyid", sub->keyid); + xjson_AddStringToObject0 (result, "card_number", sub->card_number); + xjson_AddStringToObject0 (result, "curve", sub->curve); + xjson_AddStringToObject0 (result, "keygrip", sub->keygrip); + + xjson_AddNumberToObject (result, "pubkey_algo", sub->pubkey_algo); + xjson_AddNumberToObject (result, "length", sub->length); + xjson_AddNumberToObject (result, "timestamp", sub->timestamp); + xjson_AddNumberToObject (result, "expires", sub->expires); + + return result; +} + +/* Create a key json object */ +static cjson_t +key_to_json (gpgme_key_t key) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "revoked", key->revoked); + xjson_AddBoolToObject (result, "expired", key->expired); + xjson_AddBoolToObject (result, "disabled", key->disabled); + xjson_AddBoolToObject (result, "invalid", key->invalid); + xjson_AddBoolToObject (result, "can_encrypt", key->can_encrypt); + xjson_AddBoolToObject (result, "can_sign", key->can_sign); + xjson_AddBoolToObject (result, "can_certify", key->can_certify); + xjson_AddBoolToObject (result, "can_authenticate", key->can_authenticate); + xjson_AddBoolToObject (result, "secret", key->secret); + xjson_AddBoolToObject (result, "is_qualified", key->is_qualified); + + xjson_AddStringToObject0 (result, "protocol", + protocol_to_string (key->protocol)); + xjson_AddStringToObject0 (result, "issuer_serial", key->issuer_serial); + xjson_AddStringToObject0 (result, "issuer_name", key->issuer_name); + xjson_AddStringToObject0 (result, "fingerprint", key->fpr); + xjson_AddStringToObject0 (result, "chain_id", key->chain_id); + xjson_AddStringToObject0 (result, "owner_trust", + validity_to_string (key->owner_trust)); + + xjson_AddNumberToObject (result, "origin", key->origin); + xjson_AddNumberToObject (result, "last_update", key->last_update); + + /* Add subkeys */ + if (key->subkeys) + { + cjson_t subkey_array = xjson_CreateArray (); + gpgme_subkey_t sub; + for (sub = key->subkeys; sub; sub = sub->next) + cJSON_AddItemToArray (subkey_array, subkey_to_json (sub)); + + xjson_AddItemToObject (result, "subkeys", subkey_array); + } + + /* User Ids */ + if (key->uids) + { + cjson_t uid_array = xjson_CreateArray (); + gpgme_user_id_t uid; + for (uid = key->uids; uid; uid = uid->next) + cJSON_AddItemToArray (uid_array, uid_to_json (uid)); + + xjson_AddItemToObject (result, "userids", uid_array); + } + + return result; +} + +/* Add a single signature to a json object */ static gpg_error_t add_signature_to_object (cjson_t result, gpgme_signature_t sig) { @@ -808,23 +1051,6 @@ add_signatures_object (cjson_t result, const char *name, return err; } -static const char * -protocol_to_string (gpgme_protocol_t proto) -{ - switch (proto) - { - case GPGME_PROTOCOL_OpenPGP: return "OpenPGP"; - case GPGME_PROTOCOL_CMS: return "CMS"; - case GPGME_PROTOCOL_GPGCONF: return "gpgconf"; - case GPGME_PROTOCOL_ASSUAN: return "assuan"; - case GPGME_PROTOCOL_G13: return "g13"; - case GPGME_PROTOCOL_UISERVER:return "uiserver"; - case GPGME_PROTOCOL_SPAWN: return "spawn"; - default: - return "unknown"; - } -} - static gpg_error_t add_ei_to_object (cjson_t result, gpgme_engine_info_t info) { @@ -1522,6 +1748,247 @@ op_version (cjson_t request, cjson_t result) return 0; } +static const char hlp_keylist[] = + "op: \"keylist\"\n" + "keys: Array of strings or fingerprints to lookup\n" + " For a single key a String may be used instead of an array.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "chunksize: Max number of bytes in the resulting \"data\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "secret: List secret keys.\n" + "extern: Add KEYLIST_MODE_EXTERN.\n" + "local: Add KEYLIST_MODE_LOCAL. (default mode).\n" + "sigs: Add KEYLIST_MODE_SIGS.\n" + "notations: Add KEYLIST_MODE_SIG_NOTATIONS.\n" + "tofu: Add KEYLIST_MODE_WITH_TOFU.\n" + "ephemeral: Add KEYLIST_MODE_EPHEMERAL.\n" + "validate: Add KEYLIST_MODE_VALIDATE.\n" + "\n" + "Response on success:\n" + "keys: Array of keys.\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " disabled\n" + " invalid\n" + " can_encrypt\n" + " can_sign\n" + " can_certify\n" + " can_authenticate\n" + " secret\n" + " is_qualified\n" + " String values:\n" + " protocol\n" + " issuer_serial (CMS Only)\n" + " issuer_name (CMS Only)\n" + " chain_id (CMS Only)\n" + " owner_trust (OpenPGP only)\n" + " fingerprint\n" + " Number values:\n" + " last_update\n" + " origin\n" + " Array values:\n" + " subkeys\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " disabled\n" + " invalid\n" + " can_encrypt\n" + " can_sign\n" + " can_certify\n" + " can_authenticate\n" + " secret\n" + " is_qualified\n" + " is_cardkey\n" + " is_de_vs\n" + " String values:\n" + " pubkey_algo_name\n" + " pubkey_algo_string\n" + " keyid\n" + " card_number\n" + " curve\n" + " keygrip\n" + " Number values:\n" + " pubkey_algo\n" + " length\n" + " timestamp\n" + " expires\n" + " userids\n" + " Boolean values:\n" + " revoked\n" + " invalid\n" + " String values:\n" + " validity\n" + " uid\n" + " name\n" + " email\n" + " comment\n" + " address\n" + " Number values:\n" + " origin\n" + " last_update\n" + " Array values:\n" + " signatures\n" + " Boolean values:\n" + " revoked\n" + " expired\n" + " invalid\n" + " exportable\n" + " String values:\n" + " pubkey_algo_name\n" + " keyid\n" + " status\n" + " uid\n" + " name\n" + " email\n" + " comment\n" + " Number values:\n" + " pubkey_algo\n" + " timestamp\n" + " expires\n" + " status_code\n" + " sig_class\n" + " Array values:\n" + " notations\n" + " Boolean values:\n" + " human_readable\n" + " critical\n" + " String values:\n" + " name\n" + " value\n" + " Number values:\n" + " flags\n" + " tofu\n" + " String values:\n" + " description\n" + " Number values:\n" + " validity\n" + " policy\n" + " signcount\n" + " encrcount\n" + " signfirst\n" + " signlast\n" + " encrfirst\n" + " encrlast\n" + "more: Optional boolean indicating that \"getmore\" is required.\n" + " (not implemented)"; +static gpg_error_t +op_keylist (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + size_t chunksize; + char *keystring = NULL; + int abool; + gpgme_keylist_mode_t mode = 0; + gpgme_key_t key = NULL; + cjson_t keyarray = xjson_CreateArray (); + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + if ((err = get_chunksize (request, &chunksize))) + goto leave; + + /* Handle the various keylist mode bools. */ + if ((err = get_boolean_flag (request, "secret", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_WITH_SECRET; + + if ((err = get_boolean_flag (request, "extern", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_EXTERN; + + if ((err = get_boolean_flag (request, "local", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_LOCAL; + + if ((err = get_boolean_flag (request, "sigs", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_SIGS; + + if ((err = get_boolean_flag (request, "notations", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS; + + if ((err = get_boolean_flag (request, "tofu", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_WITH_TOFU; + + if ((err = get_boolean_flag (request, "ephemeral", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_EPHEMERAL; + + if ((err = get_boolean_flag (request, "validate", 0, &abool))) + goto leave; + if (abool) + mode |= GPGME_KEYLIST_MODE_VALIDATE; + + if (!mode) + { + /* default to local */ + mode = GPGME_KEYLIST_MODE_LOCAL; + } + + /* Get the keys. */ + err = get_keys (request, &keystring); + if (err) + { + /* Provide a custom error response. */ + gpg_error_object (result, err, "Error getting keys: %s", + gpg_strerror (err)); + goto leave; + } + + /* Do a keylisting and add the keys */ + if ((err = gpgme_new (&ctx))) + goto leave; + gpgme_set_protocol (ctx, protocol); + gpgme_set_keylist_mode (ctx, mode); + + err = gpgme_op_keylist_start (ctx, keystring, + (mode & GPGME_KEYLIST_MODE_WITH_SECRET)); + if (err) + { + gpg_error_object (result, err, "Error listing keys: %s", + gpg_strerror (err)); + goto leave; + } + + while (!(err = gpgme_op_keylist_next (ctx, &key))) + { + cJSON_AddItemToArray (keyarray, key_to_json (key)); + gpgme_key_unref (key); + } + err = 0; + + if (!cJSON_AddItemToObject (result, "keys", keyarray)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + leave: + xfree (keystring); + if (err) + { + cJSON_Delete (keyarray); + } + return err; +} + static const char hlp_getmore[] = "op: \"getmore\"\n" "\n" @@ -1659,6 +2126,7 @@ process_request (const char *request) } optbl[] = { { "encrypt", op_encrypt, hlp_encrypt }, { "decrypt", op_decrypt, hlp_decrypt }, + { "keylist", op_keylist, hlp_keylist }, { "sign", op_sign, hlp_sign }, { "verify", op_verify, hlp_verify }, { "version", op_version, hlp_version },