/* key.c - Key objects. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004 g10 Code GmbH 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "util.h" #include "ops.h" #include "sema.h" /* Protects all reference counters in keys. All other accesses to a key are read only. */ DEFINE_STATIC_LOCK (key_ref_lock); /* Create a new key. */ gpgme_error_t _gpgme_key_new (gpgme_key_t *r_key) { gpgme_key_t key; key = calloc (1, sizeof *key); if (!key) return gpg_error_from_errno (errno); key->_refs = 1; *r_key = key; return 0; } gpgme_error_t _gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey) { gpgme_subkey_t subkey; subkey = calloc (1, sizeof *subkey); if (!subkey) return gpg_error_from_errno (errno); subkey->keyid = subkey->_keyid; subkey->_keyid[16] = '\0'; if (!key->subkeys) key->subkeys = subkey; if (key->_last_subkey) key->_last_subkey->next = subkey; key->_last_subkey = subkey; *r_subkey = subkey; return 0; } static char * set_user_id_part (char *tail, const char *buf, size_t len) { while (len && (buf[len - 1] == ' ' || buf[len - 1] == '\t')) len--; for (; len; len--) *tail++ = *buf++; *tail++ = 0; return tail; } static void parse_user_id (char *src, char **name, char **email, char **comment, char *tail) { const char *start = NULL; int in_name = 0; int in_email = 0; int in_comment = 0; while (*src) { if (in_email) { if (*src == '<') /* Not legal but anyway. */ in_email++; else if (*src == '>') { if (!--in_email && !*email) { *email = tail; tail = set_user_id_part (tail, start, src - start); } } } else if (in_comment) { if (*src == '(') in_comment++; else if (*src == ')') { if (!--in_comment && !*comment) { *comment = tail; tail = set_user_id_part (tail, start, src - start); } } } else if (*src == '<') { if (in_name) { if (!*name) { *name = tail; tail = set_user_id_part (tail, start, src - start); } in_name = 0; } in_email = 1; start = src + 1; } else if (*src == '(') { if (in_name) { if (!*name) { *name = tail; tail = set_user_id_part (tail, start, src - start); } in_name = 0; } in_comment = 1; start = src + 1; } else if (!in_name && *src != ' ' && *src != '\t') { in_name = 1; start = src; } src++; } if (in_name) { if (!*name) { *name = tail; tail = set_user_id_part (tail, start, src - start); } } /* Let unused parts point to an EOS. */ tail--; if (!*name) *name = tail; if (!*email) *email = tail; if (!*comment) *comment = tail; } static void parse_x509_user_id (char *src, char **name, char **email, char **comment, char *tail) { if (*src == '<' && src[strlen (src) - 1] == '>') *email = src; /* Let unused parts point to an EOS. */ tail--; if (!*name) *name = tail; if (!*email) *email = tail; if (!*comment) *comment = tail; } /* Take a name from the --with-colon listing, remove certain escape sequences sequences and put it into the list of UIDs. */ gpgme_error_t _gpgme_key_append_name (gpgme_key_t key, const char *src, int convert) { gpgme_user_id_t uid; char *dst; int src_len = strlen (src); assert (key); /* We can malloc a buffer of the same length, because the converted string will never be larger. Actually we allocate it twice the size, so that we are able to store the parsed stuff there too. */ uid = malloc (sizeof (*uid) + 2 * src_len + 3); if (!uid) return gpg_error_from_errno (errno); memset (uid, 0, sizeof *uid); uid->uid = ((char *) uid) + sizeof (*uid); dst = uid->uid; if (convert) _gpgme_decode_c_string (src, &dst, src_len + 1); else memcpy (dst, src, src_len + 1); dst += strlen (dst) + 1; if (key->protocol == GPGME_PROTOCOL_CMS) parse_x509_user_id (uid->uid, &uid->name, &uid->email, &uid->comment, dst); else parse_user_id (uid->uid, &uid->name, &uid->email, &uid->comment, dst); if (!key->uids) key->uids = uid; if (key->_last_uid) key->_last_uid->next = uid; key->_last_uid = uid; return 0; } gpgme_key_sig_t _gpgme_key_add_sig (gpgme_key_t key, char *src) { int src_len = src ? strlen (src) : 0; gpgme_user_id_t uid; gpgme_key_sig_t sig; assert (key); /* XXX */ uid = key->_last_uid; assert (uid); /* XXX */ /* We can malloc a buffer of the same length, because the converted string will never be larger. Actually we allocate it twice the size, so that we are able to store the parsed stuff there too. */ sig = malloc (sizeof (*sig) + 2 * src_len + 3); if (!sig) return NULL; memset (sig, 0, sizeof *sig); sig->keyid = sig->_keyid; sig->_keyid[16] = '\0'; sig->uid = ((char *) sig) + sizeof (*sig); if (src) { char *dst = sig->uid; _gpgme_decode_c_string (src, &dst, src_len + 1); dst += strlen (dst) + 1; if (key->protocol == GPGME_PROTOCOL_CMS) parse_x509_user_id (sig->uid, &sig->name, &sig->email, &sig->comment, dst); else parse_user_id (sig->uid, &sig->name, &sig->email, &sig->comment, dst); } else sig->uid = '\0'; if (!uid->signatures) uid->signatures = sig; if (uid->_last_keysig) uid->_last_keysig->next = sig; uid->_last_keysig = sig; return sig; } /* Acquire a reference to KEY. */ void gpgme_key_ref (gpgme_key_t key) { LOCK (key_ref_lock); key->_refs++; UNLOCK (key_ref_lock); } /* gpgme_key_unref releases the key object. Note, that this function may not do an actual release if there are other shallow copies of the objects. You have to call this function for every newly created key object as well as for every gpgme_key_ref() done on the key object. */ void gpgme_key_unref (gpgme_key_t key) { gpgme_user_id_t uid; gpgme_subkey_t subkey; if (!key) return; LOCK (key_ref_lock); assert (key->_refs > 0); if (--key->_refs) { UNLOCK (key_ref_lock); return; } UNLOCK (key_ref_lock); subkey = key->subkeys; while (subkey) { gpgme_subkey_t next = subkey->next; if (subkey->fpr) free (subkey->fpr); if (subkey->card_number) free (subkey->card_number); free (subkey); subkey = next; } uid = key->uids; while (uid) { gpgme_user_id_t next_uid = uid->next; gpgme_key_sig_t keysig = uid->signatures; while (keysig) { gpgme_key_sig_t next_keysig = keysig->next; gpgme_sig_notation_t notation = keysig->notations; while (notation) { gpgme_sig_notation_t next_notation = notation->next; _gpgme_sig_notation_free (notation); notation = next_notation; } free (keysig); keysig = next_keysig; } free (uid); uid = next_uid; } if (key->issuer_serial) free (key->issuer_serial); if (key->issuer_name) free (key->issuer_name); if (key->chain_id) free (key->chain_id); free (key); } /* Support functions. */ /* Create a dummy key to specify an email address. */ gpgme_error_t gpgme_key_from_uid (gpgme_key_t *r_key, const char *name) { gpgme_error_t err; gpgme_key_t key; *r_key = NULL; err = _gpgme_key_new (&key); if (err) return err; /* Note: protocol doesn't matter if only email is provided. */ err = _gpgme_key_append_name (key, name, 0); if (err) gpgme_key_unref (key); else *r_key = key; return err; } /* Compatibility interfaces. */ void gpgme_key_release (gpgme_key_t key) { gpgme_key_unref (key); } static const char * otrust_to_string (int otrust) { switch (otrust) { case GPGME_VALIDITY_NEVER: return "n"; case GPGME_VALIDITY_MARGINAL: return "m"; case GPGME_VALIDITY_FULL: return "f"; case GPGME_VALIDITY_ULTIMATE: return "u"; default: return "?"; } } static const char * validity_to_string (int validity) { switch (validity) { case GPGME_VALIDITY_UNDEFINED: return "q"; case GPGME_VALIDITY_NEVER: return "n"; case GPGME_VALIDITY_MARGINAL: return "m"; case GPGME_VALIDITY_FULL: return "f"; case GPGME_VALIDITY_ULTIMATE: return "u"; case GPGME_VALIDITY_UNKNOWN: default: return "?"; } } static const char * capabilities_to_string (gpgme_subkey_t subkey) { static const char *const strings[8] = { "", "c", "s", "sc", "e", "ec", "es", "esc" }; return strings[(!!subkey->can_encrypt << 2) | (!!subkey->can_sign << 1) | (!!subkey->can_certify)]; } /* Return the value of the attribute WHAT of ITEM, which has to be representable by a string. */ const char * gpgme_key_get_string_attr (gpgme_key_t key, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_subkey_t subkey; gpgme_user_id_t uid; int i; if (!key || reserved || idx < 0) return NULL; /* Select IDXth subkey. */ subkey = key->subkeys; for (i = 0; i < idx; i++) { subkey = subkey->next; if (!subkey) break; } /* Select the IDXth user ID. */ uid = key->uids; for (i = 0; i < idx; i++) { uid = uid->next; if (!uid) break; } switch (what) { case GPGME_ATTR_KEYID: return subkey ? subkey->keyid : NULL; case GPGME_ATTR_FPR: return subkey ? subkey->fpr : NULL; case GPGME_ATTR_ALGO: return subkey ? gpgme_pubkey_algo_name (subkey->pubkey_algo) : NULL; case GPGME_ATTR_TYPE: return key->protocol == GPGME_PROTOCOL_CMS ? "X.509" : "PGP"; case GPGME_ATTR_OTRUST: return otrust_to_string (key->owner_trust); case GPGME_ATTR_USERID: return uid ? uid->uid : NULL; case GPGME_ATTR_NAME: return uid ? uid->name : NULL; case GPGME_ATTR_EMAIL: return uid ? uid->email : NULL; case GPGME_ATTR_COMMENT: return uid ? uid->comment : NULL; case GPGME_ATTR_VALIDITY: return uid ? validity_to_string (uid->validity) : NULL; case GPGME_ATTR_KEY_CAPS: return subkey ? capabilities_to_string (subkey) : NULL; case GPGME_ATTR_SERIAL: return key->issuer_serial; case GPGME_ATTR_ISSUER: return idx ? NULL : key->issuer_name; case GPGME_ATTR_CHAINID: return key->chain_id; default: return NULL; } } unsigned long gpgme_key_get_ulong_attr (gpgme_key_t key, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_subkey_t subkey; gpgme_user_id_t uid; int i; if (!key || reserved || idx < 0) return 0; /* Select IDXth subkey. */ subkey = key->subkeys; for (i = 0; i < idx; i++) { subkey = subkey->next; if (!subkey) break; } /* Select the IDXth user ID. */ uid = key->uids; for (i = 0; i < idx; i++) { uid = uid->next; if (!uid) break; } switch (what) { case GPGME_ATTR_ALGO: return subkey ? (unsigned long) subkey->pubkey_algo : 0; case GPGME_ATTR_LEN: return subkey ? (unsigned long) subkey->length : 0; case GPGME_ATTR_TYPE: return key->protocol == GPGME_PROTOCOL_CMS ? 1 : 0; case GPGME_ATTR_CREATED: return (subkey && subkey->timestamp >= 0) ? (unsigned long) subkey->timestamp : 0; case GPGME_ATTR_EXPIRE: return (subkey && subkey->expires >= 0) ? (unsigned long) subkey->expires : 0; case GPGME_ATTR_VALIDITY: return uid ? uid->validity : 0; case GPGME_ATTR_OTRUST: return key->owner_trust; case GPGME_ATTR_IS_SECRET: return !!key->secret; case GPGME_ATTR_KEY_REVOKED: return subkey ? subkey->revoked : 0; case GPGME_ATTR_KEY_INVALID: return subkey ? subkey->invalid : 0; case GPGME_ATTR_KEY_EXPIRED: return subkey ? subkey->expired : 0; case GPGME_ATTR_KEY_DISABLED: return subkey ? subkey->disabled : 0; case GPGME_ATTR_UID_REVOKED: return uid ? uid->revoked : 0; case GPGME_ATTR_UID_INVALID: return uid ? uid->invalid : 0; case GPGME_ATTR_CAN_ENCRYPT: return key->can_encrypt; case GPGME_ATTR_CAN_SIGN: return key->can_sign; case GPGME_ATTR_CAN_CERTIFY: return key->can_certify; default: return 0; } } static gpgme_key_sig_t get_keysig (gpgme_key_t key, int uid_idx, int idx) { gpgme_user_id_t uid; gpgme_key_sig_t sig; if (!key || uid_idx < 0 || idx < 0) return NULL; uid = key->uids; while (uid && uid_idx > 0) { uid = uid->next; uid_idx--; } if (!uid) return NULL; sig = uid->signatures; while (sig && idx > 0) { sig = sig->next; idx--; } return sig; } const char * gpgme_key_sig_get_string_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx); if (!certsig || reserved) return NULL; switch (what) { case GPGME_ATTR_KEYID: return certsig->keyid; case GPGME_ATTR_ALGO: return gpgme_pubkey_algo_name (certsig->pubkey_algo); case GPGME_ATTR_USERID: return certsig->uid; case GPGME_ATTR_NAME: return certsig->name; case GPGME_ATTR_EMAIL: return certsig->email; case GPGME_ATTR_COMMENT: return certsig->comment; default: return NULL; } } unsigned long gpgme_key_sig_get_ulong_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx); if (!certsig || reserved) return 0; switch (what) { case GPGME_ATTR_ALGO: return (unsigned long) certsig->pubkey_algo; case GPGME_ATTR_CREATED: return certsig->timestamp < 0 ? 0L : (unsigned long) certsig->timestamp; case GPGME_ATTR_EXPIRE: return certsig->expires < 0 ? 0L : (unsigned long) certsig->expires; case GPGME_ATTR_KEY_REVOKED: return certsig->revoked; case GPGME_ATTR_KEY_INVALID: return certsig->invalid; case GPGME_ATTR_KEY_EXPIRED: return certsig->expired; case GPGME_ATTR_SIG_CLASS: return certsig->sig_class; case GPGME_ATTR_SIG_STATUS: return certsig->status; default: return 0; } }