Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/agent/agent.h b/agent/agent.h
index 626bf48c9..efdfe5b40 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -1,813 +1,813 @@
/* agent.h - Global definitions for the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef AGENT_H
#define AGENT_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT
#include <gpg-error.h>
#define map_assuan_err(a) \
map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
#include <errno.h>
#include <assuan.h>
#include <gcrypt.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/session-env.h"
#include "../common/shareddefs.h"
#include "../common/name-value.h"
/* To convey some special hash algorithms we use algorithm numbers
reserved for application use. */
#ifndef GCRY_MODULE_ID_USER
#define GCRY_MODULE_ID_USER 1024
#endif
#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1)
/* Maximum length of a digest. */
#define MAX_DIGEST_LEN 64
/* The maximum length of a passphrase (in bytes). Note: this is
further constrained by the Assuan line length (and any other text on
the same line). However, the Assuan line length is 1k bytes so
this shouldn't be a problem in practice. */
#define MAX_PASSPHRASE_LEN 255
/* The daemons we support. When you add a new daemon, add to
both the daemon_type and the daemon_modules array in call-daemon.c */
enum daemon_type
{
DAEMON_SCD,
DAEMON_TPM2D,
DAEMON_MAX_TYPE
};
/* A large struct name "opt" to keep global flags */
EXTERN_UNLESS_MAIN_MODULE
struct
{
unsigned int debug; /* Debug flags (DBG_foo_VALUE) */
int verbose; /* Verbosity level */
int quiet; /* Be as quiet as possible */
int dry_run; /* Don't change any persistent data */
int batch; /* Batch mode */
/* True if we handle sigusr2. */
int sigusr2_enabled;
/* Environment settings gathered at program start or changed using the
Assuan command UPDATESTARTUPTTY. */
session_env_t startup_env;
char *startup_lc_ctype;
char *startup_lc_messages;
/* Enable pinentry debugging (--debug 1024 should also be used). */
int debug_pinentry;
/* Filename of the program to start as pinentry (malloced). */
char *pinentry_program;
/* Filename of the program to handle daemon tasks. */
const char *daemon_program[DAEMON_MAX_TYPE];
int disable_daemon[DAEMON_MAX_TYPE]; /* Never use the daemon. */
int no_grab; /* Don't let the pinentry grab the keyboard */
/* The name of the file pinentry shall touch before exiting. If
this is not set the file name of the standard socket is used. */
const char *pinentry_touch_file;
/* A string where the first character is used by the pinentry as a
custom invisible character. */
char *pinentry_invisible_char;
/* The timeout value for the Pinentry in seconds. This is passed to
the pinentry if it is not 0. It is up to the pinentry to act
upon this timeout value. */
unsigned long pinentry_timeout;
/* If set, then passphrase formatting is enabled in pinentry. */
int pinentry_formatted_passphrase;
/* The default and maximum TTL of cache entries. */
unsigned long def_cache_ttl; /* Default. */
unsigned long def_cache_ttl_ssh; /* for SSH. */
unsigned long max_cache_ttl; /* Default. */
unsigned long max_cache_ttl_ssh; /* for SSH. */
/* Flag disallowing bypassing of the warning. */
int enforce_passphrase_constraints;
/* The required minimum length of a passphrase. */
unsigned int min_passphrase_len;
/* The minimum number of non-alpha characters in a passphrase. */
unsigned int min_passphrase_nonalpha;
/* File name with a patternfile or NULL if not enabled. If the
* second one is set, it is used for symmetric only encryption
* instead of the former. */
const char *check_passphrase_pattern;
const char *check_sym_passphrase_pattern;
/* If not 0 the user is asked to change his passphrase after these
number of days. */
unsigned int max_passphrase_days;
/* If set, a passphrase history will be written and checked at each
passphrase change. */
int enable_passphrase_history;
int running_detached; /* We are running detached from the tty. */
/* If this global option is true, the passphrase cache is ignored
for signing operations. */
int ignore_cache_for_signing;
/* If this global option is true, the user is allowed to
interactively mark certificate in trustlist.txt as trusted. */
int allow_mark_trusted;
/* Only use the system trustlist. */
int no_user_trustlist;
/* The standard system trustlist is SYSCONFDIR/trustlist.txt. This
* option can be used to change the name. */
const char *sys_trustlist_name;
/* If this global option is true, the Assuan command
PRESET_PASSPHRASE is allowed. */
int allow_preset_passphrase;
/* If this global option is true, the Assuan option
pinentry-mode=loopback is allowed. */
int allow_loopback_pinentry;
/* Allow the use of an external password cache. If this option is
enabled (which is the default) we send an option to Pinentry
to allow it to enable such a cache. */
int allow_external_cache;
/* If this global option is true, the Assuan option of Pinentry
allow-emacs-prompt is allowed. */
int allow_emacs_pinentry;
int keep_tty; /* Don't switch the TTY (for pinentry) on request */
int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */
/* This global option indicates the use of an extra socket. Note
that we use a hack for cleanup handling in gpg-agent.c: If the
value is less than 2 the name has not yet been malloced. */
int extra_socket;
/* This global option indicates the use of an extra socket for web
browsers. Note that we use a hack for cleanup handling in
gpg-agent.c: If the value is less than 2 the name has not yet
been malloced. */
int browser_socket;
/* The digest algorithm to use for ssh fingerprints when
* communicating with the user. */
int ssh_fingerprint_digest;
/* The value of the option --s2k-count. If this option is not given
* or 0 an auto-calibrated value is used. */
unsigned long s2k_count;
} opt;
/* Bit values for the --debug option. */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
/* Test macros for the debug option. */
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
/* Forward reference for local definitions in command.c. */
struct server_local_s;
/* Declaration of objects from command-ssh.c. */
struct ssh_control_file_s;
typedef struct ssh_control_file_s *ssh_control_file_t;
/* Forward reference for local definitions in call-scd.c. */
struct daemon_local_s;
/* Object to hold ephemeral secret keys. */
struct ephemeral_private_key_s
{
struct ephemeral_private_key_s *next;
unsigned char grip[KEYGRIP_LEN];
unsigned char *keybuf; /* Canon-s-exp with the private key (malloced). */
size_t keybuflen;
};
typedef struct ephemeral_private_key_s *ephemeral_private_key_t;
/* Collection of data per session (aka connection). */
struct server_control_s
{
/* Private data used to fire up the connection thread. We use this
structure do avoid an extra allocation for only a few bytes while
spawning a new connection thread. */
struct {
gnupg_fd_t fd;
} thread_startup;
/* Flag indicating the connection is run in restricted mode.
A value of 1 if used for --extra-socket,
a value of 2 is used for --browser-socket. */
int restricted;
/* Private data of the server (command.c). */
struct server_local_s *server_local;
/* Private data of the daemon (call-XXX.c). */
struct daemon_local_s *d_local[DAEMON_MAX_TYPE];
/* Linked list with ephemeral stored private keys. */
ephemeral_private_key_t ephemeral_keys;
/* If set functions will lookup keys in the ephemeral_keys list. */
int ephemeral_mode;
/* Environment settings for the connection. */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
unsigned long client_pid;
int client_uid;
/* The current pinentry mode. */
pinentry_mode_t pinentry_mode;
/* The TTL used for the --preset option of certain commands. */
int cache_ttl_opt_preset;
/* Information on the currently used digest (for signing commands). */
struct {
char *data; /* NULL or malloced data of length VALUELEN. If
this is set the other fields are ignored. Used
for PureEdDSA and RSA with PSS (in which case
data_is_pss is also set). */
int valuelen;
int algo;
unsigned char value[MAX_DIGEST_LEN];
unsigned int raw_value: 1;
unsigned int is_pss: 1; /* DATA holds PSS formatted data. */
} digest;
unsigned int have_keygrip: 1;
unsigned int have_keygrip1: 1;
unsigned char keygrip[20];
unsigned char keygrip1[20]; /* Another keygrip for hybrid crypto. */
/* A flag to enable a hack to send the PKAUTH command instead of the
PKSIGN command to the scdaemon. */
int use_auth_call;
/* A flag to inhibit enforced passphrase change during an explicit
passwd command. */
int in_passwd;
/* The current S2K which might be different from the calibrated
count. */
unsigned long s2k_count;
/* If pinentry is active for this thread. It can be more than 1,
when pinentry is called recursively. */
int pinentry_active;
};
/* Status of pinentry. */
enum
{
PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0,
PINENTRY_STATUS_PIN_REPEATED = 1 << 8,
PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9,
PINENTRY_STATUS_PASSWORD_GENERATED = 1 << 10
};
/* Information pertaining to pinentry requests. */
struct pin_entry_info_s
{
int min_digits; /* min. number of digits required or 0 for freeform entry */
int max_digits; /* max. number of allowed digits allowed*/
int max_tries; /* max. number of allowed tries. */
unsigned int constraints_flags; /* CHECK_CONSTRAINTS_... */
int failed_tries; /* Number of tries so far failed. */
int with_qualitybar; /* Set if the quality bar should be displayed. */
int with_repeat; /* Request repetition of the passphrase. */
int repeat_okay; /* Repetition worked. */
unsigned int status; /* Status. */
gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check
the PIN */
void *check_cb_arg; /* optional argument which might be of use in the CB */
const char *cb_errtext; /* used by the cb to display a specific error */
size_t max_length; /* Allocated length of the buffer PIN. */
char pin[1]; /* The buffer to hold the PIN or passphrase.
It's actual allocated length is given by
MAX_LENGTH (above). */
};
/* Types of the private keys. */
enum
{
PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */
PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */
PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */
PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard
based key. */
PROTECTED_SHARED_SECRET = 4, /* RFU. */
PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */
};
/* Values for the cache_mode arguments. */
typedef enum
{
CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */
CACHE_MODE_ANY, /* Any mode except ignore and data matches. */
CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */
CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */
CACHE_MODE_SSH, /* SSH related cache. */
CACHE_MODE_NONCE, /* This is a non-predictable nonce. */
CACHE_MODE_PIN, /* PINs stored/retrieved by scdaemon. */
CACHE_MODE_DATA /* Arbitrary data. */
}
cache_mode_t;
/* The TTL is seconds used for adding a new nonce mode cache item. */
#define CACHE_TTL_NONCE 120
/* The TTL in seconds used by the --preset option of some commands.
This is the default value changeable by an OPTION command. */
#define CACHE_TTL_OPT_PRESET 900
/* The type of a function to lookup a TTL by a keygrip. */
typedef int (*lookup_ttl_t)(const char *hexgrip);
/* This is a special version of the usual _() gettext macro. It
assumes a server connection control variable with the name "ctrl"
and uses that to translate a string according to the locale set for
the connection. The macro LunderscoreIMPL is used by i18n to
actually define the inline function when needed. */
#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
#define L_(a) agent_Lunderscore (ctrl, (a))
#define LunderscorePROTO \
static inline const char *agent_Lunderscore (ctrl_t ctrl, \
const char *string) \
GNUPG_GCC_ATTR_FORMAT_ARG(2);
#define LunderscoreIMPL \
static inline const char * \
agent_Lunderscore (ctrl_t ctrl, const char *string) \
{ \
return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
/* */: gettext (string); \
}
#else
#define L_(a) (a)
#endif
/* Information from scdaemon for card keys. */
struct card_key_info_s
{
struct card_key_info_s *next;
char keygrip[41];
char *serialno;
char *idstr;
char *usage;
};
/*-- gpg-agent.c --*/
void agent_exit (int rc)
GPGRT_ATTR_NORETURN; /* Also implemented in other tools */
void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl);
gpg_error_t agent_copy_startup_env (ctrl_t ctrl);
const char *get_agent_socket_name (void);
const char *get_agent_ssh_socket_name (void);
int get_agent_active_connection_count (void);
#ifdef HAVE_W32_SYSTEM
void *get_agent_daemon_notify_event (void);
#endif
void agent_sighup_action (void);
int map_pk_openpgp_to_gcry (int openpgp_algo);
void agent_kick_the_loop (void);
/*-- command.c --*/
gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid,
const char *extra);
gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...)
GPGRT_ATTR_SENTINEL(0);
gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword,
const char *format, ...)
GPGRT_ATTR_PRINTF(3,4);
void bump_key_eventcounter (void);
void bump_card_eventcounter (void);
void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
gpg_error_t pinentry_loopback (ctrl_t, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length);
gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok);
#ifdef HAVE_W32_SYSTEM
int serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen);
#endif /*HAVE_W32_SYSTEM*/
/*-- command-ssh.c --*/
ssh_control_file_t ssh_open_control_file (void);
void ssh_close_control_file (ssh_control_file_t cf);
gpg_error_t ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip, int *r_disabled,
int *r_ttl, int *r_confirm);
gpg_error_t ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled,
int *r_ttl, int *r_confirm);
void start_command_handler_ssh_stream (ctrl_t ctrl, estream_t stream);
void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
/*-- findkey.c --*/
gpg_error_t agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result);
gpg_error_t agent_write_private_key (ctrl_t ctrl,
const unsigned char *grip,
const void *buffer, size_t length,
int force,
const char *serialno, const char *keyref,
const char *dispserialno,
- time_t timestamp);
+ time_t timestamp, const char *linkattr);
gpg_error_t agent_key_from_file (ctrl_t ctrl,
const char *cache_nonce,
const char *desc_text,
const unsigned char *grip,
unsigned char **shadow_info,
cache_mode_t cache_mode,
lookup_ttl_t lookup_ttl,
gcry_sexp_t *result,
char **r_passphrase, time_t *r_timestamp);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result, int *r_order);
int agent_pk_get_algo (gcry_sexp_t s_key);
int agent_is_tpm2_key(gcry_sexp_t s_key);
int agent_key_available (ctrl_t ctrl, const unsigned char *grip);
gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype,
unsigned char **r_shadow_info,
unsigned char **r_shadow_info_type);
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip,
int force, int only_stubs);
gpg_error_t agent_update_private_key (ctrl_t ctrl,
const unsigned char *grip, nvc_t pk);
/*-- call-pinentry.c --*/
void initialize_module_call_pinentry (void);
void agent_query_dump_state (void);
void agent_reset_query (ctrl_t ctrl);
int pinentry_active_p (ctrl_t ctrl, int waitseconds);
gpg_error_t agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *inital_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode);
int agent_get_passphrase (ctrl_t ctrl, char **retpass,
const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode,
struct pin_entry_info_s *pininfo);
int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
const char *notokay, int with_cancel);
int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
int agent_popup_message_start (ctrl_t ctrl,
const char *desc, const char *ok_btn);
void agent_popup_message_stop (ctrl_t ctrl);
int agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode);
/*-- cache.c --*/
void initialize_module_cache (void);
void deinitialize_module_cache (void);
struct timespec *agent_cache_expiration (void);
void agent_flush_cache (int pincache_only);
int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
const char *data, int ttl);
char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode);
void agent_store_cache_hit (const char *key);
/*-- pksign.c --*/
gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
gcry_sexp_t *signature_sexp,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
const void *overridedata, size_t overridedatalen);
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode);
/*-- pkdecrypt.c --*/
gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding);
enum kemids
{
KEM_PQC_PGP,
KEM_PGP,
KEM_CMS
};
gpg_error_t agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid,
const unsigned char *ct, size_t ctlen,
membuf_t *outbuf);
/*-- genkey.c --*/
#define CHECK_CONSTRAINTS_NOT_EMPTY 1
#define CHECK_CONSTRAINTS_NEW_SYMKEY 2
#define GENKEY_FLAG_NO_PROTECTION 1
#define GENKEY_FLAG_PRESET 2
void clear_ephemeral_keys (ctrl_t ctrl);
int check_passphrase_constraints (ctrl_t ctrl, const char *pw,
unsigned int flags,
char **failed_constraint);
gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
char **r_passphrase);
int agent_genkey (ctrl_t ctrl, unsigned int flags,
const char *cache_nonce, time_t timestamp,
const char *keyparam, size_t keyparmlen,
const char *override_passphrase,
membuf_t *outbuf);
gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr);
/*-- protect.c --*/
void set_s2k_calibration_time (unsigned int milliseconds);
unsigned long get_calibrated_s2k_count (void);
unsigned long get_standard_s2k_count (void);
unsigned char get_standard_s2k_count_rfc4880 (void);
unsigned long get_standard_s2k_time (void);
int agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count);
gpg_error_t agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen);
int agent_private_key_type (const unsigned char *privatekey);
unsigned char *make_shadow_info (const char *serialno, const char *idstring);
int agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result);
int agent_shadow_key_type (const unsigned char *pubkey,
const unsigned char *shadow_info,
const unsigned char *type,
unsigned char **result);
gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info);
gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey,
unsigned char const **shadow_info,
unsigned char **shadow_type);
gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen);
gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned int s2kcount,
unsigned char *key, size_t keylen);
gpg_error_t agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force,
const char *dispserialno);
/*-- trustlist.c --*/
void initialize_module_trustlist (void);
gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled);
gpg_error_t agent_listtrusted (ctrl_t ctrl, void *assuan_context,
int status_mode);
gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
const char *fpr, int flag);
void agent_reload_trustlist (void);
/*-- divert-tpm2.c --*/
#ifdef HAVE_LIBTSS
int divert_tpm2_pksign (ctrl_t ctrl,
const unsigned char *digest, size_t digestlen, int algo,
const unsigned char *shadow_info, unsigned char **r_sig,
size_t *r_siglen);
int divert_tpm2_pkdecrypt (ctrl_t ctrl,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len, int *r_padding);
int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t s_skey);
int agent_tpm2d_ecc_kem (ctrl_t ctrl, const unsigned char *shadow_info,
const unsigned char *ecc_ct,
size_t ecc_point_len, unsigned char *ecc_ecdh);
#else /*!HAVE_LIBTSS*/
static inline int
divert_tpm2_pksign (ctrl_t ctrl,
const unsigned char *digest,
size_t digestlen, int algo,
const unsigned char *shadow_info,
unsigned char **r_sig,
size_t *r_siglen)
{
(void)ctrl; (void)digest; (void)digestlen;
(void)algo; (void)shadow_info; (void)r_sig; (void)r_siglen;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
static inline int
divert_tpm2_pkdecrypt (ctrl_t ctrl,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len,
int *r_padding)
{
(void)ctrl; (void)cipher; (void)shadow_info;
(void)r_buf; (void)r_len; (void)r_padding;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
static inline int
divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t s_skey)
{
(void)ctrl; (void)grip; (void)s_skey;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
static inline int
agent_tpm2d_ecc_kem (ctrl_t ctrl, const unsigned char *shadow_info,
const unsigned char *ecc_ct,
size_t ecc_point_len, unsigned char *ecc_ecdh)
{
(void)ctrl; (void)ecc_ct;
(void)ecc_point_len; (void)ecc_ecdh;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
#endif /*!HAVE_LIBTSS*/
/*-- divert-scd.c --*/
int divert_pksign (ctrl_t ctrl,
const unsigned char *grip,
const unsigned char *digest, size_t digestlen, int algo,
unsigned char **r_sig,
size_t *r_siglen);
int divert_pkdecrypt (ctrl_t ctrl,
const unsigned char *grip,
const unsigned char *cipher,
char **r_buf, size_t *r_len, int *r_padding);
int divert_generic_cmd (ctrl_t ctrl,
const char *cmdline, void *assuan_context);
gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen);
gpg_error_t agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct,
size_t ecc_point_len, unsigned char *ecc_ecdh);
/*-- call-daemon.c --*/
gpg_error_t daemon_start (enum daemon_type type, ctrl_t ctrl, int req_sock);
assuan_context_t daemon_type_ctx (enum daemon_type type, ctrl_t ctrl);
gpg_error_t daemon_unlock (enum daemon_type type, ctrl_t ctrl, gpg_error_t rc);
void initialize_module_daemon (void);
void agent_daemon_dump_state (void);
int agent_daemon_check_running (enum daemon_type type);
void agent_daemon_check_aliveness (void);
void agent_reset_daemon (ctrl_t ctrl);
void agent_kill_daemon (enum daemon_type type);
/*-- call-tpm2d.c --*/
int agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info,
gcry_sexp_t s_skey);
int agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest,
size_t digestlen, const unsigned char *shadow_info,
unsigned char **r_sig, size_t *r_siglen);
int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher,
size_t cipherlen, const unsigned char *shadow_info,
char **r_buf, size_t *r_len);
/*-- call-scd.c --*/
int agent_card_learn (ctrl_t ctrl, const char *demand_sn,
void (*kpinfo_cb)(void*, const char *),
void *kpinfo_cb_arg,
void (*certinfo_cb)(void*, const char *),
void *certinfo_cb_arg,
void (*sinfo_cb)(void*, const char *,
size_t, const char *),
void *sinfo_cb_arg);
int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand);
int agent_card_pksign (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg,
const char *desc_text,
int mdalgo,
const unsigned char *indata, size_t indatalen,
unsigned char **r_buf, size_t *r_buflen);
int agent_card_pkdecrypt (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*,size_t),
void *getpin_cb_arg,
const char *desc_text,
const unsigned char *indata, size_t indatalen,
unsigned char **r_buf, size_t *r_buflen,
int *r_padding);
int agent_card_readcert (ctrl_t ctrl,
const char *id, char **r_buf, size_t *r_buflen);
int agent_card_readkey (ctrl_t ctrl, const char *id,
unsigned char **r_buf, char **r_keyref);
gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg);
gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result,
const char *keygrip);
int agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg, void *assuan_context);
void agent_card_free_keyinfo (struct card_key_info_s *l);
gpg_error_t agent_card_keyinfo (ctrl_t ctrl, const char *keygrip,
int cap, struct card_key_info_s **result);
/*-- learncard.c --*/
int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context,
int force, const char *demand_sn);
/*-- cvt-openpgp.c --*/
gpg_error_t
extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
const char **r_algoname, int *r_npkey, int *r_nskey,
const char **r_format,
gcry_mpi_t *mpi_array, int arraysize,
gcry_sexp_t *r_curve, gcry_sexp_t *r_flags);
/*-- sexp-secret.c --*/
gpg_error_t fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p);
gpg_error_t sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff,
unsigned char *buf);
#endif /*AGENT_H*/
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index ab54a403f..ff4ca058e 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -1,4168 +1,4168 @@
/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol.
* Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc.
* Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs
are:
RFC-4250 - Protocol Assigned Numbers
RFC-4251 - Protocol Architecture
RFC-4252 - Authentication Protocol
RFC-4253 - Transport Layer Protocol
RFC-5656 - ECC support
RFC-8332 - Use of RSA Keys with SHA-256 and SHA-512
The protocol for the agent is defined in:
https://tools.ietf.org/html/draft-miller-ssh-agent
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SYS_UCRED_H
#include <sys/ucred.h>
#endif
#ifdef HAVE_UCRED_H
#include <ucred.h>
#endif
#include "agent.h"
#include "../common/i18n.h"
#include "../common/util.h"
#include "../common/ssh-utils.h"
/* Request types. */
#define SSH_REQUEST_REQUEST_IDENTITIES 11
#define SSH_REQUEST_SIGN_REQUEST 13
#define SSH_REQUEST_ADD_IDENTITY 17
#define SSH_REQUEST_REMOVE_IDENTITY 18
#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19
#define SSH_REQUEST_LOCK 22
#define SSH_REQUEST_UNLOCK 23
#define SSH_REQUEST_ADD_ID_CONSTRAINED 25
#define SSH_REQUEST_EXTENSION 27
/* Options. */
#define SSH_OPT_CONSTRAIN_LIFETIME 1
#define SSH_OPT_CONSTRAIN_CONFIRM 2
#define SSH_OPT_CONSTRAIN_MAXSIGN 3
#define SSH_OPT_CONSTRAIN_EXTENSION 255
/* Response types. */
#define SSH_RESPONSE_SUCCESS 6
#define SSH_RESPONSE_FAILURE 5
#define SSH_RESPONSE_IDENTITIES_ANSWER 12
#define SSH_RESPONSE_SIGN_RESPONSE 14
#define SSH_RESPONSE_EXTENSION_FAILURE 28
/* Other constants. */
#define SSH_DSA_SIGNATURE_PADDING 20
#define SSH_DSA_SIGNATURE_ELEMS 2
#define SSH_AGENT_RSA_SHA2_256 0x02
#define SSH_AGENT_RSA_SHA2_512 0x04
#define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
#define SPEC_FLAG_IS_ECDSA (1 << 1)
#define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/
#define SPEC_FLAG_WITH_CERT (1 << 7)
#define SPEC_FLAG_WITH_FIXEDLENGTH (1 << 8)
/* The name of the control file. */
#define SSH_CONTROL_FILE_NAME "sshcontrol"
/* The blurb we put into the header of a newly created control file. */
static const char sshcontrolblurb[] =
"# List of allowed ssh keys. Only keys present in this file are used\n"
"# in the SSH protocol. The ssh-add tool may add new entries to this\n"
"# file to enable them; you may also add them manually. Comment\n"
"# lines, like this one, as well as empty lines are ignored. Lines do\n"
"# have a certain length limit but this is not serious limitation as\n"
"# the format of the entries is fixed and checked by gpg-agent. A\n"
"# non-comment line starts with optional white spaces, followed by the\n"
"# keygrip of the key given as 40 hex digits, optionally followed by a\n"
"# caching TTL in seconds, and another optional field for arbitrary\n"
"# flags. Prepend the keygrip with an '!' mark to disable it.\n"
"\n";
/* Macros. */
/* Return a new uint32 with b0 being the most significant byte and b3
being the least significant byte. */
#define uint32_construct(b0, b1, b2, b3) \
((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
/*
* Basic types.
*/
/* Type for a request handler. */
typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl,
estream_t request,
estream_t response);
struct ssh_key_type_spec;
typedef struct ssh_key_type_spec ssh_key_type_spec_t;
/* Type, which is used for associating request handlers with the
appropriate request IDs. */
typedef struct ssh_request_spec
{
unsigned char type;
ssh_request_handler_t handler;
const char *identifier;
unsigned int secret_input;
} ssh_request_spec_t;
/* Type for "key modifier functions", which are necessary since
OpenSSH and GnuPG treat key material slightly different. A key
modifier is called right after a new key identity has been received
in order to "sanitize" the material. */
typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems,
gcry_mpi_t *mpis);
/* The encoding of a generated signature is dependent on the
algorithm; therefore algorithm specific signature encoding
functions are necessary. */
typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t sig);
/* Type, which is used for bundling all the algorithm specific
information together in a single object. */
struct ssh_key_type_spec
{
/* Algorithm identifier as used by OpenSSH. */
const char *ssh_identifier;
/* Human readable name of the algorithm. */
const char *name;
/* Algorithm identifier as used by GnuPG. */
int algo;
/* List of MPI names for secret keys; order matches the one of the
agent protocol. */
const char *elems_key_secret;
/* List of MPI names for public keys; order matches the one of the
agent protocol. */
const char *elems_key_public;
/* List of MPI names for signature data. */
const char *elems_signature;
/* List of MPI names for secret keys; order matches the one, which
is required by gpg-agent's key access layer. */
const char *elems_sexp_order;
/* Key modifier function. Key modifier functions are necessary in
order to fix any inconsistencies between the representation of
keys on the SSH and on the GnuPG side. */
ssh_key_modifier_t key_modifier;
/* Signature encoder function. Signature encoder functions are
necessary since the encoding of signatures depends on the used
algorithm. */
ssh_signature_encoder_t signature_encoder;
/* The name of the ECC curve or NULL for non-ECC algos. This is the
* canonical name for the curve as specified by RFC-5656. */
const char *curve_name;
/* An alias for curve_name or NULL. Actually this is Libcgrypt's
* primary name of the curve. */
const char *alt_curve_name;
/* The hash algorithm to be used with this key. 0 for using the
default. */
int hash_algo;
/* Misc flags. */
unsigned int flags;
/* Optional key size (possibly used by RSA) */
size_t keysize;
};
/* Definition of an object to access the sshcontrol file. */
struct ssh_control_file_s
{
char *fname; /* Name of the file. */
estream_t fp; /* This is never NULL. */
int lnr; /* The current line number. */
struct {
int valid; /* True if the data of this structure is valid. */
int disabled; /* The item is disabled. */
int ttl; /* The TTL of the item. */
int confirm; /* The confirm flag is set. */
char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */
} item;
};
/* Two objects definition to hold keys for later sorting. */
struct key_collection_item_s
{
gcry_sexp_t key; /* Public key. (owned by us) */
char *cardsn; /* Serial number of a card or NULL. (owned by us) */
int order; /* Computed ordinal */
};
struct key_collection_s
{
struct key_collection_item_s *items;
size_t allocated;
size_t nitems;
};
/* Prototypes. */
static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_lock (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_unlock (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_extension (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis);
static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
struct peer_info_s
{
unsigned long pid;
int uid;
};
/* Global variables. */
/* Associating request types with the corresponding request
handlers. */
static const ssh_request_spec_t request_specs[] =
{
#define REQUEST_SPEC_DEFINE(id, name, secret_input) \
{ SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input }
REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1),
REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0),
REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1),
REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1),
REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0),
REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0),
REQUEST_SPEC_DEFINE (LOCK, lock, 0),
REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0),
REQUEST_SPEC_DEFINE (EXTENSION, extension, 0)
#undef REQUEST_SPEC_DEFINE
};
/* Table holding key type specifications. */
static const ssh_key_type_spec_t ssh_key_types[] =
{
{
"ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA
},
{
"ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2
},
{
"ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, NULL, 0, 0
},
{
"ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
},
{
"ssh-ed25519-cert-v01@openssh.com", "Ed25519",
GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
},
{
"ssh-rsa-cert-v01@openssh.com", "RSA",
GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
},
{
"ssh-dss-cert-v01@openssh.com", "DSA",
GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", "NIST P-256", GCRY_MD_SHA256,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", "NIST P-384", GCRY_MD_SHA384,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", "NIST P-521", GCRY_MD_SHA512,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
}
};
/*
General utility functions.
*/
/* A secure realloc, i.e. it makes sure to allocate secure memory if A
is NULL. This is required because the standard gcry_realloc does
not know whether to allocate secure or normal if NULL is passed as
existing buffer. */
static void *
realloc_secure (void *a, size_t n)
{
void *p;
if (a)
p = gcry_realloc (a, n);
else
p = gcry_malloc_secure (n);
return p;
}
/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns
* NULL if not found. If found the ssh indetifier is returned and a
* pointer to the canonical curve name as specified for ssh is stored
* at R_CANON_NAME. */
static const char *
ssh_identifier_from_curve_name (const char *curve_name,
const char **r_canon_name)
{
int i;
for (i = 0; i < DIM (ssh_key_types); i++)
if (ssh_key_types[i].curve_name
&& (!strcmp (ssh_key_types[i].curve_name, curve_name)
|| (ssh_key_types[i].alt_curve_name
&& !strcmp (ssh_key_types[i].alt_curve_name, curve_name))))
{
*r_canon_name = ssh_key_types[i].curve_name;
return ssh_key_types[i].ssh_identifier;
}
return NULL;
}
/*
Primitive I/O functions.
*/
/* Read a byte from STREAM, store it in B. */
static gpg_error_t
stream_read_byte (estream_t stream, unsigned char *b)
{
gpg_error_t err;
int ret;
ret = es_fgetc (stream);
if (ret == EOF)
{
if (es_ferror (stream))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
*b = 0;
}
else
{
*b = ret & 0xFF;
err = 0;
}
return err;
}
/* Write the byte contained in B to STREAM. */
static gpg_error_t
stream_write_byte (estream_t stream, unsigned char b)
{
gpg_error_t err;
int ret;
ret = es_fputc (b, stream);
if (ret == EOF)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read a uint32 from STREAM, store it in UINT32. */
static gpg_error_t
stream_read_uint32 (estream_t stream, u32 *uint32)
{
unsigned char buffer[4];
size_t bytes_read;
gpg_error_t err;
int ret;
ret = es_read (stream, buffer, sizeof (buffer), &bytes_read);
if (ret)
err = gpg_error_from_syserror ();
else
{
if (bytes_read != sizeof (buffer))
err = gpg_error (GPG_ERR_EOF);
else
{
u32 n;
n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]);
*uint32 = n;
err = 0;
}
}
return err;
}
/* Write the uint32 contained in UINT32 to STREAM. */
static gpg_error_t
stream_write_uint32 (estream_t stream, u32 uint32)
{
unsigned char buffer[4];
gpg_error_t err;
int ret;
buffer[0] = uint32 >> 24;
buffer[1] = uint32 >> 16;
buffer[2] = uint32 >> 8;
buffer[3] = uint32 >> 0;
ret = es_write (stream, buffer, sizeof (buffer), NULL);
if (ret)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read SIZE bytes from STREAM into BUFFER. */
static gpg_error_t
stream_read_data (estream_t stream, unsigned char *buffer, size_t size)
{
gpg_error_t err;
size_t bytes_read;
int ret;
ret = es_read (stream, buffer, size, &bytes_read);
if (ret)
err = gpg_error_from_syserror ();
else
{
if (bytes_read != size)
err = gpg_error (GPG_ERR_EOF);
else
err = 0;
}
return err;
}
/* Skip over SIZE bytes from STREAM. */
static gpg_error_t
stream_read_skip (estream_t stream, size_t size)
{
char buffer[128];
size_t bytes_to_read, bytes_read;
int ret;
do
{
bytes_to_read = size;
if (bytes_to_read > sizeof buffer)
bytes_to_read = sizeof buffer;
ret = es_read (stream, buffer, bytes_to_read, &bytes_read);
if (ret)
return gpg_error_from_syserror ();
else if (bytes_read != bytes_to_read)
return gpg_error (GPG_ERR_EOF);
else
size -= bytes_to_read;
}
while (size);
return 0;
}
/* Write SIZE bytes from BUFFER to STREAM. */
static gpg_error_t
stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
{
gpg_error_t err;
int ret;
ret = es_write (stream, buffer, size, NULL);
if (ret)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read a binary string from STREAM into STRING, store size of string
in STRING_SIZE. Append a hidden nul so that the result may
directly be used as a C string. Depending on SECURE use secure
memory for STRING. If STRING is NULL do only a dummy read. */
static gpg_error_t
stream_read_string (estream_t stream, unsigned int secure,
unsigned char **string, u32 *string_size)
{
gpg_error_t err;
unsigned char *buffer = NULL;
u32 length = 0;
if (string_size)
*string_size = 0;
/* Read string length. */
err = stream_read_uint32 (stream, &length);
if (err)
goto out;
if (string)
{
/* Allocate space. */
if (secure)
buffer = xtrymalloc_secure (length + 1);
else
buffer = xtrymalloc (length + 1);
if (! buffer)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Read data. */
err = length? stream_read_data (stream, buffer, length) : 0;
if (err)
goto out;
/* Finalize string object. */
buffer[length] = 0;
*string = buffer;
}
else /* Dummy read requested. */
{
err = length? stream_read_skip (stream, length) : 0;
if (err)
goto out;
}
if (string_size)
*string_size = length;
out:
if (err)
xfree (buffer);
return err;
}
/* Read a binary string from STREAM and store it as an opaque MPI at
R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
Depending on SECURE use secure memory. If the string is too large
for key material return an error. */
static gpg_error_t
stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
{
gpg_error_t err;
unsigned char *buffer = NULL;
u32 length = 0;
*r_mpi = NULL;
/* Read string length. */
err = stream_read_uint32 (stream, &length);
if (err)
goto leave;
/* To avoid excessive use of secure memory we check that an MPI is
not too large. */
if (length > (4096/8) + 8)
{
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
err = GPG_ERR_TOO_LARGE;
goto leave;
}
/* Allocate space. */
if (secure)
buffer = xtrymalloc_secure (length+1);
else
buffer = xtrymalloc (length+1);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Read data. */
err = stream_read_data (stream, buffer + 1, length);
if (err)
goto leave;
buffer[0] = 0x40;
*r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
buffer = NULL;
leave:
xfree (buffer);
return err;
}
/* Read a C-string from STREAM, store copy in STRING. */
static gpg_error_t
stream_read_cstring (estream_t stream, char **string)
{
return stream_read_string (stream, 0, (unsigned char **)string, NULL);
}
/* Write a binary string from STRING of size STRING_N to STREAM. */
static gpg_error_t
stream_write_string (estream_t stream,
const unsigned char *string, u32 string_n)
{
gpg_error_t err;
err = stream_write_uint32 (stream, string_n);
if (err)
goto out;
err = stream_write_data (stream, string, string_n);
out:
return err;
}
/* Write a C-string from STRING to STREAM. */
static gpg_error_t
stream_write_cstring (estream_t stream, const char *string)
{
gpg_error_t err;
err = stream_write_string (stream,
(const unsigned char *) string, strlen (string));
return err;
}
/* Read an MPI from STREAM, store it in MPINT. Depending on SECURE
use secure memory. */
static gpg_error_t
stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint)
{
unsigned char *mpi_data;
u32 mpi_data_size;
gpg_error_t err;
gcry_mpi_t mpi;
mpi_data = NULL;
err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size);
if (err)
goto out;
/* To avoid excessive use of secure memory we check that an MPI is
not too large. */
if (mpi_data_size > 520)
{
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
err = GPG_ERR_TOO_LARGE;
goto out;
}
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL);
if (err)
goto out;
*mpint = mpi;
out:
xfree (mpi_data);
return err;
}
/* Write the MPI contained in MPINT to STREAM. */
static gpg_error_t
stream_write_mpi (estream_t stream, gcry_mpi_t mpint)
{
unsigned char *mpi_buffer;
size_t mpi_buffer_n;
gpg_error_t err;
mpi_buffer = NULL;
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint);
if (err)
goto out;
err = stream_write_string (stream, mpi_buffer, mpi_buffer_n);
out:
xfree (mpi_buffer);
return err;
}
/* Copy data from SRC to DST until EOF is reached. */
static gpg_error_t
stream_copy (estream_t dst, estream_t src)
{
char buffer[BUFSIZ];
size_t bytes_read;
gpg_error_t err;
int ret;
err = 0;
while (1)
{
ret = es_read (src, buffer, sizeof (buffer), &bytes_read);
if (ret || (! bytes_read))
{
if (ret)
err = gpg_error_from_syserror ();
break;
}
ret = es_write (dst, buffer, bytes_read, NULL);
if (ret)
{
err = gpg_error_from_syserror ();
break;
}
}
return err;
}
/* Open the ssh control file and create it if not available. With
APPEND passed as true the file will be opened in append mode,
otherwise in read only mode. On success 0 is returned and a new
control file object stored at R_CF. On error an error code is
returned and NULL is stored at R_CF. */
static gpg_error_t
open_control_file (ssh_control_file_t *r_cf, int append)
{
gpg_error_t err;
ssh_control_file_t cf;
cf = xtrycalloc (1, sizeof *cf);
if (!cf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Note: As soon as we start to use non blocking functions here
(i.e. where Pth might switch threads) we need to employ a
mutex. */
cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
if (!cf->fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: With "a+" we are not able to check whether this will
be created and thus the blurb needs to be written first. */
cf->fp = es_fopen (cf->fname, append? "a+":"r");
if (!cf->fp && errno == ENOENT)
{
estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"),
cf->fname, gpg_strerror (err));
goto leave;
}
es_fputs (sshcontrolblurb, stream);
es_fclose (stream);
cf->fp = es_fopen (cf->fname, append? "a+":"r");
}
if (!cf->fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
cf->fname, gpg_strerror (err));
goto leave;
}
err = 0;
leave:
if (err && cf)
{
if (cf->fp)
es_fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
else
*r_cf = cf;
return err;
}
static void
rewind_control_file (ssh_control_file_t cf)
{
es_fseek (cf->fp, 0, SEEK_SET);
cf->lnr = 0;
es_clearerr (cf->fp);
}
static void
close_control_file (ssh_control_file_t cf)
{
if (!cf)
return;
es_fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
/* Read the next line from the control file and store the data in CF.
Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
static gpg_error_t
read_control_file_item (ssh_control_file_t cf)
{
int c, i, n;
char *p, *pend, line[256];
long ttl = 0;
cf->item.valid = 0;
es_clearerr (cf->fp);
do
{
if (!es_fgets (line, DIM(line)-1, cf->fp) )
{
if (es_feof (cf->fp))
return gpg_error (GPG_ERR_EOF);
return gpg_error_from_syserror ();
}
cf->lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line */
while ((c = es_getc (cf->fp)) != EOF && c != '\n')
;
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
}
while (!*p || *p == '\n' || *p == '#');
cf->item.disabled = 0;
if (*p == '!')
{
cf->item.disabled = 1;
for (p++; spacep (p); p++)
;
}
for (i=0; hexdigitp (p) && i < 40; p++, i++)
cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p);
cf->item.hexgrip[i] = 0;
if (i != 40 || !(spacep (p) || *p == '\n'))
{
log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
ttl = strtol (p, &pend, 10);
p = pend;
if (!(spacep (p) || *p == '\n') || (int)ttl < -1)
{
log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr);
cf->item.ttl = 0;
}
cf->item.ttl = ttl;
/* Now check for key-value pairs of the form NAME[=VALUE]. */
cf->item.confirm = 0;
while (*p)
{
for (; spacep (p) && *p != '\n'; p++)
;
if (!*p || *p == '\n')
break;
n = strcspn (p, "= \t\n");
if (p[n] == '=')
{
log_error ("%s:%d: assigning a value to a flag is not yet supported; "
"flag ignored\n", cf->fname, cf->lnr);
p++;
}
else if (n == 7 && !memcmp (p, "confirm", 7))
{
cf->item.confirm = 1;
}
else
log_error ("%s:%d: invalid flag '%.*s'; ignored\n",
cf->fname, cf->lnr, n, p);
p += n;
}
/* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */
/* cf->fname, cf->lnr, */
/* cf->item.hexgrip, cf->item.ttl, */
/* cf->item.disabled? " disabled":"", */
/* cf->item.confirm? " confirm":""); */
cf->item.valid = 1;
return 0; /* Okay: valid entry found. */
}
/* Search the control file CF from the beginning until a matching
HEXGRIP is found; return success in this case and store true at
DISABLED if the found key has been disabled. If R_TTL is not NULL
a specified TTL for that key is stored there. If R_CONFIRM is not
NULL it is set to 1 if the key has the confirm flag set. The line
number where the item was found is stored at R_LNR. */
static gpg_error_t
search_control_file (ssh_control_file_t cf, const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm, int *r_lnr)
{
gpg_error_t err;
log_assert (strlen (hexgrip) == 40 );
if (r_disabled)
*r_disabled = 0;
if (r_ttl)
*r_ttl = 0;
if (r_confirm)
*r_confirm = 0;
if (r_lnr)
*r_lnr = -1;
rewind_control_file (cf);
while (!(err=read_control_file_item (cf)))
{
if (!cf->item.valid)
continue; /* Should not happen. */
if (!strcmp (hexgrip, cf->item.hexgrip))
break;
}
if (!err)
{
if (r_disabled)
*r_disabled = cf->item.disabled;
if (r_ttl)
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
if (r_lnr)
*r_lnr = cf->lnr;
}
return err;
}
/* Add an entry to the control file to mark the key with the keygrip
HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
for it. FMTFPR is the fingerprint string. This function is in
general used to add a key received through the ssh-add function.
We can assume that the user wants to allow ssh using this key. */
static gpg_error_t
add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
const char *hexgrip, gcry_sexp_t key,
int ttl, int confirm)
{
gpg_error_t err;
ssh_control_file_t cf;
int disabled;
char *fpr_md5 = NULL;
char *fpr_sha256 = NULL;
(void)ctrl;
err = open_control_file (&cf, 1);
if (err)
return err;
err = search_control_file (cf, hexgrip, &disabled, NULL, NULL, NULL);
if (err && gpg_err_code(err) == GPG_ERR_EOF)
{
struct tm *tp;
time_t atime = time (NULL);
err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5);
/* ignore the errors as MD5 is not available in FIPS mode */
if (err)
fpr_md5 = NULL;
err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256);
if (err)
goto out;
/* Not yet in the file - add it. Because the file has been
opened in append mode, we simply need to write to it. */
tp = localtime (&atime);
es_fprintf (cf->fp,
("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
"# Fingerprints: %s\n"
"# %s\n"
"%s %d%s\n"),
spec->name,
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec,
fpr_md5? fpr_md5:"", fpr_sha256, hexgrip, ttl,
confirm? " confirm":"");
}
out:
xfree (fpr_md5);
xfree (fpr_sha256);
close_control_file (cf);
return 0;
}
/* Scan the sshcontrol file and return the TTL. */
static int
ttl_from_sshcontrol (const char *hexgrip)
{
ssh_control_file_t cf;
int disabled, ttl;
if (!hexgrip || strlen (hexgrip) != 40)
return 0; /* Wrong input: Use global default. */
if (open_control_file (&cf, 0))
return 0; /* Error: Use the global default TTL. */
if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL, NULL)
|| disabled)
ttl = 0; /* Use the global default if not found or disabled. */
close_control_file (cf);
return ttl;
}
/* Scan the sshcontrol file and return the confirm flag. */
static int
confirm_flag_from_sshcontrol (const char *hexgrip)
{
ssh_control_file_t cf;
int disabled, confirm;
if (!hexgrip || strlen (hexgrip) != 40)
return 1; /* Wrong input: Better ask for confirmation. */
if (open_control_file (&cf, 0))
return 1; /* Error: Better ask for confirmation. */
if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm, NULL)
|| disabled)
confirm = 0; /* If not found or disabled, there is no reason to
ask for confirmation. */
close_control_file (cf);
return confirm;
}
/* Open the ssh control file for reading. This is a public version of
open_control_file. The caller must use ssh_close_control_file to
release the returned handle. */
ssh_control_file_t
ssh_open_control_file (void)
{
ssh_control_file_t cf;
/* Then look at all the registered and non-disabled keys. */
if (open_control_file (&cf, 0))
return NULL;
return cf;
}
/* Close an ssh control file handle. This is the public version of
close_control_file. CF may be NULL. */
void
ssh_close_control_file (ssh_control_file_t cf)
{
close_control_file (cf);
}
/* Read the next item from the ssh control file. The function returns
0 if a item was read, GPG_ERR_EOF on eof or another error value.
R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
file; they are only set on success. */
gpg_error_t
ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
do
err = read_control_file_item (cf);
while (!err && !cf->item.valid);
if (!err)
{
if (r_hexgrip)
strcpy (r_hexgrip, cf->item.hexgrip);
if (r_disabled)
*r_disabled = cf->item.disabled;
if (r_ttl)
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
}
return err;
}
/* Search for a key with HEXGRIP in sshcontrol and return all
info. */
gpg_error_t
ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
int i;
const char *s;
char uphexgrip[41];
/* We need to make sure that HEXGRIP is all uppercase. The easiest
way to do this and also check its length is by copying to a
second buffer. */
for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
uphexgrip[i] = 0;
if (i != 40)
err = gpg_error (GPG_ERR_INV_LENGTH);
else
err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm,
NULL);
if (gpg_err_code (err) == GPG_ERR_EOF)
err = gpg_error (GPG_ERR_NOT_FOUND);
return err;
}
/*
MPI lists.
*/
/* Free the list of MPIs MPI_LIST. */
static void
mpint_list_free (gcry_mpi_t *mpi_list)
{
if (mpi_list)
{
unsigned int i;
for (i = 0; mpi_list[i]; i++)
gcry_mpi_release (mpi_list[i]);
xfree (mpi_list);
}
}
/* Receive key material MPIs from STREAM according to KEY_SPEC;
depending on SECRET expect a public key or secret key. CERT is the
certificate blob used if KEY_SPEC indicates the certificate format;
it needs to be positioned to the end of the nonce. The newly
allocated list of MPIs is stored in MPI_LIST. Returns usual error
code. */
static gpg_error_t
ssh_receive_mpint_list (estream_t stream, int secret,
ssh_key_type_spec_t *spec, estream_t cert,
gcry_mpi_t **mpi_list)
{
const char *elems_public;
unsigned int elems_n;
const char *elems;
int elem_is_secret;
gcry_mpi_t *mpis = NULL;
gpg_error_t err = 0;
unsigned int i;
if (secret)
elems = spec->elems_key_secret;
else
elems = spec->elems_key_public;
elems_n = strlen (elems);
elems_public = spec->elems_key_public;
/* Check that either both, CERT and the WITH_CERT flag, are given or
none of them. */
if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
{
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto out;
}
mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
elem_is_secret = 0;
for (i = 0; i < elems_n; i++)
{
if (secret)
elem_is_secret = !strchr (elems_public, elems[i]);
if (cert && !elem_is_secret)
err = stream_read_mpi (cert, elem_is_secret, &mpis[i]);
else
err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
if (err)
goto out;
}
*mpi_list = mpis;
mpis = NULL;
out:
if (err)
mpint_list_free (mpis);
return err;
}
/* Key modifier function for RSA. */
static gpg_error_t
ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis)
{
gcry_mpi_t p;
gcry_mpi_t q;
gcry_mpi_t u;
if (strcmp (elems, "nedupq"))
/* Modifying only necessary for secret keys. */
goto out;
u = mpis[3];
p = mpis[4];
q = mpis[5];
if (gcry_mpi_cmp (p, q) > 0)
{
/* P shall be smaller then Q! Swap primes. iqmp becomes u. */
gcry_mpi_t tmp;
tmp = mpis[4];
mpis[4] = mpis[5];
mpis[5] = tmp;
}
else
/* U needs to be recomputed. */
gcry_mpi_invm (u, p, q);
out:
return 0;
}
/* Signature encoder function for RSA. */
static gpg_error_t
ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data;
size_t data_n;
gcry_mpi_t s;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* RSA specific */
s = mpis[0];
if ((spec->flags & SPEC_FLAG_WITH_FIXEDLENGTH))
{
data = xtrymalloc (spec->keysize);
if (!data)
{
err = gpg_error_from_syserror ();
goto out;
}
err = gcry_mpi_print (GCRYMPI_FMT_USG, data, spec->keysize, &data_n, s);
if (data_n < spec->keysize)
{
memmove (data, data+spec->keysize-data_n, data_n);
memset (data, 0, spec->keysize-data_n);
data_n = spec->keysize;
}
}
else
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s);
if (err)
goto out;
err = stream_write_string (signature_blob, data, data_n);
xfree (data);
out:
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for DSA. */
static gpg_error_t
ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
unsigned char *data = NULL;
size_t data_n;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* DSA specific code. */
/* FIXME: Why this complicated code? Why collecting both mpis in a
buffer instead of writing them out one after the other? */
for (i = 0; i < 2; i++)
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]);
if (err)
break;
if (data_n > SSH_DSA_SIGNATURE_PADDING)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0,
SSH_DSA_SIGNATURE_PADDING - data_n);
memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING)
+ (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n);
xfree (data);
data = NULL;
}
if (err)
goto out;
err = stream_write_string (signature_blob, buffer, sizeof (buffer));
out:
xfree (data);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for ECDSA. */
static gpg_error_t
ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
estream_t stream, gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data[2] = {NULL, NULL};
size_t data_n[2];
size_t innerlen;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* ECDSA specific */
innerlen = 0;
for (i = 0; i < DIM(data); i++)
{
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]);
if (err)
goto out;
innerlen += 4 + data_n[i];
}
err = stream_write_uint32 (stream, innerlen);
if (err)
goto out;
for (i = 0; i < DIM(data); i++)
{
err = stream_write_string (stream, data[i], data_n[i]);
if (err)
goto out;
}
out:
for (i = 0; i < DIM(data); i++)
xfree (data[i]);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for EdDSA. */
static gpg_error_t
ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
estream_t stream, gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data[2] = {NULL, NULL};
size_t data_n[2];
size_t totallen = 0;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
if (elems_n != DIM(data))
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
for (i = 0; i < DIM(data); i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
if (!data[i])
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
totallen += data_n[i];
gcry_sexp_release (sublist);
sublist = NULL;
}
if (err)
goto out;
err = stream_write_uint32 (stream, totallen);
if (err)
goto out;
for (i = 0; i < DIM(data); i++)
{
err = stream_write_data (stream, data[i], data_n[i]);
if (err)
goto out;
}
out:
for (i = 0; i < DIM(data); i++)
xfree (data[i]);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
return err;
}
/*
S-Expressions.
*/
/* This function constructs a new S-Expression for the key identified
by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to
be stored at R_SEXP. Returns an error code. */
static gpg_error_t
sexp_key_construct (gcry_sexp_t *r_sexp,
ssh_key_type_spec_t key_spec, int secret,
const char *curve_name, gcry_mpi_t *mpis,
const char *comment)
{
gpg_error_t err;
gcry_sexp_t sexp_new = NULL;
void *formatbuf = NULL;
void **arg_list = NULL;
estream_t format = NULL;
char *algo_name = NULL;
/* We can't encode an empty string in an S-expression, thus to keep
* the code simple we use "(none)" instead. */
if (!comment || !*comment)
comment = "(none)";
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
{
/* It is much easier and more readable to use a separate code
path for EdDSA. */
if (!curve_name)
err = gpg_error (GPG_ERR_INV_CURVE);
else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
else if (secret
&& (!mpis[1]
|| !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE)))
err = gpg_error (GPG_ERR_BAD_SECKEY);
else if (secret)
err = gcry_sexp_build (&sexp_new, NULL,
"(private-key(ecc(curve %s)"
"(flags eddsa)(q %m)(d %m))"
"(comment%s))",
curve_name,
mpis[0], mpis[1],
comment);
else
err = gcry_sexp_build (&sexp_new, NULL,
"(public-key(ecc(curve %s)"
"(flags eddsa)(q %m))"
"(comment%s))",
curve_name,
mpis[0],
comment);
}
else
{
const char *key_identifier[] = { "public-key", "private-key" };
int arg_idx;
const char *elems;
size_t elems_n;
unsigned int i, j;
if (secret)
elems = key_spec.elems_sexp_order;
else
elems = key_spec.elems_key_public;
elems_n = strlen (elems);
format = es_fopenmem (0, "a+b");
if (!format)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Key identifier, algorithm identifier, mpis, comment, and a NULL
as a safeguard. */
arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1));
if (!arg_list)
{
err = gpg_error_from_syserror ();
goto out;
}
arg_idx = 0;
es_fputs ("(%s(%s", format);
arg_list[arg_idx++] = &key_identifier[secret];
algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
if (!algo_name)
{
err = gpg_error_from_syserror ();
goto out;
}
strlwr (algo_name);
arg_list[arg_idx++] = &algo_name;
if (curve_name)
{
es_fputs ("(curve%s)", format);
arg_list[arg_idx++] = &curve_name;
}
for (i = 0; i < elems_n; i++)
{
es_fprintf (format, "(%c%%m)", elems[i]);
if (secret)
{
for (j = 0; j < elems_n; j++)
if (key_spec.elems_key_secret[j] == elems[i])
break;
}
else
j = i;
arg_list[arg_idx++] = &mpis[j];
}
es_fputs (")(comment%s))", format);
arg_list[arg_idx++] = &comment;
arg_list[arg_idx] = NULL;
es_putc (0, format);
if (es_ferror (format))
{
err = gpg_error_from_syserror ();
goto out;
}
if (es_fclose_snatch (format, &formatbuf, NULL))
{
err = gpg_error_from_syserror ();
goto out;
}
format = NULL;
err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list);
}
if (!err)
*r_sexp = sexp_new;
out:
es_fclose (format);
xfree (arg_list);
es_free (formatbuf);
xfree (algo_name);
return err;
}
/* This function extracts the key from the s-expression SEXP according
to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If
WITH_SECRET is true, the secret key parts are also extracted if
possible. Returns 0 on success or an error code. Note that data
stored at R_BLOB must be freed using es_free! */
static gpg_error_t
ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
ssh_key_type_spec_t key_spec,
void **r_blob, size_t *r_blob_size)
{
gpg_error_t err = 0;
gcry_sexp_t value_list = NULL;
gcry_sexp_t value_pair = NULL;
estream_t stream = NULL;
void *blob = NULL;
size_t blob_size;
const char *elems, *p_elems;
const char *data;
size_t datalen;
*r_blob = NULL;
*r_blob_size = 0;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Get the type of the key expression. */
data = gcry_sexp_nth_data (sexp, 0, &datalen);
if (!data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if ((datalen == 10 && !strncmp (data, "public-key", 10))
|| (datalen == 21 && !strncmp (data, "protected-private-key", 21))
|| (datalen == 20 && !strncmp (data, "shadowed-private-key", 20)))
elems = key_spec.elems_key_public;
else if (datalen == 11 && !strncmp (data, "private-key", 11))
elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public;
else
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
/* Get key value list. */
value_list = gcry_sexp_cadr (sexp);
if (!value_list)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
/* Write the ssh algorithm identifier. */
if ((key_spec.flags & SPEC_FLAG_IS_ECDSA))
{
/* Map the curve name to the ssh name. */
const char *name, *sshname, *canon_name;
name = gcry_pk_get_curve (sexp, 0, NULL);
if (!name)
{
err = gpg_error (GPG_ERR_INV_CURVE);
goto out;
}
sshname = ssh_identifier_from_curve_name (name, &canon_name);
if (!sshname)
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto out;
}
err = stream_write_cstring (stream, sshname);
if (err)
goto out;
err = stream_write_cstring (stream, canon_name);
if (err)
goto out;
}
else
{
/* Note: This is also used for EdDSA. */
err = stream_write_cstring (stream, key_spec.ssh_identifier);
if (err)
goto out;
}
/* Write the parameters. */
for (p_elems = elems; *p_elems; p_elems++)
{
gcry_sexp_release (value_pair);
value_pair = gcry_sexp_find_token (value_list, p_elems, 1);
if (!value_pair)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
{
data = gcry_sexp_nth_data (value_pair, 1, &datalen);
if (!data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if (*p_elems == 'q' && (datalen & 1) && *data == 0x40)
{ /* Remove the prefix 0x40. */
data++;
datalen--;
}
err = stream_write_string (stream, data, datalen);
if (err)
goto out;
}
else
{
gcry_mpi_t mpi;
/* Note that we need to use STD format; i.e. prepend a 0x00
to indicate a positive number if the high bit is set. */
mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
if (!mpi)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
err = stream_write_mpi (stream, mpi);
gcry_mpi_release (mpi);
if (err)
goto out;
}
}
if (es_fclose_snatch (stream, &blob, &blob_size))
{
err = gpg_error_from_syserror ();
goto out;
}
stream = NULL;
*r_blob = blob;
blob = NULL;
*r_blob_size = blob_size;
out:
gcry_sexp_release (value_list);
gcry_sexp_release (value_pair);
es_fclose (stream);
es_free (blob);
return err;
}
/*
Key I/O.
*/
/* Search for a key specification entry. If SSH_NAME is not NULL,
search for an entry whose "ssh_name" is equal to SSH_NAME;
otherwise, search for an entry whose algorithm is equal to ALGO.
Store found entry in SPEC on success, return error otherwise. */
static gpg_error_t
ssh_key_type_lookup (const char *ssh_name, int algo,
ssh_key_type_spec_t *spec)
{
gpg_error_t err;
unsigned int i;
for (i = 0; i < DIM (ssh_key_types); i++)
if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
|| algo == ssh_key_types[i].algo)
break;
if (i == DIM (ssh_key_types))
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
*spec = ssh_key_types[i];
err = 0;
}
return err;
}
/* Receive a key from STREAM, according to the key specification given
as KEY_SPEC. Depending on SECRET, receive a secret or a public
key. If READ_COMMENT is true, receive a comment string as well.
Constructs a new S-Expression from received data and stores it in
KEY_NEW. Returns zero on success or an error code. */
static gpg_error_t
ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
int read_comment, ssh_key_type_spec_t *key_spec)
{
gpg_error_t err;
char *key_type = NULL;
char *comment = NULL;
estream_t cert = NULL;
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
gcry_mpi_t *mpi_list = NULL;
const char *elems;
const char *curve_name = NULL;
err = stream_read_cstring (stream, &key_type);
if (err)
goto out;
err = ssh_key_type_lookup (key_type, 0, &spec);
if (err)
goto out;
if ((spec.flags & SPEC_FLAG_WITH_CERT))
{
/* This is an OpenSSH certificate+private key. The certificate
is an SSH string and which we store in an estream object. */
unsigned char *buffer;
u32 buflen;
char *cert_key_type;
err = stream_read_string (stream, 0, &buffer, &buflen);
if (err)
goto out;
cert = es_fopenmem_init (0, "rb", buffer, buflen);
xfree (buffer);
if (!cert)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Check that the key type matches. */
err = stream_read_cstring (cert, &cert_key_type);
if (err)
goto out;
if (strcmp (cert_key_type, key_type) )
{
xfree (cert_key_type);
log_error ("key types in received ssh certificate do not match\n");
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto out;
}
xfree (cert_key_type);
/* Skip the nonce. */
err = stream_read_string (cert, 0, NULL, NULL);
if (err)
goto out;
}
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
/* The format of an EdDSA key is:
* string key_type ("ssh-ed25519")
* string public_key
* string private_key
*
* Note that the private key is the concatenation of the private
* key with the public key. Thus there's are 64 bytes; however
* we only want the real 32 byte private key - Libgcrypt expects
* this.
*/
/* For now, it's only Ed25519. In future, Ed448 will come. */
curve_name = "Ed25519";
mpi_list = xtrycalloc (3, sizeof *mpi_list);
if (!mpi_list)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
if (err)
goto out;
if (secret)
{
u32 len = 0;
unsigned char *buffer;
/* Read string length. */
err = stream_read_uint32 (stream, &len);
if (err)
goto out;
if (len != 32 && len != 64)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto out;
}
buffer = xtrymalloc_secure (32);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_data (stream, buffer, 32);
if (err)
{
xfree (buffer);
goto out;
}
mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32);
buffer = NULL;
if (len == 64)
{
err = stream_read_skip (stream, 32);
if (err)
goto out;
}
}
}
else if ((spec.flags & SPEC_FLAG_IS_ECDSA))
{
/* The format of an ECDSA key is:
* string key_type ("ecdsa-sha2-nistp256" |
* "ecdsa-sha2-nistp384" |
* "ecdsa-sha2-nistp521" )
* string ecdsa_curve_name
* string ecdsa_public_key
* mpint ecdsa_private
*
* Note that we use the mpint reader instead of the string
* reader for ecsa_public_key. For the certificate variante
* ecdsa_curve_name+ecdsa_public_key are replaced by the
* certificate.
*/
unsigned char *buffer;
err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
if (err)
goto out;
/* Get the canonical name. Should be the same as the read
* string but we use this mapping to validate that name. */
if (!ssh_identifier_from_curve_name (buffer, &curve_name))
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
xfree (buffer);
goto out;
}
xfree (buffer);
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
if (err)
goto out;
}
else
{
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
if (err)
goto out;
}
if (read_comment)
{
err = stream_read_cstring (stream, &comment);
if (err)
goto out;
}
if (secret)
elems = spec.elems_key_secret;
else
elems = spec.elems_key_public;
if (spec.key_modifier)
{
err = (*spec.key_modifier) (elems, mpi_list);
if (err)
goto out;
}
err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
comment? comment:"");
if (!err)
{
if (key_spec)
*key_spec = spec;
*key_new = key;
}
out:
es_fclose (cert);
mpint_list_free (mpi_list);
xfree (key_type);
xfree (comment);
return err;
}
/* Write the public key from KEY to STREAM in SSH key format. If
OVERRIDE_COMMENT is not NULL, it will be used instead of the
comment stored in the key. */
static gpg_error_t
ssh_send_key_public (estream_t stream, gcry_sexp_t key,
const char *override_comment)
{
ssh_key_type_spec_t spec;
int algo;
char *comment = NULL;
void *blob = NULL;
size_t bloblen;
gpg_error_t err = 0;
algo = get_pk_algo_from_key (key);
if (algo == 0)
goto out;
err = ssh_key_type_lookup (NULL, algo, &spec);
if (err)
goto out;
err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
if (err)
goto out;
err = stream_write_string (stream, blob, bloblen);
if (err)
goto out;
if (override_comment)
err = stream_write_cstring (stream, override_comment);
else
{
err = ssh_key_extract_comment (key, &comment);
if (err)
err = stream_write_cstring (stream, "(none)");
else
err = stream_write_cstring (stream, comment);
}
if (err)
goto out;
out:
xfree (comment);
es_free (blob);
return err;
}
/* Read a public key out of BLOB/BLOB_SIZE according to the key
specification given as KEY_SPEC, storing the new key in KEY_PUBLIC.
Returns zero on success or an error code. */
static gpg_error_t
ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
gcry_sexp_t *key_public,
ssh_key_type_spec_t *key_spec)
{
gpg_error_t err;
estream_t blob_stream;
blob_stream = es_fopenmem (0, "r+b");
if (!blob_stream)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_data (blob_stream, blob, blob_size);
if (err)
goto out;
err = es_fseek (blob_stream, 0, SEEK_SET);
if (err)
goto out;
err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec);
out:
es_fclose (blob_stream);
return err;
}
/* This function calculates the key grip for the key contained in the
S-Expression KEY and writes it to BUFFER, which must be large
enough to hold it. Returns usual error code. */
static gpg_error_t
ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
{
if (!gcry_pk_get_keygrip (key, buffer))
{
gpg_error_t err = gcry_pk_testkey (key);
return err? err : gpg_error (GPG_ERR_INTERNAL);
}
return 0;
}
/* Check whether a key of KEYGRIP on smartcard is available and
whether it has a usable key. Store a copy of that key at R_PK and
return 0. If no key is available store NULL at R_PK and return an
error code. If CARDSN is not NULL, a string with the serial number
of the card will be a malloced and stored there. */
static gpg_error_t
card_key_available (ctrl_t ctrl, const struct card_key_info_s *keyinfo,
gcry_sexp_t *r_pk, char **cardsn)
{
gpg_error_t err;
unsigned char *pkbuf;
size_t pkbuflen;
gcry_sexp_t s_pk;
unsigned char grip[20];
*r_pk = NULL;
if (cardsn)
*cardsn = NULL;
/* Read the public key. */
err = agent_card_readkey (ctrl, keyinfo->keygrip, &pkbuf, NULL);
if (err)
{
if (opt.verbose)
log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
return err;
}
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen);
if (err)
{
log_error ("failed to build S-Exp from received card key: %s\n",
gpg_strerror (err));
xfree (pkbuf);
return err;
}
hex2bin (keyinfo->keygrip, grip, sizeof (grip));
if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip) )
{
char *dispserialno;
/* (Shadow)-key is not available in our key storage. */
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno,
keyinfo->keygrip);
err = agent_write_shadow_key (ctrl, grip, keyinfo->serialno,
keyinfo->idstr, pkbuf, 0, dispserialno);
xfree (dispserialno);
if (err)
{
xfree (pkbuf);
gcry_sexp_release (s_pk);
return err;
}
}
if (cardsn)
{
char *dispsn;
char *p;
/* If the card handler is able to return a short serialnumber,
use that one, else use the complete serialno. */
if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn,
keyinfo->keygrip))
{
*cardsn = xtryasprintf ("cardno:%s", dispsn);
xfree (dispsn);
}
else
*cardsn = xtryasprintf ("cardno:%s", keyinfo->serialno);
if (!*cardsn)
{
err = gpg_error_from_syserror ();
xfree (pkbuf);
gcry_sexp_release (s_pk);
return err;
}
/* Let's avoid blanks in the comment. */
for (p=*cardsn; *p; p++)
if (spacep (p))
*p = '_';
}
xfree (pkbuf);
*r_pk = s_pk;
return 0;
}
static struct card_key_info_s *
get_ssh_keyinfo_on_cards (ctrl_t ctrl)
{
struct card_key_info_s *keyinfo_on_cards = NULL;
gpg_error_t err;
char *serialno;
if (opt.disable_daemon[DAEMON_SCD])
return NULL;
/* Scan for new device(s). */
err = agent_card_serialno (ctrl, &serialno, NULL);
if (err)
{
if (opt.verbose)
log_info (_("error getting list of cards: %s\n"),
gpg_strerror (err));
return NULL;
}
xfree (serialno);
err = agent_card_keyinfo (ctrl, NULL, GCRY_PK_USAGE_AUTH, &keyinfo_on_cards);
if (err)
return NULL;
return keyinfo_on_cards;
}
/* Append (KEY,CARDSN,LNR,ORDER) to ARRAY. The array must initially
* be passed as a cleared struct. ARRAY takes ownership of KEY and
* CARDSN. */
static gpg_error_t
add_to_key_array (struct key_collection_s *array, gcry_sexp_t key,
char *cardsn, int order)
{
if (array->nitems == array->allocated)
{
struct key_collection_item_s *newitems;
size_t newsize = ((array->allocated + 63)/64 + 1) * 64;
newitems = xtryreallocarray (array->items, array->allocated, newsize+1,
sizeof *newitems);
if (!newitems)
return gpg_error_from_syserror ();
array->allocated = newsize;
array->items = newitems;
}
array->items[array->nitems].key = key;
array->items[array->nitems].cardsn = cardsn;
array->items[array->nitems].order = order;
array->nitems++;
return 0;
}
/* Release the content of ARRAY. */
static void
free_key_array (struct key_collection_s *array)
{
if (array && array->items)
{
unsigned int n;
for (n = 0; n < array->nitems; n++)
{
gcry_sexp_release (array->items[n].key);
xfree (array->items[n].cardsn);
}
xfree (array->items);
}
}
/* Helper for the qsort in ssh_send_available_keys. */
static int
compare_key_collection_items (const void *arg_a, const void *arg_b)
{
const struct key_collection_item_s *a
= (const struct key_collection_item_s *)arg_a;
const struct key_collection_item_s *b
= (const struct key_collection_item_s *)arg_b;
int res;
res = a->order - b->order;
/* If we are comparing two cards we sort by serial number. */
if (!res && a->order == 1)
res = strcmp (a->cardsn?a->cardsn:"", b->cardsn?b->cardsn:"");
return res;
}
static gpg_error_t
ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter)
{
gpg_error_t err;
char *dirname;
gnupg_dir_t dir = NULL;
gnupg_dirent_t dir_entry;
char hexgrip[41];
ssh_control_file_t cf = NULL;
struct card_key_info_s *keyinfo_on_cards, *l;
char *cardsn;
gcry_sexp_t key_public = NULL;
int count, skipped;
struct key_collection_s keyarray = { NULL };
err = open_control_file (&cf, 0);
if (err)
return err;
/* First, get information keys available on cards on-line. */
keyinfo_on_cards = get_ssh_keyinfo_on_cards (ctrl);
/* Look at all the registered and non-disabled keys, in sshcontrol. */
/* And, look at all keys with "Use-for-ssh:" flag. */
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
ssh_close_control_file (cf);
agent_card_free_keyinfo (keyinfo_on_cards);
return err;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
xfree (dirname);
ssh_close_control_file (cf);
agent_card_free_keyinfo (keyinfo_on_cards);
return err;
}
xfree (dirname);
while ( (dir_entry = gnupg_readdir (dir)) )
{
struct card_key_info_s *l_prev = NULL;
int disabled, is_ssh, lnr, order;
unsigned char grip[20];
cardsn = NULL;
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
/* Check if it's a key on card. */
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
break;
else
l_prev = l;
/* Check if it's listed in "ssh_control" file. */
disabled = is_ssh = 0;
err = search_control_file (cf, hexgrip, &disabled, NULL, NULL, &lnr);
if (!err)
{
if (!disabled)
{
is_ssh = 1;
}
}
else if (gpg_err_code (err) != GPG_ERR_EOF)
break;
/* Clamp LNR value and set the ordinal.
* Current use of ordinals:
* 1..999 - low value Use-for-ssh.
* 1000..99999 - inserted cards (right now only 1000)
* 100000..199999 - listed in sshcontrol
* 200000..299999 - order taken from Use-for-ssh
*/
if (is_ssh)
{
if (lnr < 1)
lnr = 0;
else if (lnr > 99999)
lnr = 99999;
order = lnr + 100000;
}
if (l)
{
err = card_key_available (ctrl, l, &key_public, &cardsn);
/* Remove the entry from the list of KEYINFO_ON_CARD */
if (l_prev)
l_prev->next = l->next;
else
keyinfo_on_cards = l->next;
xfree (l->serialno);
xfree (l->idstr);
xfree (l->usage);
xfree (l);
l = NULL;
/* If we want to allow that the user to change the sorting
* order of card keys (which are sorted by their s/n), we
* would need to get the use-for-ssh: value from the stub
* file and set an appropriate ordinal. */
order = 1000;
}
else if (is_ssh)
err = agent_public_key_from_file (ctrl, grip, &key_public);
else /* Examine the file if it's suitable for SSH. */
{
err = agent_ssh_key_from_file (ctrl, grip, &key_public, &order);
if (err)
order = 0;
else if (order < 0)
{
order = -order;
if (order > 999)
order = 999;
}
else if (order > 99999)
order = 299999;
else
order += 200000;
}
if (err)
{
/* Clear ERR, skipping the key in question. */
err = 0;
continue;
}
err = add_to_key_array (&keyarray, key_public, cardsn, order);
if (err)
{
gnupg_closedir (dir);
ssh_close_control_file (cf);
gcry_sexp_release (key_public);
xfree (cardsn);
goto leave;
}
}
gnupg_closedir (dir);
ssh_close_control_file (cf);
/* Lastly, handle remaining keys which don't have the stub files. */
for (l = keyinfo_on_cards, count=0; l; l = l->next, count++)
{
cardsn = NULL;
if (card_key_available (ctrl, l, &key_public, &cardsn))
continue;
err = add_to_key_array (&keyarray, key_public, cardsn, 300000+count);
if (err)
{
gcry_sexp_release (key_public);
xfree (cardsn);
goto leave;
}
}
/* Sort the array. */
qsort (keyarray.items, keyarray.nitems, sizeof *keyarray.items,
compare_key_collection_items);
if (opt.debug)
for (count=0; count < keyarray.nitems; count++)
log_debug ("sshkeys[%d]: order=%d, pubkey=%p sn=%s\n",
count, keyarray.items[count].order,
keyarray.items[count].key, keyarray.items[count].cardsn);
/* And print the keys. */
skipped = 0;
for (count=0; count < keyarray.nitems; count++)
{
err = ssh_send_key_public (key_blobs, keyarray.items[count].key,
keyarray.items[count].cardsn);
if (err)
{
if (opt.debug)
gcry_log_debugsxp ("pubkey", keyarray.items[count].key);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE
|| gpg_err_code (err) == GPG_ERR_INV_CURVE)
{
/* For example a Brainpool curve or a curve we don't
* support at all but a smartcard lists that curve.
* We ignore them. */
skipped++;
err = 0;
}
else
goto leave;
}
}
*r_key_counter = count - skipped;
leave:
agent_card_free_keyinfo (keyinfo_on_cards);
free_key_array (&keyarray);
return err;
}
/*
Request handler. Each handler is provided with a CTRL context, a
REQUEST object and a RESPONSE object. The actual request is to be
read from REQUEST, the response needs to be written to RESPONSE.
*/
/* Handler for the "request_identities" command. */
static gpg_error_t
ssh_handler_request_identities (ctrl_t ctrl,
estream_t request, estream_t response)
{
u32 key_counter;
estream_t key_blobs;
gpg_error_t err;
int ret;
gpg_error_t ret_err;
(void)request;
/* Prepare buffer stream. */
key_counter = 0;
key_blobs = es_fopenmem (0, "r+b");
if (! key_blobs)
{
err = gpg_error_from_syserror ();
goto out;
}
err = ssh_send_available_keys (ctrl, key_blobs, &key_counter);
if (!err)
{
ret = es_fseek (key_blobs, 0, SEEK_SET);
if (ret)
err = gpg_error_from_syserror ();
}
out:
/* Send response. */
if (!err)
{
ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
if (!ret_err)
ret_err = stream_write_uint32 (response, key_counter);
if (!ret_err)
ret_err = stream_copy (response, key_blobs);
}
else
{
log_error ("ssh request identities failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
}
es_fclose (key_blobs);
return ret_err;
}
/* This function hashes the data contained in DATA of size DATA_N
according to the message digest algorithm specified by MD_ALGORITHM
and writes the message digest to HASH, which needs to large enough
for the digest. */
static gpg_error_t
data_hash (unsigned char *data, size_t data_n,
int md_algorithm, unsigned char *hash)
{
gcry_md_hash_buffer (md_algorithm, hash, data, data_n);
return 0;
}
/* This function signs the data described by CTRL. If HASH is not
NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to
allow the use of signature algorithms that implement the hashing
internally (e.g. Ed25519). On success the created signature is
stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller
must use es_free to release this memory. */
static gpg_error_t
data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
const void *hash, size_t hashlen,
unsigned char **r_sig, size_t *r_siglen)
{
gpg_error_t err;
gcry_sexp_t signature_sexp = NULL;
estream_t stream = NULL;
void *blob = NULL;
size_t bloblen;
char hexgrip[40+1];
*r_sig = NULL;
*r_siglen = 0;
/* Quick check to see whether we have a valid keygrip and convert it
to hex. */
if (!ctrl->have_keygrip)
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto out;
}
bin2hex (ctrl->keygrip, 20, hexgrip);
/* Ask for confirmation if needed. */
if (confirm_flag_from_sshcontrol (hexgrip))
{
gcry_sexp_t key;
char *fpr, *prompt;
char *comment = NULL;
err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key, NULL);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr);
if (!err)
{
gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
if (tmpsxp)
comment = gcry_sexp_nth_string (tmpsxp, 1);
gcry_sexp_release (tmpsxp);
}
gcry_sexp_release (key);
if (err)
goto out;
prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
" %s%%0A"
" (%s)%%0A"
"Do you want to allow this?"),
fpr, comment? comment:"");
xfree (fpr);
gcry_free (comment);
err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
xfree (prompt);
if (err)
goto out;
}
/* Create signature. */
ctrl->use_auth_call = 1;
err = agent_pksign_do (ctrl, NULL,
L_("Please enter the passphrase "
"for the ssh key%%0A %F%%0A (%c)"),
&signature_sexp,
CACHE_MODE_SSH, ttl_from_sshcontrol,
hash, hashlen);
ctrl->use_auth_call = 0;
if (err)
goto out;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_cstring (stream, spec->ssh_identifier);
if (err)
goto out;
err = spec->signature_encoder (spec, stream, signature_sexp);
if (err)
goto out;
err = es_fclose_snatch (stream, &blob, &bloblen);
if (err)
goto out;
stream = NULL;
*r_sig = blob; blob = NULL;
*r_siglen = bloblen;
out:
es_free (blob);
es_fclose (stream);
gcry_sexp_release (signature_sexp);
return err;
}
/* Handler for the "sign_request" command. */
static gpg_error_t
ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
{
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
unsigned char hash[MAX_DIGEST_LEN];
unsigned int hash_n;
unsigned char key_grip[20];
unsigned char *key_blob = NULL;
u32 key_blob_size;
unsigned char *data = NULL;
unsigned char *sig = NULL;
size_t sig_n;
u32 data_size;
gpg_error_t err;
gpg_error_t ret_err;
int hash_algo;
/* Receive key. */
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
if (err)
goto out;
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec);
if (err)
goto out;
/* Receive data to sign. */
err = stream_read_string (request, 0, &data, &data_size);
if (err)
goto out;
/* Flag processing. */
{
u32 flags = 0;
err = stream_read_uint32 (request, &flags);
if (err)
goto out;
if (spec.algo == GCRY_PK_RSA)
{
if ((flags & SSH_AGENT_RSA_SHA2_512))
{
flags &= ~SSH_AGENT_RSA_SHA2_512;
spec.ssh_identifier = "rsa-sha2-512";
spec.hash_algo = GCRY_MD_SHA512;
spec.flags |= SPEC_FLAG_WITH_FIXEDLENGTH;
}
if ((flags & SSH_AGENT_RSA_SHA2_256))
{
/* Note: We prefer SHA256 over SHA512. */
flags &= ~SSH_AGENT_RSA_SHA2_256;
spec.ssh_identifier = "rsa-sha2-256";
spec.hash_algo = GCRY_MD_SHA256;
spec.flags |= SPEC_FLAG_WITH_FIXEDLENGTH;
}
if ((spec.flags &SPEC_FLAG_WITH_FIXEDLENGTH))
{
unsigned int n;
size_t modulus_n;
n = gcry_pk_get_nbits (key);
if (!n)
{
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto out;
}
modulus_n = (n+7)/8;
spec.keysize = modulus_n;
}
}
/* Some flag is present that we do not know about. Note that
* processed or known flags have been cleared at this point. */
if (flags)
{
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
goto out;
}
}
hash_algo = spec.hash_algo;
if (!hash_algo)
hash_algo = GCRY_MD_SHA1; /* Use the default. */
ctrl->digest.algo = hash_algo;
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
ctrl->digest.is_pss = 0;
if ((spec.flags & SPEC_FLAG_USE_PKCS1V2))
ctrl->digest.raw_value = 0;
else
ctrl->digest.raw_value = 1;
/* Calculate key grip. */
err = ssh_key_grip (key, key_grip);
if (err)
goto out;
ctrl->have_keygrip = 1;
memcpy (ctrl->keygrip, key_grip, 20);
/* Hash data unless we use EdDSA. */
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
ctrl->digest.valuelen = 0;
}
else
{
hash_n = gcry_md_get_algo_dlen (hash_algo);
if (!hash_n)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
err = data_hash (data, data_size, hash_algo, hash);
if (err)
goto out;
memcpy (ctrl->digest.value, hash, hash_n);
ctrl->digest.valuelen = hash_n;
}
/* Sign data. */
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n);
else
err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n);
out:
/* Done. */
if (!err)
{
ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE);
if (ret_err)
goto leave;
ret_err = stream_write_string (response, sig, sig_n);
if (ret_err)
goto leave;
}
else
{
log_error ("ssh sign request failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
if (ret_err)
goto leave;
}
leave:
gcry_sexp_release (key);
xfree (key_blob);
xfree (data);
es_free (sig);
return ret_err;
}
/* This function extracts the comment contained in the key
s-expression KEY and stores a copy in COMMENT. Returns usual error
code. */
static gpg_error_t
ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
{
gcry_sexp_t comment_list;
*r_comment = NULL;
comment_list = gcry_sexp_find_token (key, "comment", 0);
if (!comment_list)
return gpg_error (GPG_ERR_INV_SEXP);
*r_comment = gcry_sexp_nth_string (comment_list, 1);
gcry_sexp_release (comment_list);
if (!*r_comment)
return gpg_error (GPG_ERR_INV_SEXP);
return 0;
}
/* This function converts the key contained in the S-Expression KEY
into a buffer, which is protected by the passphrase PASSPHRASE.
If PASSPHRASE is the empty passphrase, the key is not protected.
Returns usual error code. */
static gpg_error_t
ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
unsigned char **buffer, size_t *buffer_n)
{
unsigned char *buffer_new;
unsigned int buffer_new_n;
gpg_error_t err;
buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0);
buffer_new = xtrymalloc_secure (buffer_new_n);
if (! buffer_new)
{
err = gpg_error_from_syserror ();
goto out;
}
buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON,
buffer_new, buffer_new_n);
if (*passphrase)
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0);
else
{
/* The key derivation function does not support zero length
* strings. Store key unprotected if the user wishes so. */
*buffer = buffer_new;
*buffer_n = buffer_new_n;
buffer_new = NULL;
err = 0;
}
out:
xfree (buffer_new);
return err;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_compare_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Store the ssh KEY into our local key storage and protect it after
asking for a passphrase. Cache that passphrase. TTL is the
maximum caching time for that key. If the key already exists in
our key storage, don't do anything. When entering a key also add
an entry to the sshcontrol file. */
static gpg_error_t
ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
gcry_sexp_t key, int ttl, int confirm)
{
gpg_error_t err;
unsigned char key_grip_raw[20];
char key_grip[41];
unsigned char *buffer = NULL;
size_t buffer_n;
char *description = NULL;
const char *description2 = L_("Please re-enter this passphrase");
char *comment = NULL;
char *key_fpr = NULL;
const char *initial_errtext = NULL;
struct pin_entry_info_s *pi = NULL;
struct pin_entry_info_s *pi2 = NULL;
err = ssh_key_grip (key, key_grip_raw);
if (err)
goto out;
bin2hex (key_grip_raw, 20, key_grip);
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr);
if (err)
goto out;
/* Check whether the key is already in our key storage. Don't do
anything then besides (re-)adding it to sshcontrol. */
if ( !agent_key_available (ctrl, key_grip_raw) )
goto key_exists; /* Yes, key is available. */
err = ssh_key_extract_comment (key, &comment);
if (err)
goto out;
if ( asprintf (&description,
L_("Please enter a passphrase to protect"
" the received secret key%%0A"
" %s%%0A"
" %s%%0A"
"within gpg-agent's key storage"),
key_fpr, comment ? comment : "") < 0)
{
err = gpg_error_from_syserror ();
goto out;
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
err = gpg_error_from_syserror ();
goto out;
}
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
err = gpg_error_from_syserror ();
goto out;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 1;
pi->with_repeat = 1;
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 1;
pi2->check_cb = reenter_compare_cb;
pi2->check_cb_arg = pi->pin;
next_try:
err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
initial_errtext = NULL;
if (err)
goto out;
/* Unless the passphrase is empty or the pinentry told us that
it already did the repetition check, ask to confirm it. */
if (*pi->pin && !pi->repeat_okay)
{
err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered one did not match and the user did not
hit cancel. */
initial_errtext = L_("does not match - try again");
goto next_try;
}
}
err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n);
if (err)
goto out;
/* Store this key to our key storage. We do not store a creation
* timestamp because we simply do not know. */
err = agent_write_private_key (ctrl, key_grip_raw, buffer, buffer_n, 0,
- NULL, NULL, NULL, 0);
+ NULL, NULL, NULL, 0, NULL);
if (err)
goto out;
/* Cache this passphrase. */
err = agent_put_cache (ctrl, key_grip, CACHE_MODE_SSH, pi->pin, ttl);
if (err)
goto out;
key_exists:
/* And add an entry to the sshcontrol file. */
err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm);
out:
if (pi2 && pi2->max_length)
wipememory (pi2->pin, pi2->max_length);
xfree (pi2);
if (pi && pi->max_length)
wipememory (pi->pin, pi->max_length);
xfree (pi);
xfree (buffer);
xfree (comment);
xfree (key_fpr);
xfree (description);
return err;
}
/* This function removes the key contained in the S-Expression KEY
from the local key storage, in case it exists there. Returns usual
error code. FIXME: this function is a stub. */
static gpg_error_t
ssh_identity_drop (gcry_sexp_t key)
{
unsigned char key_grip[21] = { 0 };
gpg_error_t err;
err = ssh_key_grip (key, key_grip);
if (err)
goto out;
key_grip[sizeof (key_grip) - 1] = 0;
/* FIXME: What to do here - forgetting the passphrase or deleting
the key from key cache? */
out:
return err;
}
/* Handler for the "add_identity" command. */
static gpg_error_t
ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
ssh_key_type_spec_t spec;
gpg_error_t err;
gcry_sexp_t key;
unsigned char b;
int confirm;
int ttl;
confirm = 0;
key = NULL;
ttl = 0;
/* FIXME? */
err = ssh_receive_key (request, &key, 1, 1, &spec);
if (err)
goto out;
while (1)
{
err = stream_read_byte (request, &b);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
break;
}
switch (b)
{
case SSH_OPT_CONSTRAIN_LIFETIME:
{
u32 n = 0;
err = stream_read_uint32 (request, &n);
if (! err)
ttl = n;
break;
}
case SSH_OPT_CONSTRAIN_CONFIRM:
{
confirm = 1;
break;
}
case SSH_OPT_CONSTRAIN_MAXSIGN:
case SSH_OPT_CONSTRAIN_EXTENSION:
/* Not yet implemented. */
break;
default:
/* FIXME: log/bad? */
break;
}
}
if (err)
goto out;
err = ssh_identity_register (ctrl, &spec, key, ttl, confirm);
out:
gcry_sexp_release (key);
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "remove_identity" command. */
static gpg_error_t
ssh_handler_remove_identity (ctrl_t ctrl,
estream_t request, estream_t response)
{
unsigned char *key_blob;
u32 key_blob_size;
gcry_sexp_t key;
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
/* Receive key. */
key_blob = NULL;
key = NULL;
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
if (err)
goto out;
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL);
if (err)
goto out;
err = ssh_identity_drop (key);
out:
xfree (key_blob);
gcry_sexp_release (key);
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* FIXME: stub function. Actually useful? */
static gpg_error_t
ssh_identities_remove_all (void)
{
gpg_error_t err;
err = 0;
/* FIXME: shall we remove _all_ cache entries or only those
registered through the ssh-agent protocol? */
return err;
}
/* Handler for the "remove_all_identities" command. */
static gpg_error_t
ssh_handler_remove_all_identities (ctrl_t ctrl,
estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_identities_remove_all ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Lock agent? FIXME: stub function. */
static gpg_error_t
ssh_lock (void)
{
gpg_error_t err;
/* FIXME */
log_error ("ssh-agent's lock command is not implemented\n");
err = 0;
return err;
}
/* Unock agent? FIXME: stub function. */
static gpg_error_t
ssh_unlock (void)
{
gpg_error_t err;
log_error ("ssh-agent's unlock command is not implemented\n");
err = 0;
return err;
}
/* Handler for the "lock" command. */
static gpg_error_t
ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_lock ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "unlock" command. */
static gpg_error_t
ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_unlock ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "extension" command. */
static gpg_error_t
ssh_handler_extension (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
char *exttype = NULL;
char *name = NULL;
char *value = NULL;
err = stream_read_cstring (request, &exttype);
if (err)
goto leave;
if (opt.verbose)
log_info ("ssh-agent extension '%s' received\n", exttype);
if (!strcmp (exttype, "ssh-env@gnupg.org"))
{
for (;;)
{
xfree (name); name = NULL;
err = stream_read_cstring (request, &name);
if (gpg_err_code (err) == GPG_ERR_EOF)
break; /* ready. */
if (err)
{
if (opt.verbose)
log_error ("error reading ssh-agent env name\n");
goto leave;
}
xfree (value); value = NULL;
err = stream_read_cstring (request, &value);
if (err)
{
if (opt.verbose)
log_error ("error reading ssh-agent env value\n");
goto leave;
}
if (opt.debug)
log_debug ("ssh-agent env '%s'='%s'\n", name, value);
err = session_env_setenv (ctrl->session_env, name,
*value? value : NULL);
if (err)
{
log_error ("error setting ssh-agent env value: %s\n",
gpg_strerror (err));
goto leave;
}
}
err = 0;
}
else if (!strcmp (exttype, "ssh-envnames@gnupg.org"))
{
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
if (!ret_err)
ret_err = stream_write_cstring
(response, session_env_list_stdenvnames (NULL, NULL));
goto finalleave;
}
else if (!strcmp (exttype, "session-bind@openssh.org"))
{
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
log_info ("ssh-agent extension '%s' ignored - returning success anyway\n",
exttype);
goto finalleave;
}
else
{
if (opt.verbose)
log_info ("ssh-agent extension '%s' not supported\n", exttype);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
leave:
if (!err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
finalleave:
xfree (exttype);
xfree (name);
xfree (value);
return ret_err;
}
/* Return the request specification for the request identified by TYPE
or NULL in case the requested request specification could not be
found. */
static const ssh_request_spec_t *
request_spec_lookup (int type)
{
const ssh_request_spec_t *spec;
unsigned int i;
for (i = 0; i < DIM (request_specs); i++)
if (request_specs[i].type == type)
break;
if (i == DIM (request_specs))
{
if (opt.verbose)
log_info ("ssh request %u is not supported\n", type);
spec = NULL;
}
else
spec = request_specs + i;
return spec;
}
/* Process a single request. The request is read from and the
response is written to STREAM_SOCK. Uses CTRL as context. Returns
zero in case of success, non zero in case of failure. */
static int
ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
{
const ssh_request_spec_t *spec;
estream_t response = NULL;
estream_t request = NULL;
unsigned char request_type;
gpg_error_t err;
int send_err = 0;
int ret;
unsigned char *request_data = NULL;
u32 request_data_size;
u32 response_size;
/* Create memory streams for request/response data. The entire
request will be stored in secure memory, since it might contain
secret key material. The response does not have to be stored in
secure memory, since we never give out secret keys.
Note: we only have little secure memory, but there is NO
possibility of DoS here; only trusted clients are allowed to
connect to the agent. What could happen is that the agent
returns out-of-secure-memory errors on requests in case the
agent's owner floods his own agent with many large messages.
-moritz */
/* Retrieve request. */
err = stream_read_string (stream_sock, 1, &request_data, &request_data_size);
if (err)
goto out;
if (opt.verbose > 1)
log_info ("received ssh request of length %u\n",
(unsigned int)request_data_size);
if (! request_data_size)
{
send_err = 1;
goto out;
/* Broken request; FIXME. */
}
request_type = request_data[0];
spec = request_spec_lookup (request_type);
if (! spec)
{
send_err = 1;
goto out;
/* Unknown request; FIXME. */
}
if (spec->secret_input)
request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b");
else
request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b");
if (! request)
{
err = gpg_error_from_syserror ();
goto out;
}
ret = es_setvbuf (request, NULL, _IONBF, 0);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_data (request, request_data + 1, request_data_size - 1);
if (err)
goto out;
es_rewind (request);
response = es_fopenmem (0, "r+b");
if (! response)
{
err = gpg_error_from_syserror ();
goto out;
}
if (opt.verbose)
log_info ("ssh request handler for %s (%u) started\n",
spec->identifier, spec->type);
err = (*spec->handler) (ctrl, request, response);
if (opt.verbose)
{
if (err)
log_info ("ssh request handler for %s (%u) failed: %s\n",
spec->identifier, spec->type, gpg_strerror (err));
else
log_info ("ssh request handler for %s (%u) ready\n",
spec->identifier, spec->type);
}
if (err)
{
send_err = 1;
goto out;
}
response_size = es_ftell (response);
if (opt.verbose > 1)
log_info ("sending ssh response of length %u\n",
(unsigned int)response_size);
err = es_fseek (response, 0, SEEK_SET);
if (err)
{
send_err = 1;
goto out;
}
err = stream_write_uint32 (stream_sock, response_size);
if (err)
{
send_err = 1;
goto out;
}
err = stream_copy (stream_sock, response);
if (err)
goto out;
err = es_fflush (stream_sock);
if (err)
goto out;
out:
if (err && es_feof (stream_sock))
log_error ("error occurred while processing request: %s\n",
gpg_strerror (err));
if (send_err)
{
if (opt.verbose > 1)
log_info ("sending ssh error response\n");
err = stream_write_uint32 (stream_sock, 1);
if (err)
goto leave;
err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE);
if (err)
goto leave;
}
leave:
es_fclose (request);
es_fclose (response);
xfree (request_data);
return !!err;
}
/* Return the peer's pid. */
static void
get_client_info (gnupg_fd_t fd, struct peer_info_s *out)
{
pid_t client_pid = (pid_t)(-1);
int client_uid = -1;
#ifdef SO_PEERCRED
{
#ifdef HAVE_STRUCT_SOCKPEERCRED_PID
struct sockpeercred cr;
#else
struct ucred cr;
#endif
socklen_t cl = sizeof cr;
if (!getsockopt (FD2INT (fd), SOL_SOCKET, SO_PEERCRED, &cr, &cl))
{
#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID)
client_pid = cr.pid;
client_uid = (int)cr.uid;
#elif defined (HAVE_STRUCT_UCRED_CR_PID)
client_pid = cr.cr_pid;
client_uid = (int)cr.cr_uid;
#else
#error "Unknown SO_PEERCRED struct"
#endif
}
}
#elif defined (LOCAL_PEERPID)
{
socklen_t len = sizeof (pid_t);
getsockopt (FD2INT (fd), SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
#if defined (LOCAL_PEERCRED)
{
struct xucred cr;
len = sizeof (struct xucred);
if (!getsockopt (FD2INT (fd), SOL_LOCAL, LOCAL_PEERCRED, &cr, &len))
client_uid = (int)cr.cr_uid;
}
#endif
}
#elif defined (LOCAL_PEEREID)
{
struct unpcbid unp;
socklen_t unpl = sizeof unp;
if (getsockopt (FD2INT (fd), 0, LOCAL_PEEREID, &unp, &unpl) != -1)
{
client_pid = unp.unp_pid;
client_uid = (int)unp.unp_euid;
}
}
#elif defined (HAVE_GETPEERUCRED)
{
ucred_t *ucred = NULL;
if (getpeerucred (FD2INT (fd), &ucred) != -1)
{
client_pid = ucred_getpid (ucred);
client_uid = (int)ucred_geteuid (ucred);
ucred_free (ucred);
}
}
#else
(void)fd;
#endif
out->pid = (client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid);
out->uid = client_uid;
}
/* Start serving client on STREAM. */
void
start_command_handler_ssh_stream (ctrl_t ctrl, estream_t stream)
{
gpg_error_t err;
int ret;
err = agent_copy_startup_env (ctrl);
if (err)
goto out;
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
ret = es_setvbuf (stream, NULL, _IONBF, 0);
if (ret)
{
log_error ("failed to disable buffering on socket stream: %s\n",
strerror (errno));
goto out;
}
/* Main processing loop. */
while ( !ssh_request_process (ctrl, stream) )
{
/* Check whether we have reached EOF before trying to read
another request. */
int c;
c = es_fgetc (stream);
if (c == EOF)
break;
es_ungetc (c, stream);
}
/* Reset the daemon in case it has been used. */
agent_reset_daemon (ctrl);
out:
es_fclose (stream);
}
/* Start serving client on SOCK_CLIENT. */
void
start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
{
estream_t stream_sock;
struct peer_info_s peer_info;
es_syshd_t syshd;
syshd.type = ES_SYSHD_SOCK;
#if defined(HAVE_SOCKET) && defined(HAVE_W32_SYSTEM)
syshd.u.sock = (SOCKET)sock_client;
#else
syshd.u.sock = sock_client;
#endif
get_client_info (sock_client, &peer_info);
ctrl->client_pid = peer_info.pid;
ctrl->client_uid = peer_info.uid;
/* Create stream from socket. */
stream_sock = es_sysopen (&syshd, "r+");
if (!stream_sock)
{
log_error (_("failed to create stream from socket: %s\n"),
strerror (errno));
return;
}
start_command_handler_ssh_stream (ctrl, stream_sock);
}
#ifdef HAVE_W32_SYSTEM
/* Serve one ssh-agent request. This is used for the Putty support.
REQUEST is the mmapped memory which may be accessed up to a
length of MAXREQLEN. Returns 0 on success which also indicates
that a valid SSH response message is now in REQUEST. */
int
serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen)
{
gpg_error_t err;
int send_err = 0;
int valid_response = 0;
const ssh_request_spec_t *spec;
u32 msglen;
estream_t request_stream, response_stream;
if (agent_copy_startup_env (ctrl))
goto leave; /* Error setting up the environment. */
if (maxreqlen < 5)
goto leave; /* Caller error. */
msglen = uint32_construct (request[0], request[1], request[2], request[3]);
if (msglen < 1 || msglen > maxreqlen - 4)
{
log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
goto leave;
}
spec = request_spec_lookup (request[4]);
if (!spec)
{
send_err = 1; /* Unknown request type. */
goto leave;
}
/* Create a stream object with the data part of the request. */
if (spec->secret_input)
request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
else
request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
if (!request_stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
if (es_setvbuf (request_stream, NULL, _IONBF, 0))
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy the request to the stream but omit the request type. */
err = stream_write_data (request_stream, request + 5, msglen - 1);
if (err)
goto leave;
es_rewind (request_stream);
response_stream = es_fopenmem (0, "r+b");
if (!response_stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.verbose)
log_info ("ssh request handler for %s (%u) started\n",
spec->identifier, spec->type);
err = (*spec->handler) (ctrl, request_stream, response_stream);
if (opt.verbose)
{
if (err)
log_info ("ssh request handler for %s (%u) failed: %s\n",
spec->identifier, spec->type, gpg_strerror (err));
else
log_info ("ssh request handler for %s (%u) ready\n",
spec->identifier, spec->type);
}
es_fclose (request_stream);
request_stream = NULL;
if (err)
{
send_err = 1;
goto leave;
}
/* Put the response back into the mmapped buffer. */
{
void *response_data;
size_t response_size;
/* NB: In contrast to the request-stream, the response stream
includes the message type byte. */
if (es_fclose_snatch (response_stream, &response_data, &response_size))
{
log_error ("snatching ssh response failed: %s",
gpg_strerror (gpg_error_from_syserror ()));
send_err = 1; /* Ooops. */
goto leave;
}
if (opt.verbose > 1)
log_info ("sending ssh response of length %u\n",
(unsigned int)response_size);
if (response_size > maxreqlen - 4)
{
log_error ("invalid length of the ssh response: %s",
gpg_strerror (GPG_ERR_INTERNAL));
es_free (response_data);
send_err = 1;
goto leave;
}
request[0] = response_size >> 24;
request[1] = response_size >> 16;
request[2] = response_size >> 8;
request[3] = response_size >> 0;
memcpy (request+4, response_data, response_size);
es_free (response_data);
valid_response = 1;
}
leave:
if (send_err)
{
request[0] = 0;
request[1] = 0;
request[2] = 0;
request[3] = 1;
request[4] = SSH_RESPONSE_FAILURE;
valid_response = 1;
}
/* Reset the daemon in case it has been used. */
agent_reset_daemon (ctrl);
return valid_response? 0 : -1;
}
#endif /*HAVE_W32_SYSTEM*/
diff --git a/agent/command.c b/agent/command.c
index d1ff8e27c..a50cbce5a 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1,4591 +1,4661 @@
/* command.c - gpg-agent command handler
* Copyright (C) 2001-2011 Free Software Foundation, Inc.
* Copyright (C) 2001-2013 Werner Koch
* Copyright (C) 2015-2021 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* FIXME: we should not use the default assuan buffering but setup
some buffering in secure mempory to protect session keys etc. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "agent.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/ssh-utils.h"
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum allowed size of the inquired ciphertext. */
#define MAXLEN_CIPHERTEXT 4096
/* Maximum allowed size of the key parameters. */
#define MAXLEN_KEYPARAM 1024
/* Maximum allowed size of key data as used in inquiries (bytes). */
#define MAXLEN_KEYDATA 8192
/* Maximum length of a secret to store under one key. */
#define MAXLEN_PUT_SECRET 4096
/* The size of the import/export KEK key (in bytes). */
#define KEYWRAP_KEYSIZE (128/8)
/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
text string. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Check that the maximum digest length we support has at least the
length of the keygrip. */
#if MAX_DIGEST_LEN < 20
#error MAX_DIGEST_LEN shorter than keygrip
#endif
/* Data used to associate an Assuan context with local server data.
This is this modules local part of the server_control_s struct. */
struct server_local_s
{
/* Our Assuan context. */
assuan_context_t assuan_ctx;
/* If this flag is true, the passphrase cache is used for signing
operations. It defaults to true but may be set on a per
connection base. The global option opt.ignore_cache_for_signing
takes precedence over this flag. */
unsigned int use_cache_for_signing : 1;
/* Flag to suppress I/O logging during a command. */
unsigned int pause_io_logging : 1;
/* Flag indicating that the connection is from ourselves. */
unsigned int connect_from_self : 1;
/* Helper flag for io_monitor to allow suppressing of our own
* greeting in some cases. See io_monitor for details. */
unsigned int greeting_seen : 1;
/* If this flag is set to true the agent will be terminated after
the end of the current session. */
unsigned int stopme : 1;
/* Flag indicating whether pinentry notifications shall be done. */
unsigned int allow_pinentry_notify : 1;
/* An allocated description for the next key operation. This is
used if a pinnetry needs to be popped up. */
char *keydesc;
/* Malloced KEK (Key-Encryption-Key) for the import_key command. */
void *import_key;
/* Malloced KEK for the export_key command. */
void *export_key;
/* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */
int allow_fully_canceled;
/* Last CACHE_NONCE sent as status (malloced). */
char *last_cache_nonce;
/* Last PASSWD_NONCE sent as status (malloced). */
char *last_passwd_nonce;
/* Per connection cache of the keyinfo from the cards. The
* eventcounters for cards at the time the info was fetched is
* stored here as a freshness indicator. */
struct {
struct card_key_info_s *ki;
unsigned int eventno;
unsigned int maybe_key_change;
} last_card_keyinfo;
};
/* An entry for the getval/putval commands. */
struct putval_item_s
{
struct putval_item_s *next;
size_t off; /* Offset to the value into DATA. */
size_t len; /* Length of the value. */
char d[1]; /* Key | Nul | value. */
};
/* A list of key value pairs fpr the getval/putval commands. */
static struct putval_item_s *putval_list;
/* To help polling clients, we keep track of the number of certain
events. This structure keeps those counters. The counters are
integers and there should be no problem if they are overflowing as
callers need to check only whether a counter changed. The actual
values are not meaningful. */
struct
{
/* Incremented if any of the other counters below changed. */
unsigned int any;
/* Incremented if a key is added or removed from the internal privat
key database. */
unsigned int key;
/* Incremented if a change of the card readers stati has been
detected. */
unsigned int card;
/* Internal counter to track possible changes to a key.
* FIXME: This should be replaced by generic notifications from scd.
*/
unsigned int maybe_key_change;
} eventcounter;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Release the memory buffer MB but first wipe out the used memory. */
static void
clear_outbuf (membuf_t *mb)
{
void *p;
size_t n;
p = get_membuf (mb, &n);
if (p)
{
wipememory (p, n);
xfree (p);
}
}
/* Write the content of memory buffer MB as assuan data to CTX and
wipe the buffer out afterwards. */
static gpg_error_t
write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
{
gpg_error_t ae;
void *p;
size_t n;
p = get_membuf (mb, &n);
if (!p)
return out_of_core ();
ae = assuan_send_data (ctx, p, n);
memset (p, 0, n);
xfree (p);
return ae;
}
/* Clear the nonces used to enable the passphrase cache for certain
multi-command command sequences. */
static void
clear_nonce_cache (ctrl_t ctrl)
{
if (ctrl->server_local->last_cache_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = NULL;
}
if (ctrl->server_local->last_passwd_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = NULL;
}
}
/* This function is called by Libassuan whenever the client sends a
reset. It has been registered similar to the other Assuan
commands. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
memset (ctrl->keygrip, 0, 20);
ctrl->have_keygrip = ctrl->have_keygrip1 = 0;
ctrl->digest.valuelen = 0;
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
clear_nonce_cache (ctrl);
/* Note that a RESET does not clear the ephemeral store because
* clients are used to issue a RESET on a connection. */
return 0;
}
/* Replace all '+' by a blank in the string S. */
static void
plus_to_blank (char *s)
{
for (; *s; s++)
{
if (*s == '+')
*s = ' ';
}
}
/* Parse a hex string. Return an Assuan error code or 0 on success and the
length of the parsed string in LEN. */
static int
parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
{
const char *p;
size_t n;
/* parse the hash value */
for (p=string, n=0; hexdigitp (p); p++, n++)
;
if (*p != ' ' && *p != '\t' && *p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
*len = n;
return 0;
}
/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
provide space for 20 bytes. BUF is not changed if the function
returns an error. */
static int
parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
{
int rc;
size_t n = 0;
rc = parse_hexstring (ctx, string, &n);
if (rc)
return rc;
n /= 2;
if (n != 20)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip");
if (hex2bin (string, buf, 20) < 0)
return set_error (GPG_ERR_BUG, "hex2bin");
return 0;
}
/* Parse the TTL from STRING. Leading and trailing spaces are
* skipped. The value is constrained to -1 .. MAXINT. On error 0 is
* returned, else the number of bytes scanned. */
static size_t
parse_ttl (const char *string, int *r_ttl)
{
const char *string_orig = string;
long ttl;
char *pend;
ttl = strtol (string, &pend, 10);
string = pend;
if (string == string_orig || !(spacep (string) || !*string)
|| ttl < -1L || (int)ttl != (long)ttl)
{
*r_ttl = 0;
return 0;
}
while (spacep (string) || *string== '\n')
string++;
*r_ttl = (int)ttl;
return string - string_orig;
}
/* Write an Assuan status line. KEYWORD is the first item on the
* status line. The following arguments are all separated by a space
* in the output. The last argument must be a NULL. Linefeeds and
* carriage returns characters (which are not allowed in an Assuan
* status line) are silently quoted in C-style. */
gpg_error_t
agent_write_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
arg instead of an assuan context as first argument. */
gpg_error_t
agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about a launched Pinentry. Because
that might disturb some older clients, this is only done if enabled
via an option. Returns an gpg error code. */
gpg_error_t
agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
{
char line[256];
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
pid, extra?" ":"", extra? extra:"");
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/* An agent progress callback for Libgcrypt. This has been registered
* to be called via the progress dispatcher mechanism from
* gpg-agent.c */
static void
progress_cb (ctrl_t ctrl, const char *what, int printchar,
int current, int total)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
;
else if (printchar == '\n' && what && !strcmp (what, "primegen"))
agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
else
agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total);
}
/* Helper to print a message while leaving a command. Note that this
* function does not call assuan_set_error; the caller may do this
* prior to calling us. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
/* Not all users of gpg-agent know about the fully canceled
error code; map it back if needed. */
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!ctrl->server_local->allow_fully_canceled)
err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
}
/* Most code from common/ does not know the error source, thus
we fix this here. */
if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* Take the keyinfo for cards from our local cache. Actually this
* cache could be a global one but then we would need to employ
* reference counting. */
static struct card_key_info_s *
get_keyinfo_on_cards (ctrl_t ctrl)
{
struct card_key_info_s *keyinfo_on_cards = NULL;
if (opt.disable_daemon[DAEMON_SCD])
return NULL;
if (ctrl->server_local->last_card_keyinfo.ki
&& ctrl->server_local->last_card_keyinfo.eventno == eventcounter.card
&& (ctrl->server_local->last_card_keyinfo.maybe_key_change
== eventcounter.maybe_key_change))
{
keyinfo_on_cards = ctrl->server_local->last_card_keyinfo.ki;
}
else if (!agent_card_keyinfo (ctrl, NULL, 0, &keyinfo_on_cards))
{
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
ctrl->server_local->last_card_keyinfo.ki = keyinfo_on_cards;
ctrl->server_local->last_card_keyinfo.eventno = eventcounter.card;
ctrl->server_local->last_card_keyinfo.maybe_key_change
= eventcounter.maybe_key_change;
}
return keyinfo_on_cards;
}
static const char hlp_geteventcounter[] =
"GETEVENTCOUNTER\n"
"\n"
"Return a status line named EVENTCOUNTER with the current values\n"
"of all event counters. The values are decimal numbers in the range\n"
"0 to UINT_MAX and wrapping around to 0. The actual values should\n"
"not be relied upon, they shall only be used to detect a change.\n"
"\n"
"The currently defined counters are:\n"
"\n"
"ANY - Incremented with any change of any of the other counters.\n"
"KEY - Incremented for added or removed private keys.\n"
"CARD - Incremented for changes of the card readers stati.";
static gpg_error_t
cmd_geteventcounter (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
eventcounter.any,
eventcounter.key,
eventcounter.card);
}
/* This function should be called once for all key removals or
additions. This function is assured not to do any context
switches. */
void
bump_key_eventcounter (void)
{
eventcounter.key++;
eventcounter.any++;
}
/* This function should be called for all card reader status
changes. This function is assured not to do any context
switches. */
void
bump_card_eventcounter (void)
{
eventcounter.card++;
eventcounter.any++;
}
static const char hlp_istrusted[] =
"ISTRUSTED <hexstring_with_fingerprint>\n"
"\n"
"Return OK when we have an entry with this fingerprint in our\n"
"trustlist";
static gpg_error_t
cmd_istrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p, *pn;
char fpr[41];
/* Parse the fingerprint value. */
pn = NULL; /* Indicates that we have not reparsed. */
parseagain:
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p || !(n == 40 || n == 32))
{
if (!pn && *p && strchr (p, ':'))
{
for (pn=p=line; *p ; p++)
if (*p != ':')
*pn++ = *p;
*pn = 0;
goto parseagain;
}
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
}
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
rc = agent_istrusted (ctrl, fpr, NULL);
if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
return rc;
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
return gpg_error (GPG_ERR_NOT_TRUSTED);
else
return leave_cmd (ctx, rc);
}
static const char hlp_listtrusted[] =
"LISTTRUSTED [--status]\n"
"\n"
"List all entries from the trustlist. With --status the\n"
"keys are listed using status line similar to ISTRUSTED";
static gpg_error_t
cmd_listtrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_status;
opt_status = has_option (line, "--status");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = agent_listtrusted (ctrl, ctx, opt_status);
return leave_cmd (ctx, err);
}
static const char hlp_martrusted[] =
"MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
"\n"
"Store a new key in into the trustlist.";
static gpg_error_t
cmd_marktrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
int flag;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the fingerprint value */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (!spacep (p) || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
while (spacep (p))
p++;
flag = *p++;
if ( (flag != 'S' && flag != 'P') || !spacep (p) )
return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S");
while (spacep (p))
p++;
rc = agent_marktrusted (ctrl, p, fpr, flag);
return leave_cmd (ctx, rc);
}
static const char hlp_havekey[] =
"HAVEKEY <hexstrings_with_keygrips>\n"
"HAVEKEY --list[=<limit>]\n"
"HAVEKEY --info <hexkeygrip>\n"
"\n"
"Return success if at least one of the secret keys with the given\n"
"keygrips is available. With --list return all available keygrips\n"
"as binary data; with <limit> bail out at this number of keygrips.\n"
"In --info mode check just one keygrip.";
static gpg_error_t
cmd_havekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
char *p;
int list_mode = 0; /* Less than 0 for no limit. */
int info_mode = 0;
int counter;
char *dirname = NULL;
gnupg_dir_t dir = NULL;
gnupg_dirent_t dir_entry;
char hexgrip[41];
struct card_key_info_s *keyinfo_on_cards, *l;
if (has_option (line, "--info"))
info_mode = 1;
else if (has_option_name (line, "--list"))
{
if ((p = option_value (line, "--list")))
list_mode = atoi (p);
else
list_mode = -1;
}
line = skip_options (line);
if (info_mode)
{
int keytype;
const char *infostring;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_key_info_from_file (ctrl, grip, &keytype, NULL, NULL);
if (err)
goto leave;
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE: infostring = "clear"; break;
case PRIVATE_KEY_PROTECTED: infostring = "protected"; break;
case PRIVATE_KEY_SHADOWED: infostring = "shadowed"; break;
default: infostring = "unknown"; break;
}
err = agent_write_status (ctrl, "KEYFILEINFO", infostring, NULL);
goto leave;
}
if (!list_mode)
{
do
{
err = parse_keygrip (ctx, line, grip);
if (err)
return err;
if (!agent_key_available (ctrl, grip))
return 0; /* Found. */
while (*line && *line != ' ' && *line != '\t')
line++;
while (*line == ' ' || *line == '\t')
line++;
}
while (*line);
/* No leave_cmd() here because errors are expected and would clutter
* the log. */
return gpg_error (GPG_ERR_NO_SECKEY);
}
/* List mode. */
dir = NULL;
dirname = NULL;
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
goto leave;
}
counter = 0;
while ((dir_entry = gnupg_readdir (dir)))
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
if (list_mode > 0 && ++counter > list_mode)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
err = assuan_send_data (ctx, grip, 20);
if (err)
goto leave;
}
/* And now the keys from the current cards. If they already got a
* stub, they are listed twice but we don't care. */
keyinfo_on_cards = get_keyinfo_on_cards (ctrl);
for (l = keyinfo_on_cards; l; l = l->next)
{
if ( hex2bin (l->keygrip, grip, 20) < 0 )
continue; /* Bad hex string. */
if (list_mode > 0 && ++counter > list_mode)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
err = assuan_send_data (ctx, grip, 20);
if (err)
goto leave;
}
err = 0;
leave:
gnupg_closedir (dir);
xfree (dirname);
return leave_cmd (ctx, err);
}
static const char hlp_sigkey[] =
"SIGKEY [--another] <hexstring_with_keygrip>\n"
"SETKEY [--another] <hexstring_with_keygrip>\n"
"\n"
"Set the key used for a sign or decrypt operation.";
static gpg_error_t
cmd_sigkey (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_another;
opt_another = has_option (line, "--another");
line = skip_options (line);
rc = parse_keygrip (ctx, line, opt_another? ctrl->keygrip1 : ctrl->keygrip);
if (rc)
return rc;
if (opt_another)
ctrl->have_keygrip1 = 1;
else
ctrl->have_keygrip = 1;
return 0;
}
static const char hlp_setkeydesc[] =
"SETKEYDESC plus_percent_escaped_string\n"
"\n"
"Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
"or EXPORT_KEY operation if this operation requires a passphrase. If\n"
"this command is not used a default text will be used. Note, that\n"
"this description implicitly selects the label used for the entry\n"
"box; if the string contains the string PIN (which in general will\n"
"not be translated), \"PIN\" is used, otherwise the translation of\n"
"\"passphrase\" is used. The description string should not contain\n"
"blanks unless they are percent or '+' escaped.\n"
"\n"
"The description is only valid for the next PKSIGN, PKDECRYPT,\n"
"IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
static gpg_error_t
cmd_setkeydesc (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *desc, *p;
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage; we might late use it for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
plus_to_blank (desc);
xfree (ctrl->server_local->keydesc);
if (ctrl->restricted)
{
ctrl->server_local->keydesc = strconcat
((ctrl->restricted == 2
? _("Note: Request from the web browser.")
: _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL);
}
else
ctrl->server_local->keydesc = xtrystrdup (desc);
if (!ctrl->server_local->keydesc)
return out_of_core ();
return 0;
}
static const char hlp_sethash[] =
"SETHASH (--hash=<name>)|(<algonumber>) <hexstring>]\n"
"SETHASH [--pss] --inquire\n"
"\n"
"The client can use this command to tell the server about the data\n"
"(which usually is a hash) to be signed. The option --inquire is\n"
"used to ask back for to-be-signed data in case of PureEdDSA or\n"
"with --pss for pre-formatted rsaPSS.";
static gpg_error_t
cmd_sethash (assuan_context_t ctx, char *line)
{
gpg_error_t err;
size_t n;
char *p;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *buf;
char *endp;
int algo;
int opt_inquire, opt_pss;
/* Parse the alternative hash options which may be used instead of
the algo number. */
if (has_option_name (line, "--hash"))
{
if (has_option (line, "--hash=sha1"))
algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=rmd160"))
algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=md5"))
algo = GCRY_MD_MD5;
else if (has_option (line, "--hash=tls-md5sha1"))
algo = MD_USER_TLS_MD5SHA1;
else if (has_option (line, "--hash=none"))
algo = 0;
else
{
err = set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
goto leave;
}
}
else
algo = 0;
opt_pss = has_option (line, "--pss");
opt_inquire = has_option (line, "--inquire");
line = skip_options (line);
if (!algo && !opt_inquire)
{
/* No hash option has been given: require an algo number instead */
algo = (int)strtoul (line, &endp, 10);
for (line = endp; *line == ' ' || *line == '\t'; line++)
;
if (!algo || gcry_md_test_algo (algo))
{
err = set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
goto leave;
}
}
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
ctrl->digest.algo = algo;
ctrl->digest.raw_value = 0;
ctrl->digest.is_pss = opt_pss;
if (opt_inquire)
{
/* We limit the to-be-signed data to some reasonable size which
* may eventually allow us to pass that even to smartcards. */
size_t maxlen = 2048;
if (algo)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and an algo are specified");
goto leave;
}
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!err)
err = assuan_inquire (ctx, "TBSDATA", &buf, &n, maxlen);
if (err)
goto leave;
ctrl->digest.data = buf;
ctrl->digest.valuelen = n;
}
else
{
/* Parse the hash value. */
n = 0;
err = parse_hexstring (ctx, line, &n);
if (err)
goto leave;
n /= 2;
if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
;
else if (n != 16 && n != 20 && n != 24
&& n != 28 && n != 32 && n != 48 && n != 64)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
goto leave;
}
if (n > MAX_DIGEST_LEN)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "hash value to long");
goto leave;
}
buf = ctrl->digest.value;
ctrl->digest.valuelen = n;
for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
buf[n] = xtoi_2 (p);
for (; n < ctrl->digest.valuelen; n++)
buf[n] = 0;
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_pksign[] =
"PKSIGN [<options>] [<cache_nonce>]\n"
"\n"
"Perform the actual sign operation. Neither input nor output are\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
gpg_error_t err;
cache_mode_t cache_mode = CACHE_MODE_NORMAL;
ctrl_t ctrl = assuan_get_pointer (ctx);
membuf_t outbuf;
char *cache_nonce = NULL;
char *p;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
if (opt.ignore_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
else if (!ctrl->server_local->use_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
init_membuf (&outbuf, 512);
err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
&outbuf, cache_mode);
if (err)
clear_outbuf (&outbuf);
else
err = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT [--kem[=<kemid>] [<options>]\n"
"\n"
"Perform the actual decrypt operation. Input is not\n"
"sensitive to eavesdropping.\n"
"If the --kem option is used, decryption is done with the KEM,\n"
"inquiring upper-layer option, when needed. KEMID can be\n"
"specified with --kem option; Valid value is: PQC-PGP, PGP, or CMS.\n"
"Default is PQC-PGP.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value;
size_t valuelen;
membuf_t outbuf;
int padding = -1;
const char *p;
int kemid = -1;
p = has_option_name (line, "--kem");
if (p)
{
kemid = KEM_PQC_PGP;
if (*p == '=')
{
p++;
if (!strcmp (p, "PQC-PGP"))
kemid = KEM_PQC_PGP;
else if (!strcmp (p, "PGP"))
kemid = KEM_PGP;
else if (!strcmp (p, "CMS"))
kemid = KEM_CMS;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid KEM algorithm");
}
}
/* First inquire the data to decrypt */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
if (!rc)
rc = assuan_inquire (ctx, "CIPHERTEXT",
&value, &valuelen, MAXLEN_CIPHERTEXT);
if (rc)
return rc;
init_membuf (&outbuf, 512);
if (kemid < 0)
rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
value, valuelen, &outbuf, &padding);
else
rc = agent_kem_decrypt (ctrl, ctrl->server_local->keydesc, kemid,
value, valuelen, &outbuf);
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
{
if (padding != -1)
rc = print_assuan_status (ctx, "PADDING", "%d", padding);
else
rc = 0;
if (!rc)
rc = write_and_clear_outbuf (ctx, &outbuf);
}
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, rc);
}
static const char hlp_genkey[] =
"GENKEY [--no-protection] [--preset] [--timestamp=<isodate>]\n"
" [--inq-passwd] [--passwd-nonce=<s>] [<cache_nonce>]\n"
"\n"
"Generate a new key, store the secret part and return the public\n"
"part. Here is an example transaction:\n"
"\n"
" C: GENKEY\n"
" S: INQUIRE KEYPARAM\n"
" C: D (genkey (rsa (nbits 3072)))\n"
" C: END\n"
" S: D (public-key\n"
" S: D (rsa (n 326487324683264) (e 10001)))\n"
" S: OK key created\n"
"\n"
"If the --preset option is used the passphrase for the generated\n"
"key will be added to the cache. If --inq-passwd is used an inquire\n"
"with the keyword NEWPASSWD is used to request the passphrase for the\n"
"new key. If a --passwd-nonce is used, the corresponding cached\n"
"passphrase is used to protect the new key. If --timestamp is given\n"
"its value is recorded as the key's creation time; the value is\n"
"expected in ISO format (e.g. \"20030316T120000\").";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *newpasswd = NULL;
membuf_t outbuf;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int opt_inq_passwd;
size_t n;
char *p, *pend;
const char *s;
time_t opt_timestamp;
int c;
unsigned int flags = 0;
init_membuf (&outbuf, 512);
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--no-protection"))
flags |= GENKEY_FLAG_NO_PROTECTION;
if (has_option (line, "--preset"))
flags |= GENKEY_FLAG_PRESET;
opt_inq_passwd = has_option (line, "--inq-passwd");
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
{
rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
goto leave;
}
opt_timestamp = isotime2epoch (s+1);
if (opt_timestamp < 1)
{
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
goto leave;
}
}
else
opt_timestamp = 0;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
/* First inquire the parameters */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
if (rc)
goto leave;
rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
if (rc)
goto leave;
/* If requested, ask for the password to be used for the key. If
this is not used the regular Pinentry mechanism is used. */
if (opt_inq_passwd && !(flags & GENKEY_FLAG_NO_PROTECTION))
{
/* (N is used as a dummy) */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
assuan_end_confidential (ctx);
if (rc)
goto leave;
if (!*newpasswd)
{
/* Empty password given - switch to no-protection mode. */
xfree (newpasswd);
newpasswd = NULL;
flags |= GENKEY_FLAG_NO_PROTECTION;
}
}
else if (passwd_nonce)
newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
rc = agent_genkey (ctrl, flags, cache_nonce, opt_timestamp,
(char*)value, valuelen,
newpasswd, &outbuf);
leave:
if (newpasswd)
{
/* Assuan_inquire does not allow us to read into secure memory
thus we need to wipe it ourself. */
wipememory (newpasswd, strlen (newpasswd));
xfree (newpasswd);
}
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
rc = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, rc);
}
static const char hlp_keyattr[] =
"KEYATTR [--delete] <hexstring_with_keygrip> <ATTRNAME> [<VALUE>]\n"
"\n"
"For the secret key, show the attribute of ATTRNAME. With VALUE,\n"
"put the value to the attribute. Use --delete option to delete.";
static gpg_error_t
cmd_keyattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
const char *argv[3];
int argc;
unsigned char grip[20];
int opt_delete;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_delete = has_option (line, "--delete");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
if (!strcmp (argv[1], "Key:") /* It allows only access to attribute */
/* Make sure ATTRNAME ends with colon. */
|| argv[1][strlen (argv[1]) - 1] != ':')
{
err = gpg_error (GPG_ERR_INV_PARAMETER);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (!err)
{
gcry_sexp_t s_key = NULL;
nvc_t keymeta = NULL;
const char *p;
err = agent_raw_key_from_file (ctrl, grip, &s_key, &keymeta);
if (err)
goto leave;
if (argc == 2)
{
nve_t e = NULL;
if (keymeta)
e = nvc_lookup (keymeta, argv[1]);
if (opt_delete)
{
if (e)
{
nvc_delete (keymeta, e);
goto key_attr_write;
}
}
else if (e)
{
p = nve_value (e);
if (p)
err = assuan_send_data (ctx, p, strlen (p));
}
}
else if (argc == 3)
{
if (!keymeta)
keymeta = nvc_new_private_key ();
err = nvc_set (keymeta, argv[1], argv[2]);
key_attr_write:
if (!err)
err = nvc_set_private_key (keymeta, s_key);
if (!err)
err = agent_update_private_key (ctrl, grip, keymeta);
}
nvc_release (keymeta);
gcry_sexp_release (s_key);
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_readkey[] =
"READKEY [--no-data] [--format=ssh] <hexstring_with_keygrip>\n"
" --card <keyid>\n"
"\n"
"Return the public key for the given keygrip or keyid.\n"
"With --card, private key file with card information will be created.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char grip[20];
gcry_sexp_t s_pkey = NULL;
unsigned char *pkbuf = NULL;
size_t pkbuflen;
int opt_card, opt_no_data, opt_format_ssh;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_no_data = has_option (line, "--no-data");
opt_card = has_option (line, "--card");
opt_format_ssh = has_option (line, "--format=ssh");
line = skip_options (line);
if (opt_card)
{
char *serialno = NULL;
char *keyidbuf = NULL;
const char *keyid = line;
rc = agent_card_getattr (ctrl, "SERIALNO", &serialno, NULL);
if (rc)
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (rc));
goto leave;
}
/* Hack to create the shadow key for the OpenPGP standard keys. */
if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID"))
&& !agent_card_getattr (ctrl, keyid, &keyidbuf, NULL))
keyid = keyidbuf;
rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL);
if (rc)
goto leave;
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
if (rc)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
rc = gcry_pk_testkey (s_pkey);
if (rc == 0)
rc = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip))
{
/* (Shadow)-key is not available in our key storage. */
char *dispserialno;
char hexgrip[40+1];
bin2hex (grip, 20, hexgrip);
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, hexgrip);
rc = agent_write_shadow_key (ctrl, grip, serialno, keyid, pkbuf, 0,
dispserialno);
xfree (dispserialno);
if (rc)
goto leave;
}
xfree (serialno);
xfree (keyidbuf);
}
else
{
rc = parse_keygrip (ctx, line, grip);
if (rc)
goto leave;
rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
if (rc)
goto leave;
else
{
if (opt_format_ssh)
{
estream_t stream = NULL;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = ssh_public_key_in_base64 (s_pkey, stream, "(none)");
if (rc)
{
es_fclose (stream);
goto leave;
}
rc = es_fclose_snatch (stream, (void **)&pkbuf, &pkbuflen);
if (rc)
goto leave;
}
else
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (pkbuflen);
pkbuf = xtrymalloc (pkbuflen);
if (!pkbuf)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON,
pkbuf, pkbuflen);
}
}
}
rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen);
leave:
xfree (pkbuf);
gcry_sexp_release (s_pkey);
return leave_cmd (ctx, rc);
}
static const char hlp_keyinfo[] =
"KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh]\n"
" [--need-attr=ATTRNAME] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. If --ssh-list is given information\n"
"about all keys listed in the sshcontrol are returned. With --with-ssh\n"
"information from sshcontrol is always added to the info. If --need-attr\n"
"is used the key is only listed if the value of the given attribute name\n"
"(e.g. \"Use-for-ssh\") is true. Unless --data is given, the information\n"
"is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"TYPE is describes the type of the key:\n"
" 'D' - Regular key stored on disk,\n"
" 'T' - Key is stored on a smartcard (token),\n"
" 'X' - Unknown type,\n"
" '-' - Key is missing.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"CACHED is 1 if the passphrase for the key was found in the key cache.\n"
" If not, a '-' is used instead.\n"
"\n"
"PROTECTION describes the key protection type:\n"
" 'P' - The key is protected with a passphrase,\n"
" 'C' - The key is not protected,\n"
" '-' - Unknown protection.\n"
"\n"
"FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
" printed if the option --ssh-fpr has been used. If ALGO is not given\n"
" to that option the default ssh fingerprint algo is used. Without the\n"
" option a '-' is printed.\n"
"\n"
"TTL is the TTL in seconds for that key or '-' if n/a.\n"
"\n"
"FLAGS is a word consisting of one-letter flags:\n"
" 'D' - The key has been disabled,\n"
" 'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
" 'c' - Use of the key needs to be confirmed,\n"
" 'A' - The key is available on card,\n"
" '-' - No flags given.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
int data, int with_ssh_fpr, int in_ssh,
int ttl, int disabled, int confirm, int on_card,
const char *need_attr, int list_mode)
{
gpg_error_t err;
char hexgrip[40+1];
char *fpr = NULL;
int keytype;
unsigned char *shadow_info = NULL;
unsigned char *shadow_info_type = NULL;
char *serialno = NULL;
char *idstr = NULL;
const char *keytypestr;
const char *cached;
const char *protectionstr;
char *pw;
int missing_key = 0;
char ttlbuf[20];
char flagsbuf[5];
err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info,
&shadow_info_type);
if (err)
{
if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
missing_key = 1;
else
goto leave;
}
if (need_attr || (ctrl->restricted && list_mode))
{
gcry_sexp_t s_key = NULL;
nvc_t keymeta = NULL;
int istrue, has_rl;
if (missing_key)
goto leave; /* No attribute available. */
err = agent_raw_key_from_file (ctrl, grip, &s_key, &keymeta);
if (!keymeta)
istrue = 0;
else
{
has_rl = 0;
if (ctrl->restricted && list_mode
&& !(has_rl = nvc_get_boolean (keymeta, "Remote-list:")))
istrue = 0;
else if (need_attr)
istrue = nvc_get_boolean (keymeta, need_attr);
else
istrue = has_rl;
nvc_release (keymeta);
}
gcry_sexp_release (s_key);
if (!istrue)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
}
/* Reformat the grip so that we use uppercase as good style. */
bin2hex (grip, 20, hexgrip);
if (ttl > 0)
snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
else
strcpy (ttlbuf, "-");
*flagsbuf = 0;
if (disabled)
strcat (flagsbuf, "D");
if (in_ssh)
strcat (flagsbuf, "S");
if (confirm)
strcat (flagsbuf, "c");
if (on_card)
strcat (flagsbuf, "A");
if (!*flagsbuf)
strcpy (flagsbuf, "-");
if (missing_key)
{
protectionstr = "-"; keytypestr = "-";
}
else
{
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
protectionstr = "C"; keytypestr = "D";
break;
case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
break;
case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
break;
default: protectionstr = "-"; keytypestr = "X";
break;
}
}
/* Compute the ssh fingerprint if requested. */
if (with_ssh_fpr)
{
gcry_sexp_t key;
if (!agent_raw_key_from_file (ctrl, grip, &key, NULL))
{
ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr);
gcry_sexp_release (key);
}
}
/* Here we have a little race by doing the cache check separately
from the retrieval function. Given that the cache flag is only a
hint, it should not really matter. */
pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL);
cached = pw ? "1" : "-";
xfree (pw);
if (shadow_info)
{
if (strcmp (shadow_info_type, "t1-v1") == 0)
{
err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
if (err)
goto leave;
}
else if (strcmp (shadow_info_type, "tpm2-v1") == 0)
{
serialno = xstrdup("TPM-Protected");
idstr = NULL;
}
else
{
log_error ("unrecognised shadow key type %s\n", shadow_info_type);
err = GPG_ERR_BAD_KEY;
goto leave;
}
}
if (!data)
err = agent_write_status (ctrl, "KEYINFO",
hexgrip,
keytypestr,
serialno? serialno : "-",
idstr? idstr : "-",
cached,
protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf,
NULL);
else
{
char *string;
string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
hexgrip, keytypestr,
serialno? serialno : "-",
idstr? idstr : "-", cached, protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf);
if (!string)
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, string, strlen(string));
xfree (string);
}
leave:
xfree (fpr);
xfree (shadow_info_type);
xfree (shadow_info);
xfree (serialno);
xfree (idstr);
return err;
}
/* Entry into the command KEYINFO. This function handles the
* command option processing. For details see hlp_keyinfo above. */
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int err;
unsigned char grip[20];
gnupg_dir_t dir = NULL;
int list_mode;
int opt_data, opt_ssh_fpr, opt_with_ssh;
ssh_control_file_t cf = NULL;
char hexgrip[41];
int disabled, ttl, confirm, is_ssh;
struct card_key_info_s *keyinfo_on_cards;
struct card_key_info_s *l;
int on_card;
char *need_attr = NULL;
size_t n;
if (has_option (line, "--ssh-list"))
list_mode = 2;
else
list_mode = has_option (line, "--list");
opt_data = has_option (line, "--data");
if (has_option_name (line, "--ssh-fpr"))
{
if (has_option (line, "--ssh-fpr=md5"))
opt_ssh_fpr = GCRY_MD_MD5;
else if (has_option (line, "--ssh-fpr=sha1"))
opt_ssh_fpr = GCRY_MD_SHA1;
else if (has_option (line, "--ssh-fpr=sha256"))
opt_ssh_fpr = GCRY_MD_SHA256;
else
opt_ssh_fpr = opt.ssh_fingerprint_digest;
}
else
opt_ssh_fpr = 0;
opt_with_ssh = has_option (line, "--with-ssh");
err = get_option_value (line, "--need-attr", &need_attr);
if (err)
goto leave;
if (need_attr && (n=strlen (need_attr)) && need_attr[n-1] != ':')
{
/* We need to append a colon. */
char *tmp = strconcat (need_attr, ":", NULL);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (need_attr);
need_attr = tmp;
}
line = skip_options (line);
if (opt_with_ssh || list_mode == 2)
cf = ssh_open_control_file ();
keyinfo_on_cards = get_keyinfo_on_cards (ctrl);
if (list_mode == 2)
{
if (cf)
{
while (!ssh_read_control_file (cf, hexgrip,
&disabled, &ttl, &confirm))
{
if (hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
ttl, disabled, confirm, on_card, need_attr,
list_mode);
if ((need_attr || ctrl->restricted)
&& gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
goto leave;
}
}
err = 0;
}
else if (list_mode)
{
char *dirname;
gnupg_dirent_t dir_entry;
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
xfree (dirname);
goto leave;
}
xfree (dirname);
while ( (dir_entry = gnupg_readdir (dir)) )
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, hexgrip,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card, need_attr,
list_mode);
if ((need_attr || ctrl->restricted)
&& gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
goto leave;
}
err = 0;
}
else
{
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, line,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, line, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card, need_attr, 0);
}
leave:
xfree (need_attr);
ssh_close_control_file (cf);
gnupg_closedir (dir);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
leave_cmd (ctx, err);
return err;
}
/* Helper for cmd_get_passphrase. */
static int
send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
{
size_t n;
int rc;
assuan_begin_confidential (ctx);
n = strlen (pw);
if (via_data)
rc = assuan_send_data (ctx, pw, n);
else
{
char *p = xtrymalloc_secure (n*2+1);
if (!p)
rc = gpg_error_from_syserror ();
else
{
bin2hex (pw, n, p);
rc = assuan_set_okay_line (ctx, p);
xfree (p);
}
}
assuan_end_confidential (ctx);
return rc;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
static const char hlp_get_passphrase[] =
"GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
" [--qualitybar] [--newsymkey] <cache_id>\n"
" [<error_message> <prompt> <description>]\n"
"\n"
"This function is usually used to ask for a passphrase to be used\n"
"for conventional encryption, but may also be used by programs which\n"
"need specal handling of passphrases. This command uses a syntax\n"
"which helps clients to use the agent with minimum effort. The\n"
"agent either returns with an error or with a OK followed by the hex\n"
"encoded passphrase. Note that the length of the strings is\n"
"implicitly limited by the maximum length of a command.\n"
"\n"
"If the option \"--data\" is used the passphrase is returned by usual\n"
"data lines and not on the okay line.\n"
"\n"
"If the option \"--check\" is used the passphrase constraints checks as\n"
"implemented by gpg-agent are applied. A check is not done if the\n"
"passphrase has been found in the cache.\n"
"\n"
"If the option \"--no-ask\" is used and the passphrase is not in the\n"
"cache the user will not be asked to enter a passphrase but the error\n"
"code GPG_ERR_NO_DATA is returned. \n"
"\n"
"If the option\"--newsymkey\" is used the agent asks for a new passphrase\n"
"to be used in symmetric-only encryption. This must not be empty.\n"
"\n"
"If the option \"--qualitybar\" is used a visual indication of the\n"
"entered passphrase quality is shown. (Unless no minimum passphrase\n"
"length has been configured.)";
static gpg_error_t
cmd_get_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *pw;
char *response = NULL;
char *response2 = NULL;
char *cacheid = NULL; /* May point into LINE. */
char *desc = NULL; /* Ditto */
char *prompt = NULL; /* Ditto */
char *errtext = NULL; /* Ditto */
const char *desc2 = _("Please re-enter this passphrase");
char *p;
int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey;
int opt_repeat = 0;
char *entry_errtext = NULL;
struct pin_entry_info_s *pi = NULL;
struct pin_entry_info_s *pi2 = NULL;
int is_generated;
opt_data = has_option (line, "--data");
opt_check = has_option (line, "--check");
opt_no_ask = has_option (line, "--no-ask");
if (has_option_name (line, "--repeat"))
{
p = option_value (line, "--repeat");
if (p)
opt_repeat = atoi (p);
else
opt_repeat = 1;
}
opt_qualbar = has_option (line, "--qualitybar");
opt_newsymkey = has_option (line, "--newsymkey");
line = skip_options (line);
cacheid = line;
p = strchr (cacheid, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
errtext = p;
p = strchr (errtext, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
prompt = p;
p = strchr (prompt, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* Ignore trailing garbage. */
}
}
}
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
if (!desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* The only limitation in restricted mode is that we don't consider
* the cache. */
if (ctrl->restricted || !strcmp (cacheid, "X"))
cacheid = NULL;
if (!strcmp (errtext, "X"))
errtext = NULL;
if (!strcmp (prompt, "X"))
prompt = NULL;
if (!strcmp (desc, "X"))
desc = NULL;
pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL;
if (pw)
{
rc = send_back_passphrase (ctx, opt_data, pw);
xfree (pw);
goto leave;
}
else if (opt_no_ask)
{
rc = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* Note, that we only need to replace the + characters and should
* leave the other escaping in place because the escaped string is
* send verbatim to the pinentry which does the unescaping (but not
* the + replacing) */
if (errtext)
plus_to_blank (errtext);
if (prompt)
plus_to_blank (prompt);
if (desc)
plus_to_blank (desc);
/* If opt_repeat is 2 or higher we can't use our pin_entry_info_s
* based method but fallback to the old simple method. It is
* anyway questionable whether this extra repeat count makes any
* real sense. */
if (opt_newsymkey && opt_repeat < 2)
{
/* We do not want to break any existing usage of this command
* and thus we introduced the option --newsymkey to make this
* command more useful to query the passphrase for symmetric
* encryption. */
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 3;
pi->with_qualitybar = opt_qualbar;
pi->with_repeat = opt_repeat;
pi->constraints_flags = (CHECK_CONSTRAINTS_NOT_EMPTY
| CHECK_CONSTRAINTS_NEW_SYMKEY);
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 3;
pi2->check_cb = reenter_passphrase_cmp_cb;
pi2->check_cb_arg = pi->pin;
for (;;) /* (degenerated for-loop) */
{
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response,
desc,
prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER,
pi);
if (rc)
goto leave;
xfree (entry_errtext);
entry_errtext = NULL;
is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED);
/* We don't allow an empty passphrase in this mode. */
if (!is_generated
&& check_passphrase_constraints (ctrl, pi->pin,
pi->constraints_flags,
&entry_errtext))
{
pi->failed_tries = 0;
pi2->failed_tries = 0;
continue;
}
if (*pi->pin && !pi->repeat_okay
&& ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK
&& opt_repeat)
{
/* The passphrase is empty and the pinentry did not
* already run the repetition check, do it here. This
* is only called when using an old and simple pinentry.
* It is neither called in loopback mode because the
* caller does any passphrase repetition by herself nor if
* no repetition was requested. */
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response,
L_("Please re-enter this passphrase"),
prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER,
pi2);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered passphrase one did not match and
* the user did not hit cancel. */
entry_errtext = xtrystrdup (L_("does not match - try again"));
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
goto leave;
}
continue;
}
}
break;
}
if (!rc && *pi->pin)
{
/* Return the passphrase. */
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0);
rc = send_back_passphrase (ctx, opt_data, pi->pin);
}
}
else
{
next_try:
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response, desc, prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER, NULL);
xfree (entry_errtext);
entry_errtext = NULL;
is_generated = 0;
if (!rc)
{
int i;
if (opt_check
&& !is_generated
&& check_passphrase_constraints
(ctrl, response,
(opt_newsymkey? CHECK_CONSTRAINTS_NEW_SYMKEY:0),
&entry_errtext))
{
goto next_try;
}
for (i = 0; i < opt_repeat; i++)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
break;
xfree (response2);
response2 = NULL;
rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
errtext, 0,
cacheid, CACHE_MODE_USER, NULL);
if (rc)
break;
if (strcmp (response2, response))
{
entry_errtext = try_percent_escape
(_("does not match - try again"), NULL);
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
break;
}
goto next_try;
}
}
if (!rc)
{
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0);
rc = send_back_passphrase (ctx, opt_data, response);
}
}
}
leave:
xfree (response);
xfree (response2);
xfree (entry_errtext);
xfree (pi2);
xfree (pi);
return leave_cmd (ctx, rc);
}
static const char hlp_clear_passphrase[] =
"CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
"\n"
"may be used to invalidate the cache entry for a passphrase. The\n"
"function returns with OK even when there is no cached passphrase.\n"
"The --mode=normal option is used to clear an entry for a cacheid\n"
"added by the agent. The --mode=ssh option is used for a cacheid\n"
"added for ssh.\n";
static gpg_error_t
cmd_clear_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *cacheid = NULL;
char *p;
cache_mode_t cache_mode = CACHE_MODE_USER;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--mode=normal"))
cache_mode = CACHE_MODE_NORMAL;
else if (has_option (line, "--mode=ssh"))
cache_mode = CACHE_MODE_SSH;
line = skip_options (line);
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
cacheid = p;
p = strchr (cacheid, ' ');
if (p)
*p = 0; /* ignore garbage */
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0);
agent_clear_passphrase (ctrl, cacheid, cache_mode);
return 0;
}
static const char hlp_get_confirmation[] =
"GET_CONFIRMATION <description>\n"
"\n"
"This command may be used to ask for a simple confirmation.\n"
"DESCRIPTION is displayed along with a Okay and Cancel button. This\n"
"command uses a syntax which helps clients to use the agent with\n"
"minimum effort. The agent either returns with an error or with a\n"
"OK. Note, that the length of DESCRIPTION is implicitly limited by\n"
"the maximum length of a command. DESCRIPTION should not contain\n"
"any spaces, those must be encoded either percent escaped or simply\n"
"as '+'.";
static gpg_error_t
cmd_get_confirmation (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *desc = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage -may be later used for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (desc, "X"))
desc = NULL;
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
if (desc)
plus_to_blank (desc);
rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
return leave_cmd (ctx, rc);
}
static const char hlp_learn[] =
"LEARN [--send] [--sendinfo] [--force] [SERIALNO]\n"
"\n"
"Learn something about the currently inserted smartcard. With\n"
"--sendinfo information about the card is returned; with --send\n"
"the available certificates are returned as D lines; with --force\n"
"private key storage will be updated by the result. With SERIALNO\n"
"given the current card is first switched to the specified one.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int send, sendinfo, force;
const char *demand_sn;
send = has_option (line, "--send");
sendinfo = send? 1 : has_option (line, "--sendinfo");
force = has_option (line, "--force");
line = skip_options (line);
demand_sn = *line? line : NULL;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force, demand_sn);
return leave_cmd (ctx, err);
}
static const char hlp_passwd[] =
"PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
" [--verify] <hexkeygrip>\n"
"\n"
"Change the passphrase/PIN for the key identified by keygrip in LINE. If\n"
"--preset is used then the new passphrase will be added to the cache.\n"
"If --verify is used the command asks for the passphrase and verifies\n"
"that the passphrase valid.\n";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int c;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *shadow_info = NULL;
char *passphrase = NULL;
char *pend;
int opt_preset, opt_verify;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_preset = has_option (line, "--preset");
cache_nonce = option_value (line, "--cache-nonce");
opt_verify = has_option (line, "--verify");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
ctrl->in_passwd++;
err = agent_key_from_file (ctrl,
opt_verify? NULL : cache_nonce,
ctrl->server_local->keydesc,
grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, &passphrase, NULL);
if (err)
;
else if (shadow_info)
{
log_error ("changing a smartcard PIN is not yet supported\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else if (opt_verify)
{
/* All done. */
if (passphrase)
{
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
else
{
char *newpass = NULL;
if (passwd_nonce)
newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
err = agent_protect_and_store (ctrl, s_skey, &newpass);
if (!err && passphrase)
{
/* A passphrase existed on the old key and the change was
successful. Return a nonce for that old passphrase to
let the caller try to unprotect the other subkeys with
the same key. */
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
if (newpass)
{
/* If we have a new passphrase (which might be empty) we
store it under a passwd nonce so that the caller may
send that nonce again to use it for another key. */
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
newpass, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
if (!err && opt_preset)
{
char hexgrip[40+1];
bin2hex(grip, 20, hexgrip);
err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass,
ctrl->cache_ttl_opt_preset);
}
xfree (newpass);
}
ctrl->in_passwd--;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
leave:
xfree (passphrase);
gcry_sexp_release (s_skey);
xfree (shadow_info);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, err);
}
static const char hlp_preset_passphrase[] =
"PRESET_PASSPHRASE [--inquire] [--restricted] \\\n"
" <string_or_keygrip> <timeout> [<hexstring>]\n"
"\n"
"Set the cached passphrase/PIN for the key identified by the keygrip\n"
"to passwd for the given time, where -1 means infinite and 0 means\n"
"the default (currently only a timeout of -1 is allowed, which means\n"
"to never expire it). If passwd is not provided, ask for it via the\n"
"pinentry module unless --inquire is passed in which case the passphrase\n"
"is retrieved from the client via a server inquire. The option\n"
"--restricted can be used to put the passphrase into the cache used\n"
"by restricted connections.";
static gpg_error_t
cmd_preset_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *grip_clear = NULL;
unsigned char *passphrase = NULL;
int ttl;
size_t len;
int opt_inquire;
int opt_restricted;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!opt.allow_preset_passphrase)
return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
opt_inquire = has_option (line, "--inquire");
opt_restricted = has_option (line, "--restricted");
line = skip_options (line);
grip_clear = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
return gpg_error (GPG_ERR_MISSING_VALUE);
*line = '\0';
line++;
while (*line && (*line == ' ' || *line == '\t'))
line++;
/* Currently, only infinite timeouts are allowed. */
ttl = -1;
if (line[0] != '-' || line[1] != '1')
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
line++;
line++;
while (!(*line != ' ' && *line != '\t'))
line++;
/* Syntax check the hexstring. */
len = 0;
rc = parse_hexstring (ctx, line, &len);
if (rc)
return rc;
line[len] = '\0';
/* If there is a passphrase, use it. Currently, a passphrase is
required. */
if (*line)
{
if (opt_inquire)
{
rc = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and passphrase specified");
goto leave;
}
/* Do in-place conversion. */
passphrase = line;
if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
}
else if (opt_inquire)
{
/* Note that the passphrase will be truncated at any null byte and the
* limit is 480 characters. */
size_t maxlen = 480;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!rc)
{
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
assuan_end_confidential (ctx);
}
}
else
rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
if (!rc)
{
int save_restricted = ctrl->restricted;
if (opt_restricted)
ctrl->restricted = 1;
rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl);
ctrl->restricted = save_restricted;
if (opt_inquire)
{
wipememory (passphrase, len);
xfree (passphrase);
}
}
leave:
return leave_cmd (ctx, rc);
}
static const char hlp_scd[] =
"SCD <commands to pass to the scdaemon>\n"
" \n"
"This is a general quote command to redirect everything to the\n"
"SCdaemon.";
static gpg_error_t
cmd_scd (assuan_context_t ctx, char *line)
{
int rc;
#ifdef BUILD_WITH_SCDAEMON
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl->restricted)
{
const char *argv[5];
int argc;
char *l;
l = xtrystrdup (line);
if (!l)
return gpg_error_from_syserror ();
argc = split_fields (l, argv, DIM (argv));
/* These commands are allowed. */
if ((argc >= 1 && !strcmp (argv[0], "SERIALNO"))
|| (argc == 2
&& !strcmp (argv[0], "GETINFO")
&& !strcmp (argv[1], "version"))
|| (argc == 2
&& !strcmp (argv[0], "GETATTR")
&& !strcmp (argv[1], "KEY-FPR"))
|| (argc == 2
&& !strcmp (argv[0], "KEYINFO")
&& !strcmp (argv[1], "--list=encr")))
xfree (l);
else
{
xfree (l);
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
}
}
/* All SCD prefixed commands may change a key. */
eventcounter.maybe_key_change++;
rc = divert_generic_cmd (ctrl, line, ctx);
#else
(void)ctx; (void)line;
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif
return rc;
}
static const char hlp_keywrap_key[] =
"KEYWRAP_KEY [--clear] <mode>\n"
"\n"
"Return a key to wrap another key. For now the key is returned\n"
"verbatim and thus makes not much sense because an eavesdropper on\n"
"the gpg-agent connection will see the key as well as the wrapped key.\n"
"However, this function may either be equipped with a public key\n"
"mechanism or not used at all if the key is a pre-shared key. In any\n"
"case wrapping the import and export of keys is a requirement for\n"
"certain cryptographic validations and thus useful. The key persists\n"
"until a RESET command but may be cleared using the option --clear.\n"
"\n"
"Supported modes are:\n"
" --import - Return a key to import a key into gpg-agent\n"
" --export - Return a key to export a key from gpg-agent";
static gpg_error_t
cmd_keywrap_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clearopt = has_option (line, "--clear");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
assuan_begin_confidential (ctx);
if (has_option (line, "--import"))
{
xfree (ctrl->server_local->import_key);
if (clearopt)
ctrl->server_local->import_key = NULL;
else if (!(ctrl->server_local->import_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->import_key,
KEYWRAP_KEYSIZE);
}
else if (has_option (line, "--export"))
{
xfree (ctrl->server_local->export_key);
if (clearopt)
ctrl->server_local->export_key = NULL;
else if (!(ctrl->server_local->export_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->export_key,
KEYWRAP_KEYSIZE);
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
assuan_end_confidential (ctx);
return leave_cmd (ctx, err);
}
static const char hlp_import_key[] =
- "IMPORT_KEY [--unattended] [--force] [--timestamp=<isodate>]\n"
+ "IMPORT_KEY [--unattended] [--force] [--mode1003] [--timestamp=<isodate>]\n"
" [<cache_nonce>]\n"
"\n"
"Import a secret key into the key store. The key is expected to be\n"
"encrypted using the current session's key wrapping key (cf. command\n"
"KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
"no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
"key data. The unwrapped key must be a canonical S-expression. The\n"
"option --unattended tries to import the key as-is without any\n"
"re-encryption. An existing key can be overwritten with --force.\n"
"If --timestamp is given its value is recorded as the key's creation\n"
"time; the value is expected in ISO format (e.g. \"20030316T120000\").";
static gpg_error_t
cmd_import_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_unattended;
time_t opt_timestamp;
- int force;
+ int mode1003, force;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *key = NULL;
size_t keylen, realkeylen;
char *passphrase = NULL;
unsigned char *finalkey = NULL;
size_t finalkeylen;
- unsigned char grip[20];
- gcry_sexp_t openpgp_sexp = NULL;
+ unsigned char grip1[KEYGRIP_LEN] = { 0 };
+ unsigned char grip2[KEYGRIP_LEN] = { 0 };
+ char hexgrip[2*KEYGRIP_LEN+1];
+ gcry_sexp_t keydata = NULL;
+ gcry_sexp_t skey1 = NULL; /* Part 1 of a composite key. */
+ gcry_sexp_t skey2 = NULL; /* Part 2 of a composite key. */
char *cache_nonce = NULL;
char *p;
const char *s;
+ const char *tag;
+ size_t taglen;
+ enum { KEYDATA_NORMAL,KEYDATA_PGP_TRANSFER, KEYDATA_COMPOSITE } keydata_type;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!ctrl->server_local->import_key)
{
err = gpg_error (GPG_ERR_MISSING_KEY);
goto leave;
}
opt_unattended = has_option (line, "--unattended");
+ mode1003 = has_option (line, "--mode1003");
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
{
err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
goto leave;
}
opt_timestamp = isotime2epoch (s+1);
if (opt_timestamp < 1)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
goto leave;
}
}
else
opt_timestamp = 0;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYDATA",
&wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (err)
goto leave;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
xfree (wrappedkey);
wrappedkey = NULL;
+ /* Check what kind of key we received. */
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
- err = keygrip_from_canon_sexp (key, realkeylen, grip);
+ err = gcry_sexp_sscan (&keydata, NULL, key, realkeylen);
if (err)
- {
- /* This might be due to an unsupported S-expression format.
- Check whether this is openpgp-private-key and trigger that
- import code. */
- if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
- {
- const char *tag;
- size_t taglen;
+ goto leave;
+ tag = gcry_sexp_nth_data (keydata, 0, &taglen);
+ if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
+ keydata_type = KEYDATA_PGP_TRANSFER;
+ else if (tag && taglen == 13 && !memcmp (tag, "composite-key", 13))
+ keydata_type = KEYDATA_COMPOSITE;
+ else if (gcry_pk_get_keygrip (keydata, grip1))
+ keydata_type = KEYDATA_NORMAL;
+ else /* get_keygrip failed */
+ {
+ err = gpg_error (GPG_ERR_INTERNAL);
+ goto leave;
+ }
- tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
- if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
- ;
- else
- {
- gcry_sexp_release (openpgp_sexp);
- openpgp_sexp = NULL;
- }
- }
- if (!openpgp_sexp)
- goto leave; /* Note that ERR is still set. */
+ if (opt_unattended && keydata_type != KEYDATA_PGP_TRANSFER)
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER,
+ "\"--unattended\" may only be used with OpenPGP keys");
+ goto leave;
}
- if (openpgp_sexp)
+ if (keydata_type == KEYDATA_PGP_TRANSFER && !mode1003)
{
/* In most cases the key is encrypted and thus the conversion
- function from the OpenPGP format to our internal format will
- ask for a passphrase. That passphrase will be returned and
- used to protect the key using the same code as for regular
- key import. */
-
+ * function from the OpenPGP format to our internal format will
+ * ask for a passphrase. That passphrase will be returned and
+ * used to protect the key using the same code as for regular
+ * key import. */
xfree (key);
key = NULL;
- err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
+ err = convert_from_openpgp (ctrl, keydata, force, grip1,
ctrl->server_local->keydesc, cache_nonce,
&key, opt_unattended? NULL : &passphrase);
if (err)
goto leave;
realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
if (passphrase)
{
log_assert (!opt_unattended);
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
}
}
- else if (opt_unattended)
+ else if (keydata_type == KEYDATA_COMPOSITE)
{
- err = set_error (GPG_ERR_ASS_PARAMETER,
- "\"--unattended\" may only be used with OpenPGP keys");
- goto leave;
+ if (!mode1003)
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER,
+ "\"--mode1003\" required for composite keys");
+ goto leave;
+ }
+ /* Split the key up. */
+ skey1 = gcry_sexp_nth (keydata, 1);
+ skey2 = gcry_sexp_nth (keydata, 2);
+ if (!skey1 || !skey2)
+ {
+ log_error ("broken composite key detected\n");
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ goto leave;
+ }
+ if (!gcry_pk_get_keygrip (skey1, grip1)
+ || !gcry_pk_get_keygrip (skey2, grip2))
+ {
+ log_error ("keygrip computation failed for"
+ " at least one part of composite key\n");
+ err = gpg_error (GPG_ERR_BAD_PUBKEY);
+ goto leave;
+ }
+
+ /* Test whether a key already exists. We do not yet implement
+ * FORCE because gpg is not yet able to correcly detect that
+ * both keys are on a smartcard (which is the only sensible way
+ * to allow overwriting both private keys. */
+ if (!agent_key_available (ctrl, grip1)
+ || !agent_key_available (ctrl, grip2))
+ {
+ log_error ("at least one part of a composite key already exists\n");
+ err = gpg_error (GPG_ERR_EEXIST);
+ goto leave;
+ }
+
+ /* Convert the key parts into canonical form and write them. */
+ xfree (key);
+ bin2hex (grip2, KEYGRIP_LEN, hexgrip);
+ err = make_canon_sexp (skey1, &key, &keylen);
+ if (!err)
+ err = agent_write_private_key (ctrl, grip1, key, keylen, 0, NULL,
+ NULL, NULL, opt_timestamp, hexgrip);
+ xfree (key); key = NULL;
+ bin2hex (grip1, KEYGRIP_LEN, hexgrip);
+ if (!err)
+ err = make_canon_sexp (skey2, &key, &keylen);
+ if (!err)
+ err = agent_write_private_key (ctrl, grip2, key, keylen, 0, NULL,
+ NULL, NULL, opt_timestamp, hexgrip);
+ if (err)
+ goto leave;
}
- else
+ else /* KEYDATA_NORMAL. */
{
- if (!force && !agent_key_available (ctrl, grip))
+ if (!force && !agent_key_available (ctrl, grip1))
err = gpg_error (GPG_ERR_EEXIST);
+ else if (mode1003)
+ {
+ /* (No passphrase used) */
+ }
else
{
char *prompt = xtryasprintf
(_("Please enter the passphrase to protect the "
"imported object within the %s system."), GNUPG_NAME);
if (!prompt)
err = gpg_error_from_syserror ();
else
err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
xfree (prompt);
}
if (err)
goto leave;
}
- if (passphrase)
+ if (keydata_type == KEYDATA_COMPOSITE)
+ ; /* Has already been written. */
+ else if (passphrase)
{
err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
ctrl->s2k_count);
if (!err)
- err = agent_write_private_key (ctrl, grip, finalkey, finalkeylen, force,
- NULL, NULL, NULL, opt_timestamp);
+ err = agent_write_private_key (ctrl, grip1,
+ finalkey, finalkeylen, force,
+ NULL, NULL, NULL, opt_timestamp, NULL);
}
else
- err = agent_write_private_key (ctrl, grip, key, realkeylen, force,
- NULL, NULL, NULL, opt_timestamp);
+ err = agent_write_private_key (ctrl, grip1, key, realkeylen, force,
+ NULL, NULL, NULL, opt_timestamp, NULL);
leave:
- gcry_sexp_release (openpgp_sexp);
+ gcry_sexp_release (skey1);
+ gcry_sexp_release (skey2);
+ gcry_sexp_release (keydata);
xfree (finalkey);
xfree (passphrase);
xfree (key);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_export_key[] =
"EXPORT_KEY [--cache-nonce=<nonce>] \\\n"
" [--openpgp|--mode1003] <hexkeygrip>\n"
"\n"
"Export a secret key from the key store. The key will be encrypted\n"
"using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
"using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n"
"prior to using this command. The function takes the keygrip as argument.\n"
"\n"
"If --openpgp is used, the secret key material will be exported in RFC 4880\n"
"compatible passphrase-protected form. In --mode1003 the secret key\n"
"is exported as s-expression as stored locally. Without those options,\n"
"the secret key material will be exported in the clear (after prompting\n"
"the user to unlock it, if needed).\n";
static gpg_error_t
cmd_export_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *key = NULL;
size_t keylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
int openpgp, mode1003;
char *cache_nonce;
char *passphrase = NULL;
unsigned char *shadow_info = NULL;
char *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
openpgp = has_option (line, "--openpgp");
mode1003 = has_option (line, "--mode1003");
if (mode1003)
openpgp = 0;
cache_nonce = option_value (line, "--cache-nonce");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
if (!ctrl->server_local->export_key)
{
err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
goto leave;
}
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (ctrl, grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Get the key from the file. With the openpgp flag we also ask for
* the passphrase so that we can use it to re-encrypt it. In
* mode1003 we return the key as-is. FIXME: if the key is still in
* OpenPGP-native mode we should first convert it to our internal
* protection. */
if (mode1003)
err = agent_raw_key_from_file (ctrl, grip, &s_skey, NULL);
else
err = agent_key_from_file (ctrl, cache_nonce,
ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
openpgp ? &passphrase : NULL, NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is on a smartcard. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
if (openpgp)
{
/* The openpgp option changes the key format into the OpenPGP
key transfer format. The result is already a padded
canonical S-expression. */
if (!passphrase)
{
err = agent_ask_new_passphrase
(ctrl, _("This key (or subkey) is not protected with a passphrase."
" Please enter a new passphrase to export it."),
&passphrase);
if (err)
goto leave;
}
err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
if (!err && passphrase)
{
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
}
}
else
{
/* Convert into a canonical S-expression and wrap that. */
err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
}
if (err)
goto leave;
gcry_sexp_release (s_skey);
s_skey = NULL;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
assuan_begin_confidential (ctx);
err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
assuan_end_confidential (ctx);
leave:
xfree (cache_nonce);
xfree (passphrase);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
xfree (key);
gcry_sexp_release (s_skey);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_delete_key[] =
"DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
"\n"
"Delete a secret key from the key store. If --force is used\n"
"and a loopback pinentry is allowed, the agent will not ask\n"
"the user for confirmation. If --stub-only is used the key will\n"
"only be deleted if it is a reference to a token.";
static gpg_error_t
cmd_delete_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int force, stub_only;
unsigned char grip[20];
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
stub_only = has_option (line, "--stub-only");
line = skip_options (line);
eventcounter.maybe_key_change++;
/* If the use of a loopback pinentry has been disabled, we assume
* that a silent deletion of keys shall also not be allowed. */
if (!opt.allow_loopback_pinentry)
force = 0;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
force, stub_only);
if (err)
goto leave;
leave:
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
#else
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
#endif
static const char hlp_keytocard[] =
"KEYTOCARD [--force] <hexgrip> <serialno> <keyref> [<timestamp> [<ecdh>]]\n"
"\n"
"TIMESTAMP is required for OpenPGP and defaults to the Epoch.\n"
"ECDH are the hexified ECDH parameters for OpenPGP.\n"
"SERIALNO is used for checking; use \"-\" to disable the check.";
static gpg_error_t
cmd_keytocard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int force;
gpg_error_t err = 0;
const char *argv[5];
int argc;
unsigned char grip[20];
const char *serialno, *keyref;
gcry_sexp_t s_skey = NULL;
unsigned char *keydata;
size_t keydatalen;
unsigned char *shadow_info = NULL;
time_t timestamp;
char *ecdh_params = NULL;
unsigned int ecdh_params_len;
unsigned int extralen1, extralen2;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
line = skip_options (line);
/* Need a copy of LINE, since it might inquire to the frontend which
resulted original buffer overwritten. */
line = xtrystrdup (line);
if (!line)
return gpg_error_from_syserror ();
argc = split_fields (line, argv, DIM (argv));
if (argc < 3)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (agent_key_available (ctrl, grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Note that checking of the s/n is currently not implemented but we
* want to provide a clean interface if we ever implement it. */
serialno = argv[1];
if (!strcmp (serialno, "-"))
serialno = NULL;
keyref = argv[2];
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL, &timestamp);
if (err)
goto leave;
if (shadow_info)
{
/* Key is already on a smartcard - we can't extract it. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
/* Default to the creation time as stored in the private key. The
* parameter is here so that gpg can make sure that the timestamp as
* used. It is also important for OpenPGP cards to allow computing
* of the fingerprint. Same goes for the ECDH params. */
if (argc > 3)
{
timestamp = isotime2epoch (argv[3]);
if (argc > 4)
{
size_t n;
err = parse_hexstring (ctx, argv[4], &n);
if (err)
goto leave; /* Badly formatted ecdh params. */
n /= 2;
if (n < 4)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "ecdh param too short");
goto leave;
}
ecdh_params_len = n;
ecdh_params = xtrymalloc (ecdh_params_len);
if (!ecdh_params)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (hex2bin (argv[4], ecdh_params, ecdh_params_len) < 0)
{
err = set_error (GPG_ERR_BUG, "hex2bin");
goto leave;
}
}
}
else if (timestamp == (time_t)(-1))
timestamp = isotime2epoch ("19700101T000000");
if (timestamp == (time_t)(-1))
{
err = gpg_error (GPG_ERR_INV_TIME);
goto leave;
}
/* Note: We can't use make_canon_sexp because we need to allocate a
* few extra bytes for our hack below. The 20 for extralen2
* accounts for the sexp length of ecdh_params. */
keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
extralen1 = 30;
extralen2 = ecdh_params? (20+20+ecdh_params_len) : 0;
keydata = xtrymalloc_secure (keydatalen + extralen1 + extralen2);
if (keydata == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
gcry_sexp_release (s_skey);
s_skey = NULL;
keydatalen--; /* Decrement for last '\0'. */
/* Hack to insert the timestamp "created-at" into the private key. */
snprintf (keydata+keydatalen-1, extralen1, KEYTOCARD_TIMESTAMP_FORMAT,
timestamp);
keydatalen += 10 + 19 - 1;
/* Hack to insert the timestamp "ecdh-params" into the private key. */
if (ecdh_params)
{
snprintf (keydata+keydatalen-1, extralen2, "(11:ecdh-params%u:",
ecdh_params_len);
keydatalen += strlen (keydata+keydatalen-1) -1;
memcpy (keydata+keydatalen, ecdh_params, ecdh_params_len);
keydatalen += ecdh_params_len;
memcpy (keydata+keydatalen, "))", 3);
keydatalen += 2;
}
err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen);
xfree (keydata);
leave:
xfree (line);
xfree (ecdh_params);
gcry_sexp_release (s_skey);
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_get_secret[] =
"GET_SECRET <key>\n"
"\n"
"Return the secret value stored under KEY\n";
static gpg_error_t
cmd_get_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *key;
char *value = NULL;
size_t valuelen;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
goto leave;
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
value = agent_get_cache (ctrl, key, CACHE_MODE_DATA);
if (!value)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
valuelen = percent_unescape_inplace (value, 0);
err = assuan_send_data (ctx, value, valuelen);
wipememory (value, valuelen);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_put_secret[] =
"PUT_SECRET [--clear] <key> <ttl> [<percent_escaped_value>]\n"
"\n"
"This commands stores a secret under KEY in gpg-agent's in-memory\n"
"cache. The TTL must be explicitly given by TTL and the options\n"
"from the configuration file are not used. The value is either given\n"
"percent-escaped as 3rd argument or if not given inquired by gpg-agent\n"
"using the keyword \"SECRET\".\n"
"The option --clear removes the secret from the cache."
"";
static gpg_error_t
cmd_put_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int opt_clear;
unsigned char *value = NULL;
size_t valuelen = 0;
size_t n;
char *p, *key, *ttlstr;
unsigned char *valstr;
int ttl;
char *string = NULL;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
opt_clear = has_option (line, "--clear");
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
ttlstr = NULL;
valstr = NULL;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
ttlstr = p;
p = strchr (ttlstr, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
valstr = p;
}
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl)))
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given");
goto leave;
}
if (valstr && opt_clear)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"value not expected with --clear");
goto leave;
}
if (valstr)
{
valuelen = percent_unescape_inplace (valstr, 0);
value = NULL;
}
else /* Inquire the value to store */
{
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET);
if (!err)
{
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "SECRET",
&value, &valuelen, MAXLEN_PUT_SECRET);
assuan_end_confidential (ctx);
}
if (err)
goto leave;
}
/* Our cache expects strings and thus we need to turn the buffer
* into a string. Instead of resorting to base64 encoding we use a
* special percent escaping which only quoted the Nul and the
* percent character. */
string = percent_data_escape (0, NULL, value? value : valstr, valuelen);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl);
leave:
if (string)
{
wipememory (string, strlen (string));
xfree (string);
}
if (value)
{
wipememory (value, valuelen);
xfree (value);
}
return leave_cmd (ctx, err);
}
static const char hlp_keytotpm[] =
"KEYTOTPM <hexstring_with_keygrip>\n"
"\n";
static gpg_error_t
cmd_keytotpm (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char grip[20];
gcry_sexp_t s_skey;
unsigned char *shadow_info = NULL;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (ctrl, grip))
{
err =gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL, NULL);
if (err)
{
xfree (shadow_info);
goto leave;
}
if (shadow_info)
{
/* Key is on a TPM or smartcard already. */
xfree (shadow_info);
gcry_sexp_release (s_skey);
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
err = divert_tpm2_writekey (ctrl, grip, s_skey);
gcry_sexp_release (s_skey);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getval[] =
"GETVAL <key>\n"
"\n"
"Return the value for KEY from the special environment as created by\n"
"PUTVAL.";
static gpg_error_t
cmd_getval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *p;
struct putval_item_s *vl;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list; vl; vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Got an entry. */
rc = assuan_send_data (ctx, vl->d+vl->off, vl->len);
else
return gpg_error (GPG_ERR_NO_DATA);
return leave_cmd (ctx, rc);
}
static const char hlp_putval[] =
"PUTVAL <key> [<percent_escaped_value>]\n"
"\n"
"The gpg-agent maintains a kind of environment which may be used to\n"
"store key/value pairs in it, so that they can be retrieved later.\n"
"This may be used by helper daemons to daemonize themself on\n"
"invocation and register them with gpg-agent. Callers of the\n"
"daemon's service may now first try connect to get the information\n"
"for that service from gpg-agent through the GETVAL command and then\n"
"try to connect to that daemon. Only if that fails they may start\n"
"an own instance of the service daemon. \n"
"\n"
"KEY is an arbitrary symbol with the same syntax rules as keys\n"
"for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
"corresponding value; they should be similar to the values of\n"
"envronment variables but gpg-agent does not enforce any\n"
"restrictions. If that value is not given any value under that KEY\n"
"is removed from this special environment.";
static gpg_error_t
cmd_putval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *value = NULL;
size_t valuelen = 0;
char *p;
struct putval_item_s *vl, *vlprev;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
value = p;
p = strchr (value, ' ');
if (p)
*p = 0;
valuelen = percent_plus_unescape_inplace (value, 0);
}
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Delete old entry. */
{
if (vlprev)
vlprev->next = vl->next;
else
putval_list = vl->next;
xfree (vl);
}
if (valuelen) /* Add entry. */
{
vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
if (!vl)
rc = gpg_error_from_syserror ();
else
{
vl->len = valuelen;
vl->off = strlen (key) + 1;
strcpy (vl->d, key);
memcpy (vl->d + vl->off, value, valuelen);
vl->next = putval_list;
putval_list = vl;
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_updatestartuptty[] =
"UPDATESTARTUPTTY\n"
"\n"
"Set startup TTY and X11 DISPLAY variables to the values of this\n"
"session. This command is useful to pull future pinentries to\n"
"another screen. It is only required because there is no way in the\n"
"ssh-agent protocol to convey this information.";
static gpg_error_t
cmd_updatestartuptty (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
session_env_t se;
char *lc_ctype = NULL;
char *lc_messages = NULL;
int iterator;
const char *name;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
se = session_env_new ();
if (!se)
err = gpg_error_from_syserror ();
iterator = 0;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
const char *value = session_env_getenv (ctrl->session_env, name);
if (value)
err = session_env_setenv (se, name, value);
}
if (!err && ctrl->lc_ctype)
if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && ctrl->lc_messages)
if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
err = gpg_error_from_syserror ();
if (err)
{
session_env_release (se);
xfree (lc_ctype);
xfree (lc_messages);
}
else
{
session_env_release (opt.startup_env);
opt.startup_env = se;
xfree (opt.startup_lc_ctype);
opt.startup_lc_ctype = lc_ctype;
xfree (opt.startup_lc_messages);
opt.startup_lc_messages = lc_messages;
}
return err;
}
static const char hlp_killagent[] =
"KILLAGENT\n"
"\n"
"Stop the agent.";
static gpg_error_t
cmd_killagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloadagent[] =
"RELOADAGENT\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
agent_sighup_action ();
return 0;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" ssh_socket_name - Return the name of the ssh socket.\n"
" scd_running - Return OK if the SCdaemon is already running.\n"
" s2k_time - Return the time in milliseconds required for S2K.\n"
" s2k_count - Return the standard S2K count.\n"
" s2k_count_cal - Return the calibrated S2K count.\n"
" std_env_names - List the names of the standard environment.\n"
" std_session_env - List the standard session environment.\n"
" std_startup_env - List the standard startup environment.\n"
" getenv NAME - Return value of envvar NAME.\n"
" connections - Return number of active connections.\n"
" jent_active - Returns OK if Libgcrypt's JENT is active.\n"
" ephemeral - Returns OK if the connection is in ephemeral mode.\n"
" restricted - Returns OK if the connection is in restricted mode.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "s2k_count"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "ephemeral"))
{
rc = ctrl->ephemeral_mode? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "restricted"))
{
rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (ctrl->restricted)
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All sub-commands below are not allowed in restricted mode. */
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_agent_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "ssh_socket_name"))
{
const char *s = get_agent_ssh_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "scd_running"))
{
rc = agent_daemon_check_running (DAEMON_SCD)? 0:gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "std_env_names"))
{
int iterator;
const char *name;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
rc = assuan_send_data (ctx, name, strlen (name)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
break;
}
}
else if (!strcmp (line, "std_session_env")
|| !strcmp (line, "std_startup_env"))
{
int iterator;
const char *name, *value;
char *string;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
value = session_env_getenv_or_default
(line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
if (value)
{
string = xtryasprintf ("%s=%s", name, value);
if (!string)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_send_data (ctx, string, strlen (string)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (rc)
break;
}
}
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
rc = assuan_send_data (ctx, s, strlen (s));
}
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d",
get_agent_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "jent_active"))
{
char *buf;
const char *fields[5];
buf = gcry_get_config (0, "rng-type");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 5
&& atoi (fields[4]) > 0)
rc = 0;
else
rc = gpg_error (GPG_ERR_FALSE);
gcry_free (buf);
}
else if (!strcmp (line, "s2k_count_cal"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "s2k_time"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
/* This function is called by Libassuan to parse the OPTION command.
It has been registered similar to the other Assuan commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "agent-awareness"))
{
/* The value is a version string telling us of which agent
version the caller is aware of. */
ctrl->server_local->allow_fully_canceled =
gnupg_compare_version (value, "2.1.0");
}
else if (!strcmp (key, "ephemeral"))
{
ctrl->ephemeral_mode = *value? atoi (value) : 0;
}
else if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All options below are not allowed in restricted mode. */
else if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (ctrl->session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (ctrl->session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = xtrystrdup (value);
if (!ctrl->lc_ctype)
return out_of_core ();
}
else if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "use-cache-for-signing"))
ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
else if (!strcmp (key, "allow-pinentry-notify"))
ctrl->server_local->allow_pinentry_notify = 1;
else if (!strcmp (key, "pinentry-mode"))
{
int tmp = parse_pinentry_mode (value);
if (tmp == -1)
err = gpg_error (GPG_ERR_INV_VALUE);
else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
ctrl->pinentry_mode = tmp;
}
else if (!strcmp (key, "cache-ttl-opt-preset"))
{
ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
}
else if (!strcmp (key, "s2k-count"))
{
ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
if (ctrl->s2k_count && ctrl->s2k_count < 65536)
{
ctrl->s2k_count = 0;
}
}
else if (!strcmp (key, "pretend-request-origin"))
{
log_assert (!ctrl->restricted);
switch (parse_request_origin (value))
{
case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break;
case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break;
case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
/* Better pretend to be remote in case of a bad value. */
ctrl->restricted = 1;
break;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* Called by libassuan after all commands. ERR is the error from the
last assuan operation and not the one returned from the command. */
static void
post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)err;
/* Switch off any I/O monitor controlled logging pausing. */
ctrl->server_local->pause_io_logging = 0;
}
/* This function is called by libassuan for all I/O. We use it here
to disable logging for the GETEVENTCOUNTER commands. This is so
that the debug output won't get cluttered by this primitive
command. */
static unsigned int
io_monitor (assuan_context_t ctx, void *hook, int direction,
const char *line, size_t linelen)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) hook;
/* We want to suppress all Assuan log messages for connections from
* self. However, assuan_get_pid works only after
* assuan_accept. Now, assuan_accept already logs a line ending with
* the process id. We use this hack here to get the peers pid so
* that we can compare it to our pid. We should add an assuan
* function to return the pid for a file descriptor and use that to
* detect connections to self. */
if (ctx && !ctrl->server_local->greeting_seen
&& direction == ASSUAN_IO_TO_PEER)
{
ctrl->server_local->greeting_seen = 1;
if (linelen > 32
&& !strncmp (line, "OK Pleased to meet you, process ", 32)
&& strtoul (line+32, NULL, 10) == getpid ())
return ASSUAN_IO_MONITOR_NOLOG;
}
/* Do not log self-connections. This makes the log cleaner because
* we won't see the check-our-own-socket calls. */
if (ctx && ctrl->server_local->connect_from_self)
return ASSUAN_IO_MONITOR_NOLOG;
/* Note that we only check for the uppercase name. This allows the user to
see the logging for debugging if using a non-upercase command
name. */
if (ctx && direction == ASSUAN_IO_FROM_PEER
&& linelen >= 15
&& !strncmp (line, "GETEVENTCOUNTER", 15)
&& (linelen == 15 || spacep (line+15)))
{
ctrl->server_local->pause_io_logging = 1;
}
return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "GET_PASSPHRASE"))
{
if (!strcmp (cmdopt, "repeat"))
return 1;
if (!strcmp (cmdopt, "newsymkey"))
return 1;
}
else if (!strcmp (cmd, "EXPORT_KEY"))
{
if (!strcmp (cmdopt, "mode1003"))
return 1;
}
+ else if (!strcmp (cmd, "IMPORT_KEY"))
+ {
+ if (!strcmp (cmdopt, "mode1003"))
+ return 1;
+ }
return 0;
}
/* Tell Libassuan about our commands. Also register the other Assuan
handlers. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter },
{ "ISTRUSTED", cmd_istrusted, hlp_istrusted },
{ "HAVEKEY", cmd_havekey, hlp_havekey },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ "SIGKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc },
{ "SETHASH", cmd_sethash, hlp_sethash },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase },
{ "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase },
{ "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase },
{ "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation },
{ "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted },
{ "MARKTRUSTED", cmd_marktrusted, hlp_martrusted },
{ "LEARN", cmd_learn, hlp_learn },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "SCD", cmd_scd, hlp_scd },
{ "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key },
{ "IMPORT_KEY", cmd_import_key, hlp_import_key },
{ "EXPORT_KEY", cmd_export_key, hlp_export_key },
{ "DELETE_KEY", cmd_delete_key, hlp_delete_key },
{ "GET_SECRET", cmd_get_secret, hlp_get_secret },
{ "PUT_SECRET", cmd_put_secret, hlp_put_secret },
{ "GETVAL", cmd_getval, hlp_getval },
{ "PUTVAL", cmd_putval, hlp_putval },
{ "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },
{ "KILLAGENT", cmd_killagent, hlp_killagent },
{ "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "KEYTOCARD", cmd_keytocard, hlp_keytocard },
{ "KEYTOTPM", cmd_keytotpm, hlp_keytotpm },
{ "KEYATTR", cmd_keyattr, hlp_keyattr },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_register_post_cmd_notify (ctx, post_cmd_notify);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If LISTEN_FD and FD is given as -1, this is a
simple piper server, otherwise it is a regular server. CTRL is the
control structure for this connection; it has only the basic
initialization. */
void
start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
{
int rc;
assuan_context_t ctx = NULL;
if (ctrl->restricted)
{
if (agent_copy_startup_env (ctrl))
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc));
agent_exit (2);
}
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else if (listen_fd != GNUPG_INVALID_FD)
{
rc = assuan_init_socket_server (ctx, listen_fd, 0);
/* FIXME: Need to call assuan_sock_set_nonce for Windows. But
this branch is currently not used. */
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->assuan_ctx = ctx;
ctrl->server_local->use_cache_for_signing = 1;
ctrl->digest.data = NULL;
ctrl->digest.raw_value = 0;
ctrl->digest.is_pss = 0;
assuan_set_io_monitor (ctx, io_monitor, NULL);
agent_set_progress_cb (progress_cb, ctrl);
for (;;)
{
assuan_peercred_t client_creds; /* Note: Points into CTX. */
pid_t pid;
rc = assuan_accept (ctx);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_get_peercred (ctx, &client_creds);
if (rc)
{
/* Note that on Windows we don't get the peer credentials
* and thus we silence the error. */
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
;
#ifdef HAVE_W32_SYSTEM
else if (gpg_err_code (rc) == GPG_ERR_ASS_GENERAL)
;
#endif
else
log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc));
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
}
else
{
#ifdef HAVE_W32_SYSTEM
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
#else
pid = client_creds->pid;
ctrl->client_uid = client_creds->uid;
#endif
}
ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid;
ctrl->server_local->connect_from_self = (pid == getpid ());
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Clear the keyinfo cache. */
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
/* Reset the nonce caches. */
clear_nonce_cache (ctrl);
/* Reset the SCD if needed. */
agent_reset_daemon (ctrl);
/* Reset the pinentry (in case of popup messages). */
agent_reset_query (ctrl);
/* Cleanup. */
assuan_release (ctx);
xfree (ctrl->server_local->keydesc);
xfree (ctrl->server_local->import_key);
xfree (ctrl->server_local->export_key);
if (ctrl->server_local->stopme)
agent_exit (0);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
/* Helper for the pinentry loopback mode. It merely passes the
parameters on to the client. */
gpg_error_t
pinentry_loopback(ctrl_t ctrl, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length)
{
gpg_error_t rc;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
if (rc)
return rc;
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
assuan_end_confidential (ctx);
return rc;
}
/* Helper for the pinentry loopback mode to ask confirmation
or just to show message. */
gpg_error_t
pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok)
{
gpg_error_t err = 0;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (desc)
err = print_assuan_status (ctx, "SETDESC", "%s", desc);
if (!err && ok)
err = print_assuan_status (ctx, "SETOK", "%s", ok);
if (!err && notok)
err = print_assuan_status (ctx, "SETNOTOK", "%s", notok);
if (!err)
err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0",
NULL, NULL, 0);
return err;
}
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
index 420dbb464..2dd5f8b04 100644
--- a/agent/cvt-openpgp.c
+++ b/agent/cvt-openpgp.c
@@ -1,1523 +1,1523 @@
/* cvt-openpgp.c - Convert an OpenPGP key to our internal format.
* Copyright (C) 1998-2002, 2006, 2009, 2010 Free Software Foundation, Inc.
* Copyright (C) 2013, 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "agent.h"
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/host2net.h"
/* Helper to pass data via the callback to do_unprotect. */
struct try_do_unprotect_arg_s
{
int is_v4;
int is_protected;
int pubkey_algo;
const char *curve;
int protect_algo;
char *iv;
int ivlen;
int s2k_mode;
int s2k_algo;
byte *s2k_salt;
u32 s2k_count;
u16 desired_csum;
gcry_mpi_t *skey;
size_t skeysize;
int skeyidx;
gcry_sexp_t *r_key;
};
/* Compute the keygrip from the public key and store it at GRIP. */
static gpg_error_t
get_keygrip (int pubkey_algo, const char *curve, gcry_mpi_t *pkey,
unsigned char *grip)
{
gpg_error_t err;
gcry_sexp_t s_pkey = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2], pkey[3]);
break;
case GCRY_PK_ELG:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
break;
case GCRY_PK_RSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
break;
case GCRY_PK_ECC:
if (!curve)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
{
const char *format;
if (!strcmp (curve, "Ed25519"))
format = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))";
else if (!strcmp (curve, "Curve25519"))
format = "(public-key(ecc(curve %s)(flags djb-tweak)(q%m)))";
else
format = "(public-key(ecc(curve %s)(q%m)))";
err = gcry_sexp_build (&s_pkey, NULL, format, curve, pkey[0]);
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
err = gpg_error (GPG_ERR_INTERNAL);
gcry_sexp_release (s_pkey);
return err;
}
/* Convert a secret key given as algorithm id and an array of key
parameters into our s-expression based format. Note that
PUBKEY_ALGO has an gcrypt algorithm number. */
static gpg_error_t
convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
const char *curve)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_skey, NULL,
"(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))",
skey[0], skey[1], skey[2], skey[3], skey[4]);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build (&s_skey, NULL,
"(private-key(elg(p%m)(g%m)(y%m)(x%m)))",
skey[0], skey[1], skey[2], skey[3]);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S:
err = gcry_sexp_build (&s_skey, NULL,
"(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
skey[0], skey[1], skey[2], skey[3], skey[4],
skey[5]);
break;
case GCRY_PK_ECC:
if (!curve)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
{
const char *format;
if (!strcmp (curve, "Ed25519"))
/* Do not store the OID as name but the real name and the
EdDSA flag. */
format = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%m)))";
else if (!strcmp (curve, "Curve25519"))
format = "(private-key(ecc(curve %s)(flags djb-tweak)(q%m)(d%m)))";
else
format = "(private-key(ecc(curve %s)(q%m)(d%m)))";
err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], skey[1]);
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err)
*r_key = s_skey;
return err;
}
/* Convert a secret key given as algorithm id, an array of key
parameters, and an S-expression of the original OpenPGP transfer
key into our s-expression based format. This is a variant of
convert_secret_key which is used for the openpgp-native protection
mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */
static gpg_error_t
convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey,
const char *curve, gcry_sexp_t transfer_key)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
switch (pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], skey[2], skey[3], transfer_key);
break;
case GCRY_PK_ELG:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(elg(p%m)(g%m)(y%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], skey[2], transfer_key);
break;
case GCRY_PK_RSA:
err = gcry_sexp_build
(&s_skey, NULL,
"(protected-private-key(rsa(n%m)(e%m)"
"(protected openpgp-native%S)))",
skey[0], skey[1], transfer_key );
break;
case GCRY_PK_ECC:
if (!curve)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
{
const char *format;
if (!strcmp (curve, "Ed25519"))
/* Do not store the OID as name but the real name and the
EdDSA flag. */
format = "(protected-private-key(ecc(curve %s)(flags eddsa)(q%m)"
"(protected openpgp-native%S)))";
else if (!strcmp (curve, "Curve25519"))
format = "(protected-private-key(ecc(curve %s)(flags djb-tweak)(q%m)"
"(protected openpgp-native%S)))";
else
format = "(protected-private-key(ecc(curve %s)(q%m)"
"(protected openpgp-native%S)))";
err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], transfer_key);
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (!err)
*r_key = s_skey;
return err;
}
/* Hash the passphrase and set the key. */
static gpg_error_t
hash_passphrase_and_set_key (const char *passphrase,
gcry_cipher_hd_t hd, int protect_algo,
int s2k_mode, int s2k_algo,
byte *s2k_salt, u32 s2k_count)
{
gpg_error_t err;
unsigned char *key;
size_t keylen;
keylen = gcry_cipher_get_algo_keylen (protect_algo);
if (!keylen)
return gpg_error (GPG_ERR_INTERNAL);
key = xtrymalloc_secure (keylen);
if (!key)
return gpg_error_from_syserror ();
err = s2k_hash_passphrase (passphrase,
s2k_algo, s2k_mode, s2k_salt, s2k_count,
key, keylen);
if (!err)
err = gcry_cipher_setkey (hd, key, keylen);
xfree (key);
return err;
}
static u16
checksum (const unsigned char *p, unsigned int n)
{
u16 a;
for (a=0; n; n-- )
a += *p++;
return a;
}
/* Return the number of expected key parameters. */
static void
get_npkey_nskey (int pubkey_algo, size_t *npkey, size_t *nskey)
{
switch (pubkey_algo)
{
case GCRY_PK_RSA: *npkey = 2; *nskey = 6; break;
case GCRY_PK_ELG: *npkey = 3; *nskey = 4; break;
case GCRY_PK_ELG_E: *npkey = 3; *nskey = 4; break;
case GCRY_PK_DSA: *npkey = 4; *nskey = 5; break;
case GCRY_PK_ECC: *npkey = 1; *nskey = 2; break;
default: *npkey = 0; *nskey = 0; break;
}
}
/* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number.
On success R_NPKEY and R_NSKEY receive the number or parameters for
the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of
SKEY. */
static int
prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize,
int s2k_mode,
unsigned int *r_npkey, unsigned int *r_nskey,
unsigned int *r_skeylen)
{
size_t npkey, nskey, skeylen;
int i;
/* Count the actual number of MPIs is in the array and set the
remainder to NULL for easier processing later on. */
for (skeylen = 0; skey[skeylen]; skeylen++)
;
for (i=skeylen; i < skeysize; i++)
skey[i] = NULL;
/* Check some args. */
if (s2k_mode == 1001)
{
/* Stub key. */
log_info (_("secret key parts are not available\n"));
return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
}
if (gcry_pk_test_algo (pubkey_algo))
{
log_info (_("public key algorithm %d (%s) is not supported\n"),
pubkey_algo, gcry_pk_algo_name (pubkey_algo));
return gpg_error (GPG_ERR_PUBKEY_ALGO);
}
/* Get properties of the public key algorithm and do some
consistency checks. Note that we need at least NPKEY+1 elements
in the SKEY array. */
get_npkey_nskey (pubkey_algo, &npkey, &nskey);
if (!npkey || !nskey || npkey >= nskey)
return gpg_error (GPG_ERR_INTERNAL);
if (skeylen <= npkey)
return gpg_error (GPG_ERR_MISSING_VALUE);
if (nskey+1 >= skeysize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
/* Check that the public key parameters are all available and not
encrypted. */
for (i=0; i < npkey; i++)
{
if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
return gpg_error (GPG_ERR_BAD_SECKEY);
}
if (r_npkey)
*r_npkey = npkey;
if (r_nskey)
*r_nskey = nskey;
if (r_skeylen)
*r_skeylen = skeylen;
return 0;
}
/* Scan octet string in the PGP format (length-in-two-octet octets) */
static int
scan_pgp_format (gcry_mpi_t *r_mpi, int pubkey_algo,
const unsigned char *buffer,
size_t buflen, size_t *r_nbytes)
{
/* Using gcry_mpi_scan with GCRYMPI_FLAG_PGP can be used if it is
MPI, but it will be "normalized" removing leading zeros. */
unsigned int nbits, nbytes;
if (pubkey_algo != GCRY_PK_ECC)
return gcry_mpi_scan (r_mpi, GCRYMPI_FMT_PGP, buffer, buflen, r_nbytes);
/* It's ECC, where we use SOS. */
if (buflen < 2)
return GPG_ERR_INV_OBJ;
nbits = (buffer[0] << 8) | buffer[1];
if (nbits >= 16384)
return GPG_ERR_INV_OBJ;
nbytes = (nbits + 7) / 8;
if (buflen < nbytes + 2)
return GPG_ERR_INV_OBJ;
*r_nbytes = nbytes + 2;
*r_mpi = gcry_mpi_set_opaque_copy (NULL, buffer+2, nbits);
return 0;
}
/* Note that this function modifies SKEY. SKEYSIZE is the allocated
size of the array including the NULL item; this is used for a
bounds check. On success a converted key is stored at R_KEY. */
static int
do_unprotect (const char *passphrase,
int pkt_version, int pubkey_algo, int is_protected,
const char *curve, gcry_mpi_t *skey, size_t skeysize,
int protect_algo, void *protect_iv, size_t protect_ivlen,
int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
u16 desired_csum, gcry_sexp_t *r_key)
{
gpg_error_t err;
unsigned int npkey, nskey, skeylen;
gcry_cipher_hd_t cipher_hd = NULL;
u16 actual_csum;
size_t nbytes;
int i;
gcry_mpi_t tmpmpi;
*r_key = NULL;
err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode,
&npkey, &nskey, &skeylen);
if (err)
return err;
/* Check whether SKEY is at all protected. If it is not protected
merely verify the checksum. */
if (!is_protected)
{
actual_csum = 0;
for (i=npkey; i < nskey; i++)
{
unsigned char *buffer;
if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
return gpg_error (GPG_ERR_BAD_SECKEY);
if (gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
buffer = gcry_mpi_get_opaque (skey[i], &nbits);
nbytes = (nbits+7)/8;
nbits = nbytes * 8;
if (*buffer)
if (nbits >= 8 && !(*buffer & 0x80))
if (--nbits >= 7 && !(*buffer & 0x40))
if (--nbits >= 6 && !(*buffer & 0x20))
if (--nbits >= 5 && !(*buffer & 0x10))
if (--nbits >= 4 && !(*buffer & 0x08))
if (--nbits >= 3 && !(*buffer & 0x04))
if (--nbits >= 2 && !(*buffer & 0x02))
if (--nbits >= 1 && !(*buffer & 0x01))
--nbits;
actual_csum += (nbits >> 8);
actual_csum += (nbits & 0xff);
actual_csum += checksum (buffer, nbytes);
}
else
{
err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes,
skey[i]);
if (err)
return err;
actual_csum += checksum (buffer, nbytes);
xfree (buffer);
}
}
if (actual_csum != desired_csum)
return gpg_error (GPG_ERR_CHECKSUM);
goto do_convert;
}
if (gcry_cipher_test_algo (protect_algo))
{
/* The algorithm numbers are Libgcrypt numbers but fortunately
the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
numbers. */
log_info (_("protection algorithm %d (%s) is not supported\n"),
protect_algo, gnupg_cipher_algo_name (protect_algo));
return gpg_error (GPG_ERR_CIPHER_ALGO);
}
if (gcry_md_test_algo (s2k_algo))
{
log_info (_("protection hash algorithm %d (%s) is not supported\n"),
s2k_algo, gcry_md_algo_name (s2k_algo));
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
err = gcry_cipher_open (&cipher_hd, protect_algo,
GCRY_CIPHER_MODE_CFB,
(GCRY_CIPHER_SECURE
| (protect_algo >= 100 ?
0 : GCRY_CIPHER_ENABLE_SYNC)));
if (err)
{
log_error ("failed to open cipher_algo %d: %s\n",
protect_algo, gpg_strerror (err));
return err;
}
err = hash_passphrase_and_set_key (passphrase, cipher_hd, protect_algo,
s2k_mode, s2k_algo, s2k_salt, s2k_count);
if (err)
{
gcry_cipher_close (cipher_hd);
return err;
}
gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen);
actual_csum = 0;
if (pkt_version >= 4)
{
int ndata;
unsigned int ndatabits;
const unsigned char *p;
unsigned char *data;
u16 csum_pgp7 = 0;
gcry_mpi_t skey_encrypted = skey[npkey];
if (!gcry_mpi_get_flag (skey_encrypted, GCRYMPI_FLAG_USER1))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
p = gcry_mpi_get_opaque (skey_encrypted, &ndatabits);
ndata = (ndatabits+7)/8;
if (ndata > 1)
csum_pgp7 = buf16_to_u16 (p+ndata-2);
data = xtrymalloc_secure (ndata);
if (!data)
{
err = gpg_error_from_syserror ();
gcry_cipher_close (cipher_hd);
return err;
}
gcry_cipher_decrypt (cipher_hd, data, ndata, p, ndata);
p = data;
if (is_protected == 2)
{
/* This is the new SHA1 checksum method to detect tampering
with the key as used by the Klima/Rosa attack. */
desired_csum = 0;
actual_csum = 1; /* Default to bad checksum. */
if (ndata < 20)
log_error ("not enough bytes for SHA-1 checksum\n");
else
{
gcry_md_hd_t h;
if (gcry_md_open (&h, GCRY_MD_SHA1, 1))
BUG(); /* Algo not available. */
gcry_md_write (h, data, ndata - 20);
gcry_md_final (h);
if (!memcmp (gcry_md_read (h, GCRY_MD_SHA1), data+ndata-20, 20))
actual_csum = 0; /* Digest does match. */
gcry_md_close (h);
}
}
else
{
/* Old 16 bit checksum method. */
if (ndata < 2)
{
log_error ("not enough bytes for checksum\n");
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
}
else
{
desired_csum = buf16_to_u16 (data+ndata-2);
actual_csum = checksum (data, ndata-2);
if (desired_csum != actual_csum)
{
/* This is a PGP 7.0.0 workaround */
desired_csum = csum_pgp7; /* Take the encrypted one. */
}
}
}
/* Better check it here. Otherwise the gcry_mpi_scan would fail
because the length may have an arbitrary value. */
if (desired_csum == actual_csum)
{
for (i = npkey; i < nskey; i++)
{
if (scan_pgp_format (&tmpmpi, pubkey_algo, p, ndata, &nbytes))
break;
skey[i] = tmpmpi;
ndata -= nbytes;
p += nbytes;
}
if (i == nskey)
{
skey[nskey] = NULL;
skeylen = nskey;
gcry_mpi_release (skey_encrypted);
log_assert (skeylen <= skeysize);
/* Note: at this point NDATA should be 2 for a simple
checksum or 20 for the sha1 digest. */
}
else
{
/* Checksum was okay, but not correctly decrypted. */
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
/* Recover encrypted SKEY. */
for (--i; i >= npkey; i--)
{
gcry_mpi_release (skey[i]);
skey[i] = NULL;
}
skey[npkey] = skey_encrypted;
}
}
xfree(data);
}
else /* Packet version <= 3. */
{
unsigned char *buffer;
gcry_mpi_t skey_tmpmpi[10];
log_assert (nskey - npkey <= 10);
for (i = npkey; i < nskey; i++)
{
const unsigned char *p;
size_t ndata;
unsigned int ndatabits;
if (!skey[i] || !gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
p = gcry_mpi_get_opaque (skey[i], &ndatabits);
ndata = (ndatabits+7)/8;
if (!(ndata >= 2) || !(ndata == (buf16_to_ushort (p) + 7)/8 + 2))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
buffer = xtrymalloc_secure (ndata);
if (!buffer)
{
err = gpg_error_from_syserror ();
gcry_cipher_close (cipher_hd);
return err;
}
gcry_cipher_sync (cipher_hd);
buffer[0] = p[0];
buffer[1] = p[1];
gcry_cipher_decrypt (cipher_hd, buffer+2, ndata-2, p+2, ndata-2);
actual_csum += checksum (buffer, ndata);
err = scan_pgp_format (&tmpmpi, pubkey_algo, buffer, ndata, &nbytes);
xfree (buffer);
if (err)
break;
skey_tmpmpi[i - npkey] = tmpmpi;
}
if (i == nskey)
{
for (i = npkey; i < nskey; i++)
{
gcry_mpi_release (skey[i]);
skey[i] = skey_tmpmpi[i - npkey];
}
}
else
{
/* Checksum was okay, but not correctly decrypted. */
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
for (--i; i >= npkey; i--)
gcry_mpi_release (skey_tmpmpi[i - npkey]);
}
}
gcry_cipher_close (cipher_hd);
/* Now let's see whether we have used the correct passphrase. */
if (actual_csum != desired_csum)
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
do_convert:
if (nskey != skeylen)
err = gpg_error (GPG_ERR_BAD_SECKEY);
else
err = convert_secret_key (r_key, pubkey_algo, skey, curve);
if (err)
return err;
/* The checksum may fail, thus we also check the key itself. */
err = gcry_pk_testkey (*r_key);
if (err)
{
gcry_sexp_release (*r_key);
*r_key = NULL;
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
return 0;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
try_do_unprotect_cb (struct pin_entry_info_s *pi)
{
gpg_error_t err;
struct try_do_unprotect_arg_s *arg = pi->check_cb_arg;
err = do_unprotect (pi->pin,
arg->is_v4? 4:3,
arg->pubkey_algo, arg->is_protected,
arg->curve,
arg->skey, arg->skeysize,
arg->protect_algo, arg->iv, arg->ivlen,
arg->s2k_mode, arg->s2k_algo,
arg->s2k_salt, arg->s2k_count,
arg->desired_csum, arg->r_key);
/* SKEY may be modified now, thus we need to re-compute SKEYIDX. */
for (arg->skeyidx = 0; (arg->skeyidx < arg->skeysize
&& arg->skey[arg->skeyidx]); arg->skeyidx++)
;
return err;
}
/* See convert_from_openpgp for the core of the description. This
function adds an optional PASSPHRASE argument and uses this to
silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be
NULL in this mode. */
static gpg_error_t
convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
unsigned char *grip, const char *prompt,
const char *cache_nonce, const char *passphrase,
unsigned char **r_key, char **r_passphrase)
{
gpg_error_t err;
int unattended;
int from_native;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
const char *value;
size_t valuelen;
char *string;
int idx;
int is_v4, is_protected;
int pubkey_algo;
int protect_algo = 0;
char iv[16];
int ivlen = 0;
int s2k_mode = 0;
int s2k_algo = 0;
byte s2k_salt[8];
u32 s2k_count = 0;
size_t npkey, nskey;
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
char *curve = NULL;
u16 desired_csum;
int skeyidx = 0;
gcry_sexp_t s_skey = NULL;
*r_key = NULL;
if (r_passphrase)
*r_passphrase = NULL;
unattended = !r_passphrase;
from_native = (!cache_nonce && passphrase && !r_passphrase);
top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
if (!top_list)
goto bad_seckey;
list = gcry_sexp_find_token (top_list, "version", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value || valuelen != 1
|| !(value[0] == '3' || value[0] == '4' || value[0] == '5'))
goto bad_seckey;
is_v4 = (value[0] == '4' || value[0] == '5');
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "protection", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value)
goto bad_seckey;
if (valuelen == 4 && !memcmp (value, "sha1", 4))
is_protected = 2;
else if (valuelen == 3 && !memcmp (value, "sum", 3))
is_protected = 1;
else if (valuelen == 4 && !memcmp (value, "none", 4))
is_protected = 0;
else
goto bad_seckey;
if (is_protected)
{
string = gcry_sexp_nth_string (list, 2);
if (!string)
goto bad_seckey;
protect_algo = gcry_cipher_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 3, &valuelen);
if (!value || !valuelen || valuelen > sizeof iv)
goto bad_seckey;
memcpy (iv, value, valuelen);
ivlen = valuelen;
string = gcry_sexp_nth_string (list, 4);
if (!string)
goto bad_seckey;
s2k_mode = strtol (string, NULL, 10);
xfree (string);
string = gcry_sexp_nth_string (list, 5);
if (!string)
goto bad_seckey;
s2k_algo = gcry_md_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 6, &valuelen);
if (!value || !valuelen || valuelen > sizeof s2k_salt)
goto bad_seckey;
memcpy (s2k_salt, value, valuelen);
string = gcry_sexp_nth_string (list, 7);
if (!string)
goto bad_seckey;
s2k_count = strtoul (string, NULL, 10);
xfree (string);
}
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "algo", 0);
if (!list)
goto bad_seckey;
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
pubkey_algo = gcry_pk_map_name (string);
xfree (string);
get_npkey_nskey (pubkey_algo, &npkey, &nskey);
if (!npkey || !nskey || npkey >= nskey)
goto bad_seckey;
if (npkey == 1) /* This is ECC */
{
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "curve", 0);
if (!list)
goto bad_seckey;
curve = gcry_sexp_nth_string (list, 1);
if (!curve)
goto bad_seckey;
}
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "skey", 0);
if (!list)
goto bad_seckey;
for (idx=0;;)
{
int is_enc;
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value && skeyidx >= npkey)
break; /* Ready. */
/* Check for too many parameters. Note that depending on the
protection mode and version number we may see less than NSKEY
(but at least NPKEY+1) parameters. */
if (idx >= 2*nskey)
goto bad_seckey;
if (skeyidx >= DIM (skey)-1)
goto bad_seckey;
if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
goto bad_seckey;
is_enc = (value[0] == 'e');
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value || !valuelen)
goto bad_seckey;
if (is_enc || npkey == 1 /* This is ECC */)
{
skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8);
if (!skey[skeyidx])
goto outofmem;
if (is_enc)
/* Encrypted parameters need to have a USER1 flag. */
gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1);
}
else
{
if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
value, valuelen, NULL))
goto bad_seckey;
}
skeyidx++;
}
skey[skeyidx++] = NULL;
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "csum", 0);
if (list)
{
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
desired_csum = strtoul (string, NULL, 10);
xfree (string);
}
else
desired_csum = 0;
gcry_sexp_release (list); list = NULL;
gcry_sexp_release (top_list); top_list = NULL;
#if 0
log_debug ("XXX is v4_or_later=%d\n", is_v4);
log_debug ("XXX pubkey_algo=%d\n", pubkey_algo);
log_debug ("XXX is_protected=%d\n", is_protected);
log_debug ("XXX protect_algo=%d\n", protect_algo);
log_printhex (iv, ivlen, "XXX iv");
log_debug ("XXX ivlen=%d\n", ivlen);
log_debug ("XXX s2k_mode=%d\n", s2k_mode);
log_debug ("XXX s2k_algo=%d\n", s2k_algo);
log_printhex (s2k_salt, sizeof s2k_salt, "XXX s2k_salt");
log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count);
log_debug ("XXX curve='%s'\n", curve);
for (idx=0; skey[idx]; idx++)
gcry_log_debugmpi (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_USER1)
? "skey(e)" : "skey(_)", skey[idx]);
#endif /*0*/
err = get_keygrip (pubkey_algo, curve, skey, grip);
if (err)
goto leave;
if (!dontcare_exist && !from_native && !agent_key_available (ctrl, grip))
{
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
if (unattended && !from_native)
{
err = prepare_unprotect (pubkey_algo, skey, DIM(skey), s2k_mode,
NULL, NULL, NULL);
if (err)
goto leave;
err = convert_transfer_key (&s_skey, pubkey_algo, skey, curve, s_pgp);
if (err)
goto leave;
}
else
{
struct pin_entry_info_s *pi;
struct try_do_unprotect_arg_s pi_arg;
pi = xtrycalloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
err = gpg_error_from_syserror ();
goto leave;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->min_digits = 0; /* We want a real passphrase. */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_do_unprotect_cb;
pi->check_cb_arg = &pi_arg;
pi_arg.is_v4 = is_v4;
pi_arg.is_protected = is_protected;
pi_arg.pubkey_algo = pubkey_algo;
pi_arg.curve = curve;
pi_arg.protect_algo = protect_algo;
pi_arg.iv = iv;
pi_arg.ivlen = ivlen;
pi_arg.s2k_mode = s2k_mode;
pi_arg.s2k_algo = s2k_algo;
pi_arg.s2k_salt = s2k_salt;
pi_arg.s2k_count = s2k_count;
pi_arg.desired_csum = desired_csum;
pi_arg.skey = skey;
pi_arg.skeysize = DIM (skey);
pi_arg.skeyidx = skeyidx;
pi_arg.r_key = &s_skey;
err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (!is_protected)
{
err = try_do_unprotect_cb (pi);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
err = gpg_error (GPG_ERR_BAD_SECKEY);
}
else if (cache_nonce)
{
char *cache_value;
cache_value = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (cache_value)
{
if (strlen (cache_value) < pi->max_length)
strcpy (pi->pin, cache_value);
xfree (cache_value);
}
if (*pi->pin)
err = try_do_unprotect_cb (pi);
}
else if (from_native)
{
if (strlen (passphrase) < pi->max_length)
strcpy (pi->pin, passphrase);
err = try_do_unprotect_cb (pi);
}
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native)
err = agent_askpin (ctrl, prompt, NULL, NULL, pi, NULL, 0);
skeyidx = pi_arg.skeyidx;
if (!err && r_passphrase && is_protected)
{
*r_passphrase = xtrystrdup (pi->pin);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
xfree (pi);
if (err)
goto leave;
}
/* Save some memory and get rid of the SKEY array now. */
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
skeyidx = 0;
/* Note that the padding is not required - we use it only because
that function allows us to create the result in secure memory. */
err = make_canon_sexp_pad (s_skey, 1, r_key, NULL);
leave:
xfree (curve);
gcry_sexp_release (s_skey);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
if (err && r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
return err;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
outofmem:
err = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
/* Convert an OpenPGP transfer key into our internal format. Before
asking for a passphrase we check whether the key already exists in
our key storage. S_PGP is the OpenPGP key in transfer format. If
CACHE_NONCE is given the passphrase will be looked up in the cache.
On success R_KEY will receive a canonical encoded S-expression with
the unprotected key in our internal format; the caller needs to
release that memory. The passphrase used to decrypt the OpenPGP
key will be returned at R_PASSPHRASE; the caller must release this
passphrase. If R_PASSPHRASE is NULL the unattended conversion mode
will be used which uses the openpgp-native protection format for
the key. The keygrip will be stored at the 20 byte buffer pointed
to by GRIP. On error NULL is stored at all return arguments. */
gpg_error_t
convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist,
unsigned char *grip, const char *prompt,
const char *cache_nonce,
unsigned char **r_key, char **r_passphrase)
{
return convert_from_openpgp_main (ctrl, s_pgp, dontcare_exist, grip, prompt,
cache_nonce, NULL,
r_key, r_passphrase);
}
/* This function is called by agent_unprotect to re-protect an
openpgp-native protected private-key into the standard private-key
protection format. */
gpg_error_t
convert_from_openpgp_native (ctrl_t ctrl,
gcry_sexp_t s_pgp, const char *passphrase,
unsigned char **r_key)
{
gpg_error_t err;
unsigned char grip[20];
if (!passphrase)
return gpg_error (GPG_ERR_INTERNAL);
err = convert_from_openpgp_main (ctrl, s_pgp, 0, grip, NULL,
NULL, passphrase,
r_key, NULL);
/* On success try to re-write the key. */
if (!err)
{
if (*passphrase)
{
unsigned char *protectedkey = NULL;
size_t protectedkeylen;
if (!agent_protect (*r_key, passphrase,
&protectedkey, &protectedkeylen,
ctrl->s2k_count))
agent_write_private_key (ctrl, grip,
protectedkey,
protectedkeylen,
- 1, NULL, NULL, NULL, 0);
+ 1, NULL, NULL, NULL, 0, NULL);
xfree (protectedkey);
}
else
{
/* Empty passphrase: write key without protection. */
agent_write_private_key (ctrl, grip,
*r_key,
gcry_sexp_canon_len (*r_key, 0, NULL,NULL),
- 1, NULL, NULL, NULL, 0);
+ 1, NULL, NULL, NULL, 0, NULL);
}
}
return err;
}
/* Given an ARRAY of mpis with the key parameters, protect the secret
parameters in that array and replace them by one opaque encoded
mpi. NPKEY is the number of public key parameters and NSKEY is
the number of secret key parameters (including the public ones).
On success the array will have NPKEY+1 elements. */
static gpg_error_t
apply_protection (gcry_mpi_t *array, int npkey, int nskey,
const char *passphrase,
int protect_algo, void *protect_iv, size_t protect_ivlen,
int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count)
{
gpg_error_t err;
int i, j;
gcry_cipher_hd_t cipherhd;
unsigned char *bufarr[10];
size_t narr[10];
unsigned int nbits[10];
int ndata;
unsigned char *p, *data;
log_assert (npkey < nskey);
log_assert (nskey < DIM (bufarr));
/* Collect only the secret key parameters into BUFARR et al and
compute the required size of the data buffer. */
ndata = 20; /* Space for the SHA-1 checksum. */
for (i = npkey, j = 0; i < nskey; i++, j++ )
{
if (gcry_mpi_get_flag (array[i], GCRYMPI_FLAG_OPAQUE))
{
p = gcry_mpi_get_opaque (array[i], &nbits[j]);
narr[j] = (nbits[j] + 7)/8;
data = xtrymalloc_secure (narr[j]);
if (!data)
err = gpg_error_from_syserror ();
else
{
memcpy (data, p, narr[j]);
bufarr[j] = data;
err = 0;
}
}
else
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]);
nbits[j] = gcry_mpi_get_nbits (array[i]);
}
if (err)
{
for (i = 0; i < j; i++)
xfree (bufarr[i]);
return err;
}
ndata += 2 + narr[j];
}
/* Allocate data buffer and stuff it with the secret key parameters. */
data = xtrymalloc_secure (ndata);
if (!data)
{
err = gpg_error_from_syserror ();
for (i = 0; i < (nskey-npkey); i++ )
xfree (bufarr[i]);
return err;
}
p = data;
for (i = 0; i < (nskey-npkey); i++ )
{
*p++ = nbits[i] >> 8 ;
*p++ = nbits[i];
memcpy (p, bufarr[i], narr[i]);
p += narr[i];
xfree (bufarr[i]);
bufarr[i] = NULL;
}
log_assert (p == data + ndata - 20);
/* Append a hash of the secret key parameters. */
gcry_md_hash_buffer (GCRY_MD_SHA1, p, data, ndata - 20);
/* Encrypt it. */
err = gcry_cipher_open (&cipherhd, protect_algo,
GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
if (!err)
err = hash_passphrase_and_set_key (passphrase, cipherhd, protect_algo,
s2k_mode, s2k_algo, s2k_salt, s2k_count);
if (!err)
err = gcry_cipher_setiv (cipherhd, protect_iv, protect_ivlen);
if (!err)
err = gcry_cipher_encrypt (cipherhd, data, ndata, NULL, 0);
gcry_cipher_close (cipherhd);
if (err)
{
xfree (data);
return err;
}
/* Replace the secret key parameters in the array by one opaque value. */
for (i = npkey; i < nskey; i++ )
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
array[npkey] = gcry_mpi_set_opaque (NULL, data, ndata*8);
gcry_mpi_set_flag (array[npkey], GCRYMPI_FLAG_USER1);
return 0;
}
/*
* Examining S_KEY in S-Expression and extract data.
* When REQ_PRIVATE_KEY_DATA == 1, S_KEY's CAR should be 'private-key',
* but it also allows shadowed or protected versions.
* On success, it returns 0, otherwise error number.
* R_ALGONAME is static string which is no need to free by caller.
* R_NPKEY is pointer to number of public key data.
* R_NSKEY is pointer to number of private key data.
* R_ELEMS is static string which is no need to free by caller.
* ARRAY contains public and private key data.
* ARRAYSIZE is the allocated size of the array for cross-checking.
* R_CURVE is pointer to S-Expression of the curve (can be NULL).
* R_FLAGS is pointer to S-Expression of the flags (can be NULL).
*/
gpg_error_t
extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
const char **r_algoname, int *r_npkey, int *r_nskey,
const char **r_elems,
gcry_mpi_t *array, int arraysize,
gcry_sexp_t *r_curve, gcry_sexp_t *r_flags)
{
gpg_error_t err;
gcry_sexp_t list, l2;
char *name;
const char *algoname, *format, *elems;
int npkey, nskey;
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
*r_curve = NULL;
*r_flags = NULL;
if (!req_private_key_data)
{
list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "private-key", 0 );
}
else
list = gcry_sexp_find_token (s_key, "private-key", 0);
if (!list)
{
log_error ("invalid private key format\n");
return gpg_error (GPG_ERR_BAD_SECKEY);
}
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_string (list, 0);
if (!name)
{
gcry_sexp_release (list);
return gpg_error (GPG_ERR_INV_OBJ); /* Invalid structure of object. */
}
if (arraysize < 7)
BUG ();
/* Map NAME to a name as used by Libgcrypt. We do not use the
Libgcrypt function here because we need a lowercase name and
require special treatment for some algorithms. */
strlwr (name);
if (!strcmp (name, "rsa"))
{
algoname = "rsa";
format = elems = "ned?p?q?u?";
npkey = 2;
nskey = 6;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
array+4, array+5, NULL);
}
else if (!strcmp (name, "elg"))
{
algoname = "elg";
format = elems = "pgyx?";
npkey = 3;
nskey = 4;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
NULL);
}
else if (!strcmp (name, "dsa"))
{
algoname = "dsa";
format = elems = "pqgyx?";
npkey = 4;
nskey = 5;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
array+4, NULL);
}
else if (!strcmp (name, "ecc") || !strcmp (name, "ecdsa"))
{
algoname = "ecc";
format = "/qd?";
elems = "qd?";
npkey = 1;
nskey = 2;
curve = gcry_sexp_find_token (list, "curve", 0);
flags = gcry_sexp_find_token (list, "flags", 0);
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, NULL);
}
else if ( !strcmp (name, (algoname = "kyber512"))
|| !strcmp (name, (algoname = "kyber768"))
|| !strcmp (name, (algoname = "kyber1024")))
{
format = "/ps?";
elems = "ps?";
npkey = 1;
nskey = 2;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, NULL);
}
else
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
xfree (name);
gcry_sexp_release (list);
if (err)
{
gcry_sexp_release (curve);
gcry_sexp_release (flags);
return err;
}
else
{
*r_algoname = algoname;
if (r_elems)
*r_elems = elems;
*r_npkey = npkey;
if (r_nskey)
*r_nskey = nskey;
*r_curve = curve;
*r_flags = flags;
return 0;
}
}
/* Convert our key S_KEY into an OpenPGP key transfer format. On
success a canonical encoded S-expression is stored at R_TRANSFERKEY
and its length at R_TRANSFERKEYLEN; this S-expression is also
padded to a multiple of 64 bits. */
gpg_error_t
convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
unsigned char **r_transferkey, size_t *r_transferkeylen)
{
gpg_error_t err;
const char *algoname;
int npkey, nskey;
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
char protect_iv[16];
char salt[8];
unsigned long s2k_count;
int i, j;
(void)ctrl;
*r_transferkey = NULL;
for (i=0; i < DIM (array); i++)
array[i] = NULL;
err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL,
array, DIM (array), &curve, &flags);
if (err)
return err;
gcry_create_nonce (protect_iv, sizeof protect_iv);
gcry_create_nonce (salt, sizeof salt);
/* We need to use the encoded S2k count. It is not possible to
encode it after it has been used because the encoding procedure
may round the value up. */
s2k_count = get_standard_s2k_count_rfc4880 ();
err = apply_protection (array, npkey, nskey, passphrase,
GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
3, GCRY_MD_SHA1, salt, s2k_count);
/* Turn it into the transfer key S-expression. Note that we always
return a protected key. */
if (!err)
{
char countbuf[35];
membuf_t mbuf;
void *format_args[10+2];
gcry_sexp_t tmpkey;
gcry_sexp_t tmpsexp = NULL;
snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
for (i=j=0; i < npkey; i++)
{
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = array + i;
}
put_membuf_str (&mbuf, " e %m");
format_args[j++] = array + npkey;
put_membuf_str (&mbuf, ")\n");
put_membuf (&mbuf, "", 1);
tmpkey = NULL;
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args);
xfree (format);
}
if (!err)
err = gcry_sexp_build (&tmpsexp, NULL,
"(openpgp-private-key\n"
" (version 1:4)\n"
" (algo %s)\n"
" %S%S\n"
" (protection sha1 aes %b 1:3 sha1 %b %s))\n",
algoname,
curve,
tmpkey,
(int)sizeof protect_iv, protect_iv,
(int)sizeof salt, salt,
countbuf);
gcry_sexp_release (tmpkey);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen);
gcry_sexp_release (tmpsexp);
}
for (i=0; i < DIM (array); i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
return err;
}
diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c
index b9e8784bd..5500c07f1 100644
--- a/agent/divert-tpm2.c
+++ b/agent/divert-tpm2.c
@@ -1,201 +1,201 @@
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
#include "../common/i18n.h"
#include "../common/sexp-parse.h"
int
divert_tpm2_pksign (ctrl_t ctrl,
const unsigned char *digest, size_t digestlen, int algo,
const unsigned char *shadow_info, unsigned char **r_sig,
size_t *r_siglen)
{
(void)algo;
return agent_tpm2d_pksign(ctrl, digest, digestlen,
shadow_info, r_sig, r_siglen);
}
static gpg_error_t
agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip,
unsigned char *shadow_info,
gcry_sexp_t s_key)
{
gpg_error_t err, err1;
unsigned char *shdkey;
unsigned char *pkbuf;
size_t len;
gcry_sexp_t s_pkey;
err = agent_public_key_from_file (ctrl, grip, &s_pkey);
len = gcry_sexp_sprint(s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
pkbuf = xtrymalloc (len);
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, len);
gcry_sexp_release (s_pkey);
err = agent_shadow_key_type (pkbuf, shadow_info, "tpm2-v1", &shdkey);
xfree (pkbuf);
if (err)
{
log_error ("shadowing the tpm key failed: %s\n", gpg_strerror (err));
return err;
}
err = agent_delete_key (ctrl, NULL, grip, 1, 0);
if (err)
{
log_error ("failed to delete unshadowed key: %s\n", gpg_strerror (err));
xfree (shdkey);
return err;
}
len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
err = agent_write_private_key (ctrl, grip, shdkey, len, 1 /*force*/,
- NULL, NULL, NULL, 0);
+ NULL, NULL, NULL, 0, NULL);
xfree (shdkey);
if (err)
{
log_error ("error writing tpm key: %s\n", gpg_strerror (err));
len = gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, NULL, 0);
pkbuf = xtrymalloc(len);
if (!pkbuf)
return GPG_ERR_ENOMEM;
gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, pkbuf, len);
err1 = agent_write_private_key (ctrl, grip, pkbuf, len, 1 /*force*/,
- NULL, NULL, NULL, 0);
+ NULL, NULL, NULL, 0, NULL);
xfree(pkbuf);
if (err1)
log_error ("error trying to restore private key: %s\n",
gpg_strerror (err1));
}
return err;
}
int
divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t s_skey)
{
int ret;
/* shadow_info is always shielded so no special handling required */
unsigned char *shadow_info;
ret = agent_tpm2d_writekey(ctrl, &shadow_info, s_skey);
if (!ret) {
ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info, s_skey);
xfree (shadow_info);
}
return ret;
}
int
divert_tpm2_pkdecrypt (ctrl_t ctrl,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len, int *r_padding)
{
const unsigned char *s;
size_t n;
if (r_padding)
*r_padding = -1;
s = cipher;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "enc-val"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "rsa"))
{
if (r_padding)
*r_padding = 0;
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "a"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
}
else if (smatch (&s, n, "ecdh"))
{
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "s"))
{
n = snext (&s);
s += n;
if (*s++ != ')')
return gpg_error (GPG_ERR_INV_SEXP);
if (*s++ != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
}
if (!smatch (&s, n, "e"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
return agent_tpm2d_pkdecrypt (ctrl, s, n, shadow_info, r_buf, r_len);
}
int
agent_tpm2d_ecc_kem (ctrl_t ctrl,
const unsigned char *shadow_info,
const unsigned char *ecc_ct,
size_t ecc_point_len, unsigned char *ecc_ecdh)
{
char *ecdh = NULL;
size_t len;
int rc;
rc = agent_tpm2d_pkdecrypt (ctrl, ecc_ct, ecc_point_len, shadow_info,
&ecdh, &len);
if (rc)
return rc;
if (len == ecc_point_len)
memcpy (ecc_ecdh, ecdh, len);
else if (len == ecc_point_len + 1 && ecdh[0] == 0x40) /* The prefix */
memcpy (ecc_ecdh, ecdh + 1, len - 1);
else
{
if (opt.verbose)
log_info ("%s: ECC result length invalid (%zu != %zu)\n",
__func__, len, ecc_point_len);
return gpg_error (GPG_ERR_INV_DATA);
}
xfree (ecdh);
return rc;
}
diff --git a/agent/findkey.c b/agent/findkey.c
index 4ca83bce4..954199fcd 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1,2113 +1,2123 @@
/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2019 Werner Koch
* Copyright (C) 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <npth.h> /* (we use pth_sleep) */
#include "agent.h"
#include "../common/i18n.h"
#include "../common/ssh-utils.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
static gpg_error_t read_key_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta,
char **r_orig_key_value);
static gpg_error_t is_shadowed_key (gcry_sexp_t s_skey);
/* Helper to pass data to the check callback of the unprotect function. */
struct try_unprotect_arg_s
{
ctrl_t ctrl;
const unsigned char *protected_key;
unsigned char *unprotected_key;
int change_required; /* Set by the callback to indicate that the
user should change the passphrase. */
};
/* Return the file name for the 20 byte keygrip GRIP. With FOR_NEW
* create a file name for later renaming to the actual name. Return
* NULL on error. */
static char *
fname_from_keygrip (const unsigned char *grip, int for_new)
{
char hexgrip[40+4+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, for_new? ".key.tmp" : ".key");
return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
}
/* Helper until we have a "wipe" mode flag in es_fopen. */
static void
wipe_and_fclose (estream_t fp)
{
void *blob;
size_t blob_size;
if (!fp)
;
else if (es_fclose_snatch (fp, &blob, &blob_size))
{
log_error ("error wiping buffer during fclose\n");
es_fclose (fp);
}
else if (blob)
{
wipememory (blob, blob_size);
gpgrt_free (blob);
}
}
/* Replace all linefeeds in STRING by "%0A" and return a new malloced
* string. May return NULL on memory error. */
static char *
linefeed_to_percent0A (const char *string)
{
const char *s;
size_t n;
char *buf, *p;
for (n=0, s=string; *s; s++)
if (*s == '\n')
n += 3;
else
n++;
p = buf = xtrymalloc (n+1);
if (!buf)
return NULL;
for (s=string; *s; s++)
if (*s == '\n')
{
memcpy (p, "%0A", 3);
p += 3;
}
else
*p++ = *s;
*p = 0;
return buf;
}
/* Write the S-expression formatted key (BUFFER,LENGTH) to our key
* storage. With FORCE passed as true an existing key with the given
* GRIP will get overwritten. If SERIALNO and KEYREF are given a
* Token line is added to the key if the extended format is used. If
* TIMESTAMP is not zero and the key does not yet exists it will be
- * recorded as creation date. */
+ * recorded as creation date. If LINKATTR is not NULL a Link: entry
+ * with that value will also be written. */
gpg_error_t
agent_write_private_key (ctrl_t ctrl,
const unsigned char *grip,
const void *buffer, size_t length, int force,
const char *serialno, const char *keyref,
const char *dispserialno,
- time_t timestamp)
+ time_t timestamp, const char *linkattr)
{
gpg_error_t err;
char *fname = NULL;
char *tmpfname = NULL;
estream_t fp = NULL;
int newkey = 0;
nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int removetmp = 0;
char *token0 = NULL;
char *token = NULL;
char *dispserialno_buffer = NULL;
char **tokenfields = NULL;
int is_regular;
int blocksigs = 0;
char *orig_key_value = NULL;
const char *s;
int force_modify = 0;
fname = (ctrl->ephemeral_mode
? xtrystrdup ("[ephemeral key store]")
: fname_from_keygrip (grip, 0));
if (!fname)
return gpg_error_from_syserror ();
err = read_key_file (ctrl, grip, &key, &pk, &orig_key_value);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
newkey = 1;
else
{
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
}
nvc_modified (pk, 1); /* Clear that flag after a read. */
if (!pk)
{
/* Key is still in the old format or does not exist - create a
* new container. */
pk = nvc_new_private_key ();
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
force_modify = 1;
}
/* Check whether we already have a regular key. */
is_regular = (key && gpg_err_code (is_shadowed_key (key)) != GPG_ERR_TRUE);
/* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into
* our name value container. */
gcry_sexp_release (key);
err = gcry_sexp_sscan (&key, NULL, buffer, length);
if (err)
goto leave;
err = nvc_set_private_key (pk, key);
if (err)
goto leave;
/* Detect whether the key value actually changed and if not clear
* the modified flag. This extra check is required because
* read_key_file removes the Key entry from the container and we
* then create a new Key entry which might be the same, though. */
if (!force_modify
&& orig_key_value && (s = nvc_get_string (pk, "Key:"))
&& !strcmp (orig_key_value, s))
{
nvc_modified (pk, 1); /* Clear that flag. */
}
xfree (orig_key_value);
orig_key_value = NULL;
/* Check that we do not update a regular key with a shadow key. */
if (is_regular && gpg_err_code (is_shadowed_key (key)) == GPG_ERR_TRUE)
{
log_info ("updating regular key file '%s'"
" by a shadow key inhibited\n", fname);
err = 0; /* Simply ignore the error. */
goto leave;
}
/* Check that we update a regular key only in force mode. */
if (is_regular && !force)
{
log_error ("secret key file '%s' already exists\n", fname);
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
/* If requested write a Token line. */
if (serialno && keyref)
{
nve_t item;
size_t token0len;
if (dispserialno)
{
/* Escape the DISPSERIALNO. */
dispserialno_buffer = percent_plus_escape (dispserialno);
if (!dispserialno_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
dispserialno = dispserialno_buffer;
}
token0 = strconcat (serialno, " ", keyref, NULL);
if (token0)
token = strconcat (token0, " - ", dispserialno? dispserialno:"-", NULL);
if (!token0 || !token)
{
err = gpg_error_from_syserror ();
goto leave;
}
token0len = strlen (token0);
for (item = nvc_lookup (pk, "Token:");
item;
item = nve_next_value (item, "Token:"))
if ((s = nve_value (item)) && !strncmp (s, token0, token0len))
break;
if (!item)
{
/* No token or no token with that value exists. Add a new
* one so that keys which have been stored on several cards
* are well supported. */
err = nvc_add (pk, "Token:", token);
if (err)
goto leave;
}
else
{
/* Token exists: Update the display s/n. It may have
* changed due to changes in a newer software version. */
if (s && (tokenfields = strtokenize (s, " \t\n"))
&& tokenfields[0] && tokenfields[1] && tokenfields[2]
&& tokenfields[3]
&& !strcmp (tokenfields[3], dispserialno))
; /* No need to update Token entry. */
else
{
err = nve_set (pk, item, token);
if (err)
goto leave;
}
}
}
/* If a timestamp has been supplied and the key is new, write a
* creation timestamp. (We douple check that there is no Created
* item yet.)*/
if (timestamp && newkey && !nvc_lookup (pk, "Created:"))
{
gnupg_isotime_t timebuf;
epoch2isotime (timebuf, timestamp);
err = nvc_add (pk, "Created:", timebuf);
if (err)
goto leave;
}
+ /* Write a link attribute if supplied. */
+ if (linkattr && *linkattr)
+ {
+ err = nvc_add (pk, "Link:", linkattr);
+ if (err)
+ goto leave;
+ }
+
+
/* Check whether we need to write the file at all. */
if (!nvc_modified (pk, 0))
{
err = 0;
goto leave;
}
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
void *blob;
size_t blobsize;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN))
break;
if (!ek)
{
ek = xtrycalloc (1, sizeof *ek);
if (!ek)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (ek->grip, grip, KEYGRIP_LEN);
ek->next = ctrl->ephemeral_keys;
ctrl->ephemeral_keys = ek;
}
fp = es_fopenmem (0, "wb,wipe");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open memory stream: %s\n", gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing to memory stream: %s\n",gpg_strerror (err));
goto leave;
}
if (es_fclose_snatch (fp, &blob, &blobsize) || !blob)
{
err = gpg_error_from_syserror ();
log_error ("error getting memory stream buffer: %s\n",
gpg_strerror (err));
/* Closing right away so that we don't try another snatch in
* the cleanup. */
es_fclose (fp);
fp = NULL;
goto leave;
}
fp = NULL;
xfree (ek->keybuf);
ek->keybuf = blob;
ek->keybuflen = blobsize;
}
else
{
/* Create a temporary file for writing. */
tmpfname = fname_from_keygrip (grip, 1);
fp = tmpfname ? es_fopen (tmpfname, "wbx,mode=-rw") : NULL;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", tmpfname, gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (!err && es_fflush (fp))
err = gpg_error_from_syserror ();
if (err)
{
log_error ("error writing '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
else
fp = NULL;
err = gnupg_rename_file (tmpfname, fname, &blocksigs);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("error renaming '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
}
bump_key_eventcounter ();
leave:
if (blocksigs)
gnupg_unblock_all_signals ();
if (ctrl->ephemeral_mode)
wipe_and_fclose (fp);
else
es_fclose (fp);
if (removetmp && tmpfname)
gnupg_remove (tmpfname);
xfree (orig_key_value);
xfree (fname);
xfree (tmpfname);
xfree (token);
xfree (token0);
xfree (dispserialno_buffer);
xfree (tokenfields);
gcry_sexp_release (key);
nvc_release (pk);
return err;
}
gpg_error_t
agent_update_private_key (ctrl_t ctrl, const unsigned char *grip, nvc_t pk)
{
gpg_error_t err;
char *fname0 = NULL; /* The existing file name. */
char *fname = NULL; /* The temporary new file name. */
estream_t fp = NULL;
int removetmp = 0;
int blocksigs = 0;
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
void *blob;
size_t blobsize;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN))
break;
if (!ek)
{
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
fp = es_fopenmem (0, "wbx,wipe");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open memory stream: %s\n", gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing to memory stream: %s\n",gpg_strerror (err));
goto leave;
}
if (es_fclose_snatch (fp, &blob, &blobsize) || !blob)
{
err = gpg_error_from_syserror ();
log_error ("error getting memory stream buffer: %s\n",
gpg_strerror (err));
/* Closing right away so that we don't try another snatch in
* the cleanup. */
es_fclose (fp);
fp = NULL;
goto leave;
}
fp = NULL;
/* No need to revisit the linked list because the found EK is
* not expected to change due to the other syscalls above. */
xfree (ek->keybuf);
ek->keybuf = blob;
ek->keybuflen = blobsize;
goto leave;
}
fname0 = fname_from_keygrip (grip, 0);
if (!fname0)
{
err = gpg_error_from_syserror ();
goto leave;
}
fname = fname_from_keygrip (grip, 1);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = es_fopen (fname, "wbx,mode=-rw");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
es_fclose (fp);
fp = NULL;
err = gnupg_rename_file (fname, fname0, &blocksigs);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("error renaming '%s': %s\n", fname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
leave:
if (blocksigs)
gnupg_unblock_all_signals ();
if (ctrl->ephemeral_mode)
wipe_and_fclose (fp);
else
es_fclose (fp);
if (removetmp && fname)
gnupg_remove (fname);
xfree (fname);
xfree (fname0);
return err;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
try_unprotect_cb (struct pin_entry_info_s *pi)
{
struct try_unprotect_arg_s *arg = pi->check_cb_arg;
ctrl_t ctrl = arg->ctrl;
size_t dummy;
gpg_error_t err;
gnupg_isotime_t now, protected_at, tmptime;
char *desc = NULL;
log_assert (!arg->unprotected_key);
arg->change_required = 0;
err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
&arg->unprotected_key, &dummy);
if (err)
return err;
if (!opt.max_passphrase_days || ctrl->in_passwd)
return 0; /* No regular passphrase change required. */
if (!*protected_at)
{
/* No protection date known - must force passphrase change. */
desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
"Please change it now."));
if (!desc)
return gpg_error_from_syserror ();
}
else
{
gnupg_get_isotime (now);
gnupg_copy_time (tmptime, protected_at);
err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
if (err)
return err;
if (strcmp (now, tmptime) > 0 )
{
/* Passphrase "expired". */
desc = xtryasprintf
(L_("This passphrase has not been changed%%0A"
"since %.4s-%.2s-%.2s. Please change it now."),
protected_at, protected_at+4, protected_at+6);
if (!desc)
return gpg_error_from_syserror ();
}
}
if (desc)
{
/* Change required. */
if (opt.enforce_passphrase_constraints)
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"), NULL, 0);
if (!err)
arg->change_required = 1;
}
else
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"),
L_("I'll change it later"), 0);
if (!err)
arg->change_required = 1;
else if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
err = 0;
}
xfree (desc);
}
return err;
}
/* Return true if the STRING has an %C or %c expando. */
static int
has_comment_expando (const char *string)
{
const char *s;
int percent = 0;
if (!string)
return 0;
for (s = string; *s; s++)
{
if (percent)
{
if (*s == 'c' || *s == 'C')
return 1;
percent = 0;
}
else if (*s == '%')
percent = 1;
}
return 0;
}
/* Modify a Key description, replacing certain special format
characters. List of currently supported replacements:
%% - Replaced by a single %
%c - Replaced by the content of COMMENT.
%C - Same as %c but put into parentheses.
%F - Replaced by an ssh style fingerprint computed from KEY.
The functions returns 0 on success or an error code. On success a
newly allocated string is stored at the address of RESULT.
*/
gpg_error_t
agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result)
{
size_t comment_length;
size_t in_len;
size_t out_len;
char *out;
size_t i;
int special, pass;
char *ssh_fpr = NULL;
char *p;
*result = NULL;
if (!comment)
comment = "";
comment_length = strlen (comment);
in_len = strlen (in);
/* First pass calculates the length, second pass does the actual
copying. */
/* FIXME: This can be simplified by using es_fopenmem. */
out = NULL;
out_len = 0;
for (pass=0; pass < 2; pass++)
{
special = 0;
for (i = 0; i < in_len; i++)
{
if (special)
{
special = 0;
switch (in[i])
{
case '%':
if (out)
*out++ = '%';
else
out_len++;
break;
case 'c': /* Comment. */
if (out)
{
memcpy (out, comment, comment_length);
out += comment_length;
}
else
out_len += comment_length;
break;
case 'C': /* Comment. */
if (!comment_length)
;
else if (out)
{
*out++ = '(';
memcpy (out, comment, comment_length);
out += comment_length;
*out++ = ')';
}
else
out_len += comment_length + 2;
break;
case 'F': /* SSH style fingerprint. */
if (!ssh_fpr && key)
ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
&ssh_fpr);
if (ssh_fpr)
{
if (out)
out = stpcpy (out, ssh_fpr);
else
out_len += strlen (ssh_fpr);
}
break;
default: /* Invalid special sequences are kept as they are. */
if (out)
{
*out++ = '%';
*out++ = in[i];
}
else
out_len+=2;
break;
}
}
else if (in[i] == '%')
special = 1;
else
{
if (out)
*out++ = in[i];
else
out_len++;
}
}
if (!pass)
{
*result = out = xtrymalloc (out_len + 1);
if (!out)
{
xfree (ssh_fpr);
return gpg_error_from_syserror ();
}
}
}
*out = 0;
log_assert (*result + out_len == out);
xfree (ssh_fpr);
/* The ssh prompt may sometimes end in
* "...%0A ()"
* The empty parentheses doesn't look very good. We use this hack
* here to remove them as well as the indentation spaces. */
p = *result;
i = strlen (p);
if (i > 2 && !strcmp (p + i - 2, "()"))
{
p += i - 2;
*p-- = 0;
while (p > *result && spacep (p))
*p-- = 0;
}
return 0;
}
/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
should be the hex encoded keygrip of that key to be used with the
caching mechanism. DESC_TEXT may be set to override the default
description used for the pinentry. If LOOKUP_TTL is given this
function is used to lookup the default ttl. If R_PASSPHRASE is not
NULL, the function succeeded and the key was protected the used
passphrase (entered or from the cache) is stored there; if not NULL
will be stored. The caller needs to free the returned
passphrase. */
static gpg_error_t
unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
unsigned char **keybuf, const unsigned char *grip,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
char **r_passphrase)
{
struct pin_entry_info_s *pi;
struct try_unprotect_arg_s arg;
int rc;
unsigned char *result;
size_t resultlen;
char hexgrip[40+1];
if (r_passphrase)
*r_passphrase = NULL;
bin2hex (grip, 20, hexgrip);
/* Initially try to get it using a cache nonce. */
if (cache_nonce)
{
char *pw;
pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
else if (opt.verbose)
log_info (_("Unprotecting key failed: %s\n"), gpg_strerror (rc));
xfree (pw);
}
}
/* First try to get it from the cache - if there is none or we can't
unprotect it, we fall back to ask the user */
if (cache_mode != CACHE_MODE_IGNORE)
{
char *pw;
retry:
pw = agent_get_cache (ctrl, hexgrip, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (cache_mode == CACHE_MODE_NORMAL)
agent_store_cache_hit (hexgrip);
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
else if (cache_mode == CACHE_MODE_NORMAL)
{
/* The standard use of GPG keys is to have a signing and an
encryption subkey. Commonly both use the same
passphrase. We try to help the user to enter the
passphrase only once by silently trying the last
correctly entered passphrase. Checking one additional
passphrase should be acceptable; despite the S2K
introduced delays. The assumed workflow is:
1. Read encrypted message in a MUA and thus enter a
passphrase for the encryption subkey.
2. Reply to that mail with an encrypted and signed
mail, thus entering the passphrase for the signing
subkey.
We can often avoid the passphrase entry in the second
step. We do this only in normal mode, so not to
interfere with unrelated cache entries. */
pw = agent_get_cache (ctrl, NULL, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
&result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* If the pinentry is currently in use, we wait up to 60 seconds
for it to close and check the cache again. This solves a common
situation where several requests for unprotecting a key have
been made but the user is still entering the passphrase for
the first request. Because all requests to agent_askpin are
serialized they would then pop up one after the other to
request the passphrase - despite that the user has already
entered it and is then available in the cache. This
implementation is not race free but in the worst case the
user has to enter the passphrase only once more. */
if (pinentry_active_p (ctrl, 0))
{
/* Active - wait */
if (!pinentry_active_p (ctrl, 60))
{
/* We need to give the other thread a chance to actually put
it into the cache. */
gnupg_sleep (1);
goto retry;
}
/* Timeout - better call pinentry now the plain way. */
}
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->min_digits = 0; /* we want a real passphrase */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_unprotect_cb;
arg.ctrl = ctrl;
arg.protected_key = *keybuf;
arg.unprotected_key = NULL;
arg.change_required = 0;
pi->check_cb_arg = &arg;
rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
if (rc)
{
if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
log_error ("Clearing pinentry cache which caused error %s\n",
gpg_strerror (rc));
agent_clear_passphrase (ctrl, hexgrip, cache_mode);
}
}
else
{
log_assert (arg.unprotected_key);
if (arg.change_required)
{
/* The callback told as that the user should change their
passphrase. Present the dialog to do. */
size_t canlen, erroff;
gcry_sexp_t s_skey;
log_assert (arg.unprotected_key);
canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_skey, &erroff,
(char*)arg.unprotected_key, canlen);
if (rc)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
rc = agent_protect_and_store (ctrl, s_skey, NULL);
gcry_sexp_release (s_skey);
if (rc)
{
log_error ("changing the passphrase failed: %s\n",
gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
}
else
{
/* Passphrase is fine. */
agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
lookup_ttl? lookup_ttl (hexgrip) : 0);
agent_store_cache_hit (hexgrip);
if (r_passphrase && *pi->pin)
*r_passphrase = xtrystrdup (pi->pin);
}
xfree (*keybuf);
*keybuf = arg.unprotected_key;
}
xfree (pi);
return rc;
}
/* Read the key identified by GRIP from the private key directory and
* return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
* is not NULL and the extended key format is used, the meta data
* items are stored there. However the "Key:" item is removed from
* it. If R_ORIG_KEY_VALUE is non-NULL and the Key item was removed,
* its original value is stored at that R_ORIG_KEY_VALUE and the
* caller must free it. On failure returns an error code and stores
* NULL at RESULT and R_KEYMETA. */
static gpg_error_t
read_key_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value)
{
gpg_error_t err;
char *fname;
estream_t fp = NULL;
unsigned char *buf = NULL;
size_t buflen, erroff;
nvc_t pk = NULL;
char first;
size_t keybuflen;
*result = NULL;
if (r_keymeta)
*r_keymeta = NULL;
if (r_orig_key_value)
*r_orig_key_value = NULL;
fname = (ctrl->ephemeral_mode
? xtrystrdup ("[ephemeral key store]")
: fname_from_keygrip (grip, 0));
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN)
&& ek->keybuf && ek->keybuflen)
break;
if (!ek || !ek->keybuf || !ek->keybuflen)
{
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
keybuflen = ek->keybuflen;
fp = es_fopenmem_init (0, "rb", ek->keybuf, ek->keybuflen);
}
else
{
keybuflen = 0; /* Indicates that this is not ephemeral mode. */
fp = es_fopen (fname, "rb");
}
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
if (es_fread (&first, 1, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
if (es_fseek (fp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
if (first != '(')
{
/* Key is in extended format. */
int line;
err = nvc_parse_private_key (&pk, &line, fp);
if (err)
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
else
{
err = nvc_get_private_key (pk, result);
if (err)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (err));
else
{
if (r_orig_key_value)
{
const char *s = nvc_get_string (pk, "Key:");
if (s)
{
*r_orig_key_value = xtrystrdup (s);
if (!*r_orig_key_value)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
}
nvc_delete_named (pk, "Key:");
}
}
goto leave; /* Ready. */
}
if (keybuflen)
buflen = keybuflen;
else
{
struct stat st;
if (fstat (es_fileno (fp), &st))
{
err = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
buflen = st.st_size;
}
buf = xtrymalloc (buflen+1);
if (!buf)
{
err = gpg_error_from_syserror ();
log_error ("error allocating %zu bytes for '%s': %s\n",
buflen, fname, gpg_strerror (err));
goto leave;
}
if (es_fread (buf, buflen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading %zu bytes from '%s': %s\n",
buflen, fname, gpg_strerror (err));
goto leave;
}
/* Convert the file into a gcrypt S-expression object. */
{
gcry_sexp_t s_skey;
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
if (err)
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
else
*result = s_skey;
}
leave:
xfree (buf);
if (!err && r_keymeta)
*r_keymeta = pk;
else
nvc_release (pk);
if (ctrl->ephemeral_mode)
wipe_and_fclose (fp);
else
es_fclose (fp);
xfree (fname);
return err;
}
/* Remove the key identified by GRIP from the private key directory. */
static gpg_error_t
remove_key_file (const unsigned char *grip)
{
gpg_error_t err = 0;
char *fname;
fname = fname_from_keygrip (grip, 0);
if (!fname)
{
return gpg_error_from_syserror ();
}
/* On Windows we wait up to 400ms until a sharing violation has
* vanished. This may for example happen if Kleopatra or another
* frontend directly reads a private key file. */
if (gnupg_remove_ext (fname, 400))
err = gpg_error_from_syserror ();
xfree (fname);
return err;
}
/*
* Prompt a user the card insertion, when it's not available yet.
*/
static gpg_error_t
prompt_for_card (ctrl_t ctrl, const unsigned char *grip,
nvc_t keymeta, const unsigned char *shadow_info)
{
char *serialno;
char *desc;
char *want_sn = NULL;
int len;
gpg_error_t err;
char hexgrip[41];
char *comment_buffer = NULL;
const char *comment = NULL;
int refuse_prompt = 0;
bin2hex (grip, 20, hexgrip);
if (keymeta)
{
const char *p;
if ((p = nvc_get_string (keymeta, "Prompt:")) && !strcmp (p, "no"))
refuse_prompt = 1;
if ((p = nvc_get_string (keymeta, "Label:")))
{
if (strchr (p, '\n')
&& (comment_buffer = linefeed_to_percent0A (p)))
comment = comment_buffer;
else
comment = p;
}
}
err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL);
if (err)
return err;
len = want_sn? strlen (want_sn) : 0;
if (len == 32 && !strncmp (want_sn, "D27600012401", 12))
{
/* This is an OpenPGP card - reformat */
if (!strncmp (want_sn+16, "0006", 4))
{
/* This is a Yubikey. Print the s/n as it would be printed
* on Yubikey 5. Example: D2760001240100000006120808620000
* mmmm^^^^^^^^ */
unsigned long sn;
sn = atoi_4 (want_sn+20) * 10000;
sn += atoi_4 (want_sn+24);
snprintf (want_sn, 32, "%lu %03lu %03lu",
(sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul));
}
else /* Default is the Zeitcontrol card print format. */
{
memmove (want_sn, want_sn+16, 4);
want_sn[4] = ' ';
memmove (want_sn+5, want_sn+20, 8);
want_sn[13] = 0;
}
}
else if (len == 20 && want_sn[19] == '0')
{
/* We assume that a 20 byte serial number is a standard one
* which has the property to have a zero in the last nibble (Due
* to BCD representation). We don't display this '0' because it
* may confuse the user. */
want_sn[19] = 0;
}
for (;;)
{
/* Scan device(s), and check if key for GRIP is available. */
err = agent_card_serialno (ctrl, &serialno, NULL);
if (!err)
{
struct card_key_info_s *keyinfo;
xfree (serialno);
err = agent_card_keyinfo (ctrl, hexgrip, 0, &keyinfo);
if (!err)
{
/* Key for GRIP found, use it. */
agent_card_free_keyinfo (keyinfo);
break;
}
}
/* Card is not available. Prompt the insertion. */
if (refuse_prompt)
{
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
}
if (asprintf (&desc,
"%s:%%0A%%0A"
" %s%%0A"
" %s",
L_("Please insert the card with serial number"),
want_sn ? want_sn : "",
comment? comment:"") < 0)
err = out_of_core ();
else
{
err = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY)
err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
xfree (desc);
}
if (err)
break;
}
xfree (want_sn);
gcry_free (comment_buffer);
return err;
}
/* Return the secret key as an S-Exp in RESULT after locating it using
the GRIP. Caller should set GRIP=NULL, when a key in a file is
intended to be used for cryptographic operation (except the case of
GRIP==CTRL->keygrip1 for PQC). When GRIP==NULL, CTRL->keygrip is
used to locate the file. When GRIP==NULL or GRIP==CTRL->keygrip1,
it may ask a user for confirmation. If the operation shall be
diverted to a token, an allocated S-expression with the shadow_info
part from the file is stored at SHADOW_INFO; if not NULL will be
stored at SHADOW_INFO. CACHE_MODE defines now the cache shall be
used. DESC_TEXT may be set to present a custom description for the
pinentry. LOOKUP_TTL is an optional function to convey a TTL to
the cache manager; we do not simply pass the TTL value because the
value is only needed if an unprotect action was needed and looking
up the TTL may have some overhead (e.g. scanning the sshcontrol
file). If a CACHE_NONCE is given that cache item is first tried to
get a passphrase. If R_PASSPHRASE is not NULL, the function
succeeded and the key was protected the used passphrase (entered or
from the cache) is stored there; if not NULL will be stored. The
caller needs to free the returned passphrase. */
gpg_error_t
agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
const unsigned char *grip, unsigned char **shadow_info,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
gcry_sexp_t *result, char **r_passphrase,
time_t *r_timestamp)
{
gpg_error_t err;
unsigned char *buf;
size_t len, erroff;
gcry_sexp_t s_skey;
nvc_t keymeta = NULL;
char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */
int for_crypto_operation = 0;
*result = NULL;
if (shadow_info)
*shadow_info = NULL;
if (r_passphrase)
*r_passphrase = NULL;
if (r_timestamp)
*r_timestamp = (time_t)(-1);
if (!grip)
{
if (!ctrl->have_keygrip)
return gpg_error (GPG_ERR_NO_SECKEY);
for_crypto_operation = 1;
grip = ctrl->keygrip;
}
else if (grip == ctrl->keygrip1)
/* This is the use case for composite PQC, */
for_crypto_operation = 1;
err = read_key_file (ctrl, grip, &s_skey, &keymeta, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
else
log_error ("findkey: error reading key file: %s\n",
gpg_strerror (err));
return err;
}
/* For use with the protection functions we also need the key as an
canonical encoded S-expression in a buffer. Create this buffer
now. */
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
{
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
if (r_timestamp && keymeta)
{
const char *created = nvc_get_string (keymeta, "Created:");
if (created)
*r_timestamp = isotime2epoch (created);
}
if (for_crypto_operation && keymeta)
{
const char *ask_confirmation = nvc_get_string (keymeta, "Confirm:");
if (ask_confirmation
&& ((!strcmp (ask_confirmation, "restricted") && ctrl->restricted)
|| !strcmp (ask_confirmation, "yes")))
{
char hexgrip[40+4+1];
char *prompt;
char *comment_buffer = NULL;
const char *comment = NULL;
bin2hex (ctrl->keygrip, 20, hexgrip);
if ((comment = nvc_get_string (keymeta, "Label:")))
{
if (strchr (comment, '\n')
&& (comment_buffer = linefeed_to_percent0A (comment)))
comment = comment_buffer;
}
prompt = xtryasprintf (L_("Requested the use of key%%0A"
" %s%%0A"
" %s%%0A"
"Do you want to allow this?"),
hexgrip, comment? comment:"");
gcry_free (comment_buffer);
err = agent_get_confirmation (ctrl, prompt,
L_("Allow"), L_("Deny"), 0);
xfree (prompt);
if (err)
return err;
}
}
switch (agent_private_key_type (buf))
{
case PRIVATE_KEY_CLEAR:
break; /* no unprotection needed */
case PRIVATE_KEY_OPENPGP_NONE:
{
unsigned char *buf_new;
size_t buf_newlen;
err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
if (err)
log_error ("failed to convert unprotected openpgp key: %s\n",
gpg_strerror (err));
else
{
xfree (buf);
buf = buf_new;
}
}
break;
case PRIVATE_KEY_PROTECTED:
{
char *desc_text_final;
char *comment_buffer = NULL;
const char *comment = NULL;
/* Note, that we will take the comment as a C string for
* display purposes; i.e. all stuff beyond a Nul character is
* ignored. If a "Label" entry is available in the meta data
* this is used instead of the s-expression comment. */
if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
{
if (strchr (comment, '\n')
&& (comment_buffer = linefeed_to_percent0A (comment)))
comment = comment_buffer;
/* In case DESC_TEXT has no escape pattern for a comment
* we append one. */
if (desc_text && !has_comment_expando (desc_text))
{
desc_text_buffer = strconcat (desc_text, "%0A%C", NULL);
if (desc_text_buffer)
desc_text = desc_text_buffer;
}
}
else
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment_buffer = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
comment = comment_buffer;
}
desc_text_final = NULL;
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
gcry_free (comment_buffer);
if (!err)
{
err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
cache_mode, lookup_ttl, r_passphrase);
if (err)
log_error ("failed to unprotect the secret key: %s\n",
gpg_strerror (err));
}
xfree (desc_text_final);
}
break;
case PRIVATE_KEY_SHADOWED:
if (shadow_info)
{
const unsigned char *s;
unsigned char *shadow_type;
size_t n;
err = agent_get_shadow_info_type (buf, &s, &shadow_type);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
log_assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
{
err = out_of_core ();
goto shadow_error;
}
else
{
memcpy (*shadow_info, s, n);
/*
* When it's a key on card (not on tpm2), make sure
* it's available.
*/
if (strcmp (shadow_type, "t1-v1") == 0
&& for_crypto_operation)
err = prompt_for_card (ctrl, grip, keymeta, *shadow_info);
}
}
else
shadow_error:
log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
xfree (shadow_type);
}
else
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
gcry_sexp_release (s_skey);
s_skey = NULL;
if (err)
{
xfree (buf);
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
err = sexp_sscan_private_key (result, &erroff, buf);
xfree (buf);
nvc_release (keymeta);
xfree (desc_text_buffer);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
}
return err;
}
/* This function returns GPG_ERR_TRUE if S_SKEY represents a shadowed
* key. 0 is return for other key types. Any other error may occur
* if S_SKEY is not valid. */
static gpg_error_t
is_shadowed_key (gcry_sexp_t s_skey)
{
gpg_error_t err;
unsigned char *buf;
size_t buflen;
err = make_canon_sexp (s_skey, &buf, &buflen);
if (err)
return err;
if (agent_private_key_type (buf) == PRIVATE_KEY_SHADOWED)
err = gpg_error (GPG_ERR_TRUE);
wipememory (buf, buflen);
xfree (buf);
return err;
}
/* Return the key for the keygrip GRIP. The result is stored at
RESULT. This function extracts the key from the private key
database and returns it as an S-expression object as it is. On
failure an error code is returned and NULL stored at RESULT. */
gpg_error_t
agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
*result = NULL;
err = read_key_file (ctrl, grip, &s_skey, r_keymeta, NULL);
if (!err)
*result = s_skey;
return err;
}
/* Return the public key for the keygrip GRIP. The result is stored
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
at RESULT. If R_SSHORDER is not NULL the ordinal from the
Use-for-ssh attribute is stored at that address. */
static gpg_error_t
public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, int for_ssh, int *r_sshorder)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
nvc_t keymeta = NULL;
const char *algoname, *elems;
int npkey;
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
gcry_sexp_t uri_sexp, comment_sexp;
const char *uri, *comment;
size_t uri_length, comment_length;
int uri_intlen, comment_intlen;
membuf_t format_mb;
char *format;
void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
for comment + end-of-list. */
int argidx;
gcry_sexp_t list = NULL;
const char *s;
(void)ctrl;
*result = NULL;
if (r_sshorder)
*r_sshorder = 0;
err = read_key_file (ctrl, grip, &s_skey, for_ssh? &keymeta : NULL, NULL);
if (err)
return err;
if (for_ssh)
{
/* Use-for-ssh: yes */
int is_ssh = 0;
if (keymeta == NULL)
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
is_ssh = nvc_get_boolean (keymeta, "Use-for-ssh:");
nvc_release (keymeta);
keymeta = NULL;
if (!is_ssh)
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
if (r_sshorder)
*r_sshorder = is_ssh;
}
for (i=0; i < DIM (array); i++)
array[i] = NULL;
err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
array, DIM (array), &curve, &flags);
if (err)
{
gcry_sexp_release (s_skey);
return err;
}
uri = NULL;
uri_length = 0;
uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0);
if (uri_sexp)
uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length);
comment = NULL;
comment_length = 0;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
gcry_sexp_release (s_skey);
s_skey = NULL;
log_assert (sizeof (size_t) <= sizeof (void*));
init_membuf (&format_mb, 256);
argidx = 0;
put_membuf_printf (&format_mb, "(public-key(%s%%S%%S", algoname);
args[argidx++] = &curve;
args[argidx++] = &flags;
for (idx=0, s=elems; idx < npkey; idx++)
{
put_membuf_printf (&format_mb, "(%c %%m)", *s++);
log_assert (argidx < DIM (args));
args[argidx++] = &array[idx];
}
put_membuf_str (&format_mb, ")");
if (uri)
{
put_membuf_str (&format_mb, "(uri %b)");
log_assert (argidx+1 < DIM (args));
uri_intlen = (int)uri_length;
args[argidx++] = (void *)&uri_intlen;
args[argidx++] = (void *)&uri;
}
if (comment)
{
put_membuf_str (&format_mb, "(comment %b)");
log_assert (argidx+1 < DIM (args));
comment_intlen = (int)comment_length;
args[argidx++] = (void *)&comment_intlen;
args[argidx++] = (void *)&comment;
}
put_membuf (&format_mb, ")", 2);
log_assert (argidx < DIM (args));
args[argidx] = NULL;
format = get_membuf (&format_mb, NULL);
if (!format)
{
err = gpg_error_from_syserror ();
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
return err;
}
err = gcry_sexp_build_array (&list, NULL, format, args);
xfree (format);
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
if (!err)
*result = list;
return err;
}
gpg_error_t
agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result)
{
return public_key_from_file (ctrl, grip, result, 0, NULL);
}
gpg_error_t
agent_ssh_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result, int *r_order)
{
return public_key_from_file (ctrl, grip, result, 1, r_order);
}
/* Check whether the secret key identified by GRIP is available.
Returns 0 is the key is available. */
int
agent_key_available (ctrl_t ctrl, const unsigned char *grip)
{
int result;
char *fname;
char hexgrip[40+4+1];
ephemeral_private_key_t ek;
if (ctrl && ctrl->ephemeral_mode)
{
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN)
&& ek->keybuf && ek->keybuflen)
return 0;
return -1;
}
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
result = !gnupg_access (fname, R_OK)? 0 : -1;
xfree (fname);
return result;
}
/* Return the information about the secret key specified by the binary
keygrip GRIP. If the key is a shadowed one the shadow information
will be stored at the address R_SHADOW_INFO as an allocated
S-expression. */
gpg_error_t
agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype, unsigned char **r_shadow_info,
unsigned char **r_shadow_info_type)
{
gpg_error_t err;
unsigned char *buf;
size_t len;
int keytype;
(void)ctrl;
if (r_keytype)
*r_keytype = PRIVATE_KEY_UNKNOWN;
if (r_shadow_info)
*r_shadow_info = NULL;
{
gcry_sexp_t sexp;
err = read_key_file (ctrl, grip, &sexp, NULL, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
return gpg_error (GPG_ERR_NOT_FOUND);
else
return err;
}
err = make_canon_sexp (sexp, &buf, &len);
gcry_sexp_release (sexp);
if (err)
return err;
}
keytype = agent_private_key_type (buf);
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
break;
case PRIVATE_KEY_PROTECTED:
/* If we ever require it we could retrieve the comment fields
from such a key. */
break;
case PRIVATE_KEY_SHADOWED:
if (r_shadow_info)
{
const unsigned char *s;
size_t n;
err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
log_assert (n);
*r_shadow_info = xtrymalloc (n);
if (!*r_shadow_info)
err = gpg_error_from_syserror ();
else
memcpy (*r_shadow_info, s, n);
}
}
break;
default:
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
if (!err && r_keytype)
*r_keytype = keytype;
xfree (buf);
return err;
}
/* Delete the key with GRIP from the disk after having asked for
* confirmation using DESC_TEXT. If FORCE is set the function won't
* require a confirmation via Pinentry or warns if the key is also
* used by ssh. If ONLY_STUBS is set only stub keys (references to
* smartcards) will be affected.
*
* Common error codes are:
* GPG_ERR_NO_SECKEY
* GPG_ERR_KEY_ON_CARD
* GPG_ERR_NOT_CONFIRMED
* GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested.
*/
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip, int force, int only_stubs)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
unsigned char *buf = NULL;
size_t len;
char *desc_text_final = NULL;
char *comment = NULL;
ssh_control_file_t cf = NULL;
char hexgrip[40+4+1];
char *default_desc = NULL;
int key_type;
if (ctrl->ephemeral_mode)
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
err = read_key_file (ctrl, grip, &s_skey, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
goto leave;
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
goto leave;
key_type = agent_private_key_type (buf);
if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
switch (key_type)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
case PRIVATE_KEY_PROTECTED:
bin2hex (grip, 20, hexgrip);
if (!force)
{
if (!desc_text)
{
default_desc = xtryasprintf
(L_("Do you really want to delete the key identified by keygrip%%0A"
" %s%%0A %%C%%0A?"), hexgrip);
desc_text = default_desc;
}
/* Note, that we will take the comment as a C string for
display purposes; i.e. all stuff beyond a Nul character is
ignored. */
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
}
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
if (err)
goto leave;
err = agent_get_confirmation (ctrl, desc_text_final,
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
cf = ssh_open_control_file ();
if (cf)
{
if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
{
err = agent_get_confirmation
(ctrl,
L_("Warning: This key is also listed for use with SSH!\n"
"Deleting the key might remove your ability to "
"access remote machines."),
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
}
}
}
err = remove_key_file (grip);
break;
case PRIVATE_KEY_SHADOWED:
err = remove_key_file (grip);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
leave:
ssh_close_control_file (cf);
gcry_free (comment);
xfree (desc_text_final);
xfree (default_desc);
xfree (buf);
gcry_sexp_release (s_skey);
return err;
}
/* Write an S-expression formatted shadow key to our key storage.
Shadow key is created by an S-expression public key in PKBUF and
card's SERIALNO and the IDSTRING. With FORCE passed as true an
existing key with the given GRIP will get overwritten. */
gpg_error_t
agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force,
const char *dispserialno)
{
gpg_error_t err;
unsigned char *shadow_info;
unsigned char *shdkey;
size_t len;
/* Just in case some caller did not parse the stuff correctly, skip
* leading spaces. */
while (spacep (serialno))
serialno++;
while (spacep (keyid))
keyid++;
shadow_info = make_shadow_info (serialno, keyid);
if (!shadow_info)
return gpg_error_from_syserror ();
err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
xfree (shadow_info);
if (err)
{
log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
return err;
}
len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
err = agent_write_private_key (ctrl, grip, shdkey, len, force,
- serialno, keyid, dispserialno, 0);
+ serialno, keyid, dispserialno, 0, NULL);
xfree (shdkey);
if (err)
log_error ("error writing key: %s\n", gpg_strerror (err));
return err;
}
diff --git a/agent/genkey.c b/agent/genkey.c
index 0fb94350d..b1b692d61 100644
--- a/agent/genkey.c
+++ b/agent/genkey.c
@@ -1,712 +1,712 @@
/* genkey.c - Generate a keypair
* Copyright (C) 2002, 2003, 2004, 2007, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "agent.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
void
clear_ephemeral_keys (ctrl_t ctrl)
{
while (ctrl->ephemeral_keys)
{
ephemeral_private_key_t next = ctrl->ephemeral_keys->next;
if (ctrl->ephemeral_keys->keybuf)
{
wipememory (ctrl->ephemeral_keys->keybuf,
ctrl->ephemeral_keys->keybuflen);
xfree (ctrl->ephemeral_keys->keybuf);
}
xfree (ctrl->ephemeral_keys);
ctrl->ephemeral_keys = next;
}
}
/* Store the key either to a file, or in ctrl->ephemeral_mode in the
* session data. */
static gpg_error_t
store_key (ctrl_t ctrl, gcry_sexp_t private,
const char *passphrase, int force,
unsigned long s2k_count, time_t timestamp)
{
gpg_error_t err;
unsigned char *buf;
size_t len;
unsigned char grip[KEYGRIP_LEN];
if ( !gcry_pk_get_keygrip (private, grip) )
{
log_error ("can't calculate keygrip\n");
return gpg_error (GPG_ERR_GENERAL);
}
len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (len);
buf = gcry_malloc_secure (len);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
log_assert (len);
if (passphrase)
{
unsigned char *p;
err = agent_protect (buf, passphrase, &p, &len, s2k_count);
if (err)
goto leave;
xfree (buf);
buf = p;
}
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN))
break;
if (!ek)
{
ek = xtrycalloc (1, sizeof *ek);
if (!ek)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (ek->grip, grip, KEYGRIP_LEN);
ek->next = ctrl->ephemeral_keys;
ctrl->ephemeral_keys = ek;
}
if (ek->keybuf)
{
wipememory (ek->keybuf, ek->keybuflen);
xfree (ek->keybuf);
}
ek->keybuf = buf;
buf = NULL;
ek->keybuflen = len;
err = 0;
}
else
err = agent_write_private_key (ctrl, grip, buf, len, force,
- NULL, NULL, NULL, timestamp);
+ NULL, NULL, NULL, timestamp, NULL);
if (!err)
{
char hexgrip[2*KEYGRIP_LEN+1];
bin2hex (grip, KEYGRIP_LEN, hexgrip);
agent_write_status (ctrl, "KEYGRIP", hexgrip, NULL);
}
leave:
xfree (buf);
return err;
}
/* Count the number of non-alpha characters in S. Control characters
and non-ascii characters are not considered. */
static size_t
nonalpha_count (const char *s)
{
size_t n;
for (n=0; *s; s++)
if (isascii (*s) && ( isdigit (*s) || ispunct (*s) ))
n++;
return n;
}
/* Check PW against a list of pattern. Return 0 if PW does not match
these pattern. If CHECK_CONSTRAINTS_NEW_SYMKEY is set in flags and
--check-sym-passphrase-pattern has been configured, use the pattern
file from that option. */
static int
do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags)
{
gpg_error_t err = 0;
const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN);
estream_t stream_to_check_pattern = NULL;
const char *argv[10];
gpgrt_process_t proc;
int result, i;
const char *pattern;
char *patternfname;
(void)ctrl;
pattern = opt.check_passphrase_pattern;
if ((flags & CHECK_CONSTRAINTS_NEW_SYMKEY)
&& opt.check_sym_passphrase_pattern)
pattern = opt.check_sym_passphrase_pattern;
if (!pattern)
return 1; /* Oops - Assume password should not be used */
if (strchr (pattern, '/') || strchr (pattern, '\\')
|| (*pattern == '~' && pattern[1] == '/'))
patternfname = make_absfilename_try (pattern, NULL);
else
patternfname = make_filename_try (gnupg_sysconfdir (), pattern, NULL);
if (!patternfname)
{
log_error ("error making filename from '%s': %s\n",
pattern, gpg_strerror (gpg_error_from_syserror ()));
return 1; /* Do not pass the check. */
}
/* Make debugging a broken config easier by printing a useful error
* message. */
if (gnupg_access (patternfname, F_OK))
{
log_error ("error accessing '%s': %s\n",
patternfname, gpg_strerror (gpg_error_from_syserror ()));
xfree (patternfname);
return 1; /* Do not pass the check. */
}
i = 0;
argv[i++] = "--null";
argv[i++] = "--",
argv[i++] = patternfname,
argv[i] = NULL;
log_assert (i < sizeof argv);
if (gpgrt_process_spawn (pgmname, argv,
GPGRT_PROCESS_STDIN_PIPE,
NULL, &proc))
result = 1; /* Execute error - assume password should no be used. */
else
{
int status;
gpgrt_process_get_streams (proc, 0, &stream_to_check_pattern,
NULL, NULL);
es_set_binary (stream_to_check_pattern);
if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing to pipe: %s\n"), gpg_strerror (err));
result = 1; /* Error - assume password should not be used. */
}
else
es_fflush (stream_to_check_pattern);
es_fclose (stream_to_check_pattern);
gpgrt_process_wait (proc, 1);
gpgrt_process_ctl (proc, GPGRT_PROCESS_GET_EXIT_ID, &status);
if (status)
result = 1; /* Helper returned an error - probably a match. */
else
result = 0; /* Success; i.e. no match. */
gpgrt_process_release (proc);
}
xfree (patternfname);
return result;
}
static int
take_this_one_anyway (ctrl_t ctrl, const char *desc, const char *anyway_btn)
{
return agent_get_confirmation (ctrl, desc,
anyway_btn, L_("Enter new passphrase"), 0);
}
/* Check whether the passphrase PW is suitable. Returns 0 if the
* passphrase is suitable and true if it is not and the user should be
* asked to provide a different one. If FAILED_CONSTRAINT is set, a
* message describing the problem is returned at FAILED_CONSTRAINT.
* The FLAGS are:
* CHECK_CONSTRAINTS_NOT_EMPTY
* Do not allow an empty passphrase
* CHECK_CONSTRAINTS_NEW_SYMKEY
* Hint that the passphrase is used for a new symmetric key.
*/
int
check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags,
char **failed_constraint)
{
gpg_error_t err = 0;
unsigned int minlen = opt.min_passphrase_len;
unsigned int minnonalpha = opt.min_passphrase_nonalpha;
char *msg1 = NULL;
char *msg2 = NULL;
char *msg3 = NULL;
int no_empty = !!(flags & CHECK_CONSTRAINTS_NOT_EMPTY);
if (ctrl && ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return 0;
if (!pw)
pw = "";
/* The first check is to warn about an empty passphrase. */
if (!*pw)
{
const char *desc = (opt.enforce_passphrase_constraints || no_empty?
L_("You have not entered a passphrase!%0A"
"An empty passphrase is not allowed.") :
L_("You have not entered a passphrase - "
"this is in general a bad idea!%0A"
"Please confirm that you do not want to "
"have any protection on your key."));
err = 1;
if (failed_constraint)
{
if (opt.enforce_passphrase_constraints || no_empty)
*failed_constraint = xstrdup (desc);
else
err = take_this_one_anyway (ctrl, desc,
L_("Yes, protection is not needed"));
}
goto leave;
}
/* Now check the constraints and collect the error messages unless
in silent mode which returns immediately. */
if (utf8_charcount (pw, -1) < minlen )
{
if (!failed_constraint)
{
err = gpg_error (GPG_ERR_INV_PASSPHRASE);
goto leave;
}
msg1 = xtryasprintf
( ngettext ("A passphrase should be at least %u character long.",
"A passphrase should be at least %u characters long.",
minlen), minlen );
if (!msg1)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if (nonalpha_count (pw) < minnonalpha )
{
if (!failed_constraint)
{
err = gpg_error (GPG_ERR_INV_PASSPHRASE);
goto leave;
}
msg2 = xtryasprintf
( ngettext ("A passphrase should contain at least %u digit or%%0A"
"special character.",
"A passphrase should contain at least %u digits or%%0A"
"special characters.",
minnonalpha), minnonalpha );
if (!msg2)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* If configured check the passphrase against a list of known words
and pattern. The actual test is done by an external program.
The warning message is generic to give the user no hint on how to
circumvent this list. */
if (*pw
&& (opt.check_passphrase_pattern || opt.check_sym_passphrase_pattern)
&& do_check_passphrase_pattern (ctrl, pw, flags))
{
if (!failed_constraint)
{
err = gpg_error (GPG_ERR_INV_PASSPHRASE);
goto leave;
}
msg3 = xtryasprintf
(L_("A passphrase may not be a known term or match%%0A"
"certain pattern."));
if (!msg3)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if (failed_constraint && (msg1 || msg2 || msg3))
{
char *msg;
size_t n;
msg = strconcat
(L_("Warning: You have entered an insecure passphrase."),
"%0A%0A",
msg1? msg1 : "", msg1? "%0A" : "",
msg2? msg2 : "", msg2? "%0A" : "",
msg3? msg3 : "", msg3? "%0A" : "",
NULL);
if (!msg)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Strip a trailing "%0A". */
n = strlen (msg);
if (n > 3 && !strcmp (msg + n - 3, "%0A"))
msg[n-3] = 0;
err = 1;
if (opt.enforce_passphrase_constraints)
*failed_constraint = msg;
else
{
err = take_this_one_anyway (ctrl, msg, L_("Take this one anyway"));
xfree (msg);
}
}
leave:
xfree (msg1);
xfree (msg2);
xfree (msg3);
return err;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_compare_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Ask the user for a new passphrase using PROMPT. On success the
function returns 0 and store the passphrase at R_PASSPHRASE; if the
user opted not to use a passphrase NULL will be stored there. The
user needs to free the returned string. In case of an error and
error code is returned and NULL stored at R_PASSPHRASE. */
gpg_error_t
agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
char **r_passphrase)
{
gpg_error_t err;
const char *text1 = prompt;
const char *text2 = L_("Please re-enter this passphrase");
char *initial_errtext = NULL;
struct pin_entry_info_s *pi, *pi2;
*r_passphrase = NULL;
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
size_t size;
unsigned char *buffer;
err = pinentry_loopback (ctrl, "NEW_PASSPHRASE", &buffer, &size,
MAX_PASSPHRASE_LEN);
if (!err)
{
if (size)
{
buffer[size] = 0;
*r_passphrase = buffer;
}
else
*r_passphrase = NULL;
}
return err;
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
err = gpg_error_from_syserror ();
xfree (pi);
return err;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 3;
pi->with_qualitybar = 0;
pi->with_repeat = 1;
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 3;
pi2->check_cb = reenter_compare_cb;
pi2->check_cb_arg = pi->pin;
next_try:
err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0);
xfree (initial_errtext);
initial_errtext = NULL;
if (!err)
{
if (check_passphrase_constraints (ctrl, pi->pin, 0, &initial_errtext))
{
pi->failed_tries = 0;
pi2->failed_tries = 0;
goto next_try;
}
/* Unless the passphrase is empty or the pinentry told us that
it already did the repetition check, ask to confirm it. */
if (*pi->pin && !pi->repeat_okay)
{
err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered one did not match and the user did not
hit cancel. */
initial_errtext = xtrystrdup (L_("does not match - try again"));
if (initial_errtext)
goto next_try;
err = gpg_error_from_syserror ();
}
}
}
if (!err && *pi->pin)
{
/* User wants a passphrase. */
*r_passphrase = xtrystrdup (pi->pin);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
xfree (initial_errtext);
xfree (pi2);
xfree (pi);
return err;
}
/* Generate a new keypair according to the parameters given in
* KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase
* using the cache nonce. If NO_PROTECTION is true the key will not
* be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that
* passphrase will be used for the new key. If TIMESTAMP is not zero
* it will be recorded as creation date of the key (unless extended
* format is disabled). In ctrl_ephemeral_mode the key is stored in
* the session data and an identifier is returned using a status
* line. */
int
agent_genkey (ctrl_t ctrl, unsigned int flags,
const char *cache_nonce, time_t timestamp,
const char *keyparam, size_t keyparamlen,
const char *override_passphrase, membuf_t *outbuf)
{
gcry_sexp_t s_keyparam, s_key, s_private, s_public;
char *passphrase_buffer = NULL;
const char *passphrase;
int rc;
size_t len;
char *buf;
rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen);
if (rc)
{
log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc));
return gpg_error (GPG_ERR_INV_DATA);
}
/* Get the passphrase now, cause key generation may take a while. */
if (override_passphrase)
passphrase = override_passphrase;
else if ((flags & GENKEY_FLAG_NO_PROTECTION) || !cache_nonce)
passphrase = NULL;
else
{
passphrase_buffer = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
passphrase = passphrase_buffer;
}
if (passphrase || (flags & GENKEY_FLAG_NO_PROTECTION))
; /* No need to ask for a passphrase. */
else
{
rc = agent_ask_new_passphrase (ctrl,
L_("Please enter the passphrase to%0A"
"protect your new key"),
&passphrase_buffer);
if (rc)
{
gcry_sexp_release (s_keyparam);
return rc;
}
passphrase = passphrase_buffer;
}
rc = gcry_pk_genkey (&s_key, s_keyparam );
gcry_sexp_release (s_keyparam);
if (rc)
{
log_error ("key generation failed: %s\n", gpg_strerror (rc));
xfree (passphrase_buffer);
return rc;
}
/* break out the parts */
s_private = gcry_sexp_find_token (s_key, "private-key", 0);
if (!s_private)
{
log_error ("key generation failed: invalid return value\n");
gcry_sexp_release (s_key);
xfree (passphrase_buffer);
return gpg_error (GPG_ERR_INV_DATA);
}
s_public = gcry_sexp_find_token (s_key, "public-key", 0);
if (!s_public)
{
log_error ("key generation failed: invalid return value\n");
gcry_sexp_release (s_private);
gcry_sexp_release (s_key);
xfree (passphrase_buffer);
return gpg_error (GPG_ERR_INV_DATA);
}
gcry_sexp_release (s_key); s_key = NULL;
/* store the secret key */
if (opt.verbose)
log_info ("storing %sprivate key\n",
ctrl->ephemeral_mode?"ephemeral ":"");
rc = store_key (ctrl, s_private, passphrase, 0, ctrl->s2k_count, timestamp);
if (!rc && !ctrl->ephemeral_mode)
{
/* FIXME: or does it make sense to also cache passphrases in
* ephemeral mode using a dedicated cache? */
if (!cache_nonce)
{
char tmpbuf[12];
gcry_create_nonce (tmpbuf, 12);
cache_nonce = bin2hex (tmpbuf, 12, NULL);
}
if (cache_nonce
&& !(flags & GENKEY_FLAG_NO_PROTECTION)
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, ctrl->cache_ttl_opt_preset))
agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL);
if ((flags & GENKEY_FLAG_PRESET)
&& !(flags & GENKEY_FLAG_NO_PROTECTION))
{
unsigned char grip[20];
char hexgrip[40+1];
if (gcry_pk_get_keygrip (s_private, grip))
{
bin2hex(grip, 20, hexgrip);
rc = agent_put_cache (ctrl, hexgrip,
CACHE_MODE_ANY, passphrase,
ctrl->cache_ttl_opt_preset);
}
}
}
xfree (passphrase_buffer);
passphrase_buffer = NULL;
passphrase = NULL;
gcry_sexp_release (s_private);
if (rc)
{
gcry_sexp_release (s_public);
return rc;
}
/* return the public key */
if (DBG_CRYPTO)
log_debug ("returning public key\n");
len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (len);
buf = xtrymalloc (len);
if (!buf)
{
gpg_error_t tmperr = out_of_core ();
gcry_sexp_release (s_private);
gcry_sexp_release (s_public);
return tmperr;
}
len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len);
log_assert (len);
put_membuf (outbuf, buf, len);
gcry_sexp_release (s_public);
xfree (buf);
return 0;
}
/* Apply a new passphrase to the key S_SKEY and store it. If
PASSPHRASE_ADDR and *PASSPHRASE_ADDR are not NULL, use that
passphrase. If PASSPHRASE_ADDR is not NULL store a newly entered
passphrase at that address. */
gpg_error_t
agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr)
{
gpg_error_t err;
if (passphrase_addr && *passphrase_addr)
{
/* Take an empty string as request not to protect the key. */
err = store_key (ctrl, s_skey,
**passphrase_addr? *passphrase_addr:NULL, 1,
ctrl->s2k_count, 0);
}
else
{
char *pass = NULL;
if (passphrase_addr)
{
xfree (*passphrase_addr);
*passphrase_addr = NULL;
}
err = agent_ask_new_passphrase (ctrl,
L_("Please enter the new passphrase"),
&pass);
if (!err)
err = store_key (ctrl, s_skey, pass, 1, ctrl->s2k_count, 0);
if (!err && passphrase_addr)
*passphrase_addr = pass;
else
xfree (pass);
}
return err;
}
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
index c6450a20e..40e9aed76 100644
--- a/agent/protect-tool.c
+++ b/agent/protect-tool.c
@@ -1,844 +1,846 @@
/* protect-tool.c - A tool to test the secret key protection
* Copyright (C) 2002, 2003, 2004, 2006 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
#include <fcntl.h> /* for setmode() */
#endif
#define INCLUDED_BY_MAIN_MODULE 1
#include "agent.h"
#include "../common/i18n.h"
#include "../common/get-passphrase.h"
#include "../common/sysutils.h"
#include "../common/init.h"
enum cmd_and_opt_values
{
aNull = 0,
oVerbose = 'v',
oArmor = 'a',
oPassphrase = 'P',
oProtect = 'p',
oUnprotect = 'u',
oNoVerbose = 500,
oShadow,
oShowShadowInfo,
oShowKeygrip,
oS2Kcalibration,
oCanonical,
oStore,
oForce,
oHaveCert,
oNoFailOnExist,
oHomedir,
oPrompt,
oStatusMsg,
oDebugUseOCB,
oAgentProgram
};
struct rsa_secret_key_s
{
gcry_mpi_t n; /* public modulus */
gcry_mpi_t e; /* public exponent */
gcry_mpi_t d; /* exponent */
gcry_mpi_t p; /* prime p. */
gcry_mpi_t q; /* prime q. */
gcry_mpi_t u; /* inverse of p mod q. */
};
static int opt_armor;
static int opt_canonical;
static int opt_store;
static int opt_force;
static int opt_no_fail_on_exist;
static int opt_have_cert;
static const char *opt_passphrase;
static char *opt_prompt;
static int opt_status_msg;
static const char *opt_agent_program;
static char *get_passphrase (int promptno);
static void release_passphrase (char *pw);
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (oProtect, "protect", "protect a private key"),
ARGPARSE_c (oUnprotect, "unprotect", "unprotect a private key"),
ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"),
ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"),
ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""),
ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", "verbose"),
ARGPARSE_s_n (oArmor, "armor", "write output in advanced format"),
ARGPARSE_s_n (oCanonical, "canonical", "write output in canonical format"),
ARGPARSE_s_s (oPassphrase, "passphrase", "|STRING|use passphrase STRING"),
ARGPARSE_s_n (oHaveCert, "have-cert",
"certificate to export provided on STDIN"),
ARGPARSE_s_n (oStore, "store",
"store the created key in the appropriate place"),
ARGPARSE_s_n (oForce, "force",
"force overwriting"),
ARGPARSE_s_n (oNoFailOnExist, "no-fail-on-exist", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oPrompt, "prompt",
"|ESCSTRING|use ESCSTRING as prompt in pinentry"),
ARGPARSE_s_n (oStatusMsg, "enable-status-msg", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */
ARGPARSE_end ()
};
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "gpg-protect-tool (" GNUPG_NAME ")";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n");
break;
case 41: p = _("Syntax: gpg-protect-tool [options] [args]\n"
"Secret key maintenance tool\n");
break;
default: p = NULL;
}
return p;
}
/* static void */
/* print_mpi (const char *text, gcry_mpi_t a) */
/* { */
/* char *buf; */
/* void *bufaddr = &buf; */
/* int rc; */
/* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */
/* if (rc) */
/* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */
/* else */
/* { */
/* log_info ("%s: %s\n", text, buf); */
/* gcry_free (buf); */
/* } */
/* } */
static unsigned char *
make_canonical (const char *fname, const char *buf, size_t buflen)
{
int rc;
size_t erroff, len;
gcry_sexp_t sexp;
unsigned char *result;
rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
if (rc)
{
log_error ("invalid S-Expression in '%s' (off=%u): %s\n",
fname, (unsigned int)erroff, gpg_strerror (rc));
return NULL;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (len);
result = xmalloc (len);
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len);
log_assert (len);
gcry_sexp_release (sexp);
return result;
}
static char *
make_advanced (const unsigned char *buf, size_t buflen)
{
int rc;
size_t erroff, len;
gcry_sexp_t sexp;
char *result;
rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen);
if (rc)
{
log_error ("invalid canonical S-Expression (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
return NULL;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
log_assert (len);
result = xmalloc (len);
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
log_assert (len);
gcry_sexp_release (sexp);
return result;
}
static char *
read_file (const char *fname, size_t *r_length)
{
estream_t fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = es_stdin;
es_set_binary (fp);
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize);
else
buf = xrealloc (buf, bufsize);
nread = es_fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && es_ferror (fp))
{
log_error ("error reading '[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = es_fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (es_fileno (fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
es_fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (es_fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
es_fclose (fp);
xfree (buf);
return NULL;
}
es_fclose (fp);
}
*r_length = buflen;
return buf;
}
static unsigned char *
read_key (const char *fname)
{
char *buf;
size_t buflen;
unsigned char *key;
buf = read_file (fname, &buflen);
if (!buf)
return NULL;
if (buflen >= 4 && !memcmp (buf, "Key:", 4))
{
log_error ("Extended key format is not supported by this tool\n");
xfree (buf);
return NULL;
}
key = make_canonical (fname, buf, buflen);
xfree (buf);
return key;
}
static void
read_and_protect (const char *fname)
{
int rc;
unsigned char *key;
unsigned char *result;
size_t resultlen;
char *pw;
key = read_key (fname);
if (!key)
return;
pw = get_passphrase (1);
rc = agent_protect (key, pw, &result, &resultlen, 0);
release_passphrase (pw);
xfree (key);
if (rc)
{
log_error ("protecting the key failed: %s\n", gpg_strerror (rc));
return;
}
if (opt_armor)
{
char *p = make_advanced (result, resultlen);
xfree (result);
if (!p)
return;
result = (unsigned char*)p;
resultlen = strlen (p);
}
fwrite (result, resultlen, 1, stdout);
xfree (result);
}
static void
read_and_unprotect (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
unsigned char *key;
unsigned char *result;
size_t resultlen;
char *pw;
gnupg_isotime_t protected_at;
key = read_key (fname);
if (!key)
return;
err = agent_unprotect (ctrl, key, (pw=get_passphrase (1)),
protected_at, &result, &resultlen);
release_passphrase (pw);
xfree (key);
if (err)
{
if (opt_status_msg)
log_info ("[PROTECT-TOOL:] bad-passphrase\n");
log_error ("unprotecting the key failed: %s\n", gpg_strerror (err));
return;
}
if (opt.verbose)
{
if (*protected_at)
log_info ("key protection done at %.4s-%.2s-%.2s %.2s:%.2s:%s\n",
protected_at, protected_at+4, protected_at+6,
protected_at+9, protected_at+11, protected_at+13);
else
log_info ("key protection done at [unknown]\n");
}
err = fixup_when_ecc_private_key (result, &resultlen);
if (err)
{
log_error ("malformed key: %s\n", gpg_strerror (err));
return;
}
if (opt_armor)
{
char *p = make_advanced (result, resultlen);
xfree (result);
if (!p)
return;
result = (unsigned char*)p;
resultlen = strlen (p);
}
fwrite (result, resultlen, 1, stdout);
xfree (result);
}
static void
read_and_shadow (const char *fname)
{
int rc;
unsigned char *key;
unsigned char *result;
size_t resultlen;
unsigned char dummy_info[] = "(8:313233342:43)";
key = read_key (fname);
if (!key)
return;
rc = agent_shadow_key (key, dummy_info, &result);
xfree (key);
if (rc)
{
log_error ("shadowing the key failed: %s\n", gpg_strerror (rc));
return;
}
resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL);
log_assert (resultlen);
if (opt_armor)
{
char *p = make_advanced (result, resultlen);
xfree (result);
if (!p)
return;
result = (unsigned char*)p;
resultlen = strlen (p);
}
fwrite (result, resultlen, 1, stdout);
xfree (result);
}
static void
show_shadow_info (const char *fname)
{
int rc;
unsigned char *key;
const unsigned char *info;
size_t infolen;
key = read_key (fname);
if (!key)
return;
rc = agent_get_shadow_info (key, &info);
xfree (key);
if (rc)
{
log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
return;
}
infolen = gcry_sexp_canon_len (info, 0, NULL,NULL);
log_assert (infolen);
if (opt_armor)
{
char *p = make_advanced (info, infolen);
if (!p)
return;
fwrite (p, strlen (p), 1, stdout);
xfree (p);
}
else
fwrite (info, infolen, 1, stdout);
}
static void
show_file (const char *fname)
{
unsigned char *key;
size_t keylen;
char *p;
key = read_key (fname);
if (!key)
return;
keylen = gcry_sexp_canon_len (key, 0, NULL,NULL);
log_assert (keylen);
if (opt_canonical)
{
fwrite (key, keylen, 1, stdout);
}
else
{
p = make_advanced (key, keylen);
if (p)
{
fwrite (p, strlen (p), 1, stdout);
xfree (p);
}
}
xfree (key);
}
static void
show_keygrip (const char *fname)
{
unsigned char *key;
gcry_sexp_t private;
unsigned char grip[20];
int i;
key = read_key (fname);
if (!key)
return;
if (gcry_sexp_new (&private, key, 0, 0))
{
log_error ("gcry_sexp_new failed\n");
return;
}
xfree (key);
if (!gcry_pk_get_keygrip (private, grip))
{
log_error ("can't calculate keygrip\n");
return;
}
gcry_sexp_release (private);
for (i=0; i < 20; i++)
printf ("%02X", grip[i]);
putchar ('\n');
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int cmd = 0;
const char *fname;
ctrl_t ctrl;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("gpg-protect-tool", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= ARGPARSE_FLAG_KEEP;
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oArmor: opt_armor=1; break;
case oCanonical: opt_canonical=1; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt_agent_program = pargs.r.ret_str; break;
case oProtect: cmd = oProtect; break;
case oUnprotect: cmd = oUnprotect; break;
case oShadow: cmd = oShadow; break;
case oShowShadowInfo: cmd = oShowShadowInfo; break;
case oShowKeygrip: cmd = oShowKeygrip; break;
case oS2Kcalibration: cmd = oS2Kcalibration; break;
case oPassphrase: opt_passphrase = pargs.r.ret_str; break;
case oStore: opt_store = 1; break;
case oForce: opt_force = 1; break;
case oNoFailOnExist: opt_no_fail_on_exist = 1; break;
case oHaveCert: opt_have_cert = 1; break;
case oPrompt: opt_prompt = pargs.r.ret_str; break;
case oStatusMsg: opt_status_msg = 1; break;
case oDebugUseOCB: /* dummy */; break;
default: pargs.err = ARGPARSE_PRINT_ERROR; break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (log_get_errorcount (0))
exit (2);
fname = "-";
if (argc == 1)
fname = *argv;
else if (argc > 1)
gpgrt_usage (1);
/* Allocate an CTRL object. An empty object should be sufficient. */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno));
agent_exit (1);
}
/* Set the information which can't be taken from envvars. */
gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT,
opt.verbose,
opt_agent_program,
NULL, NULL, NULL);
if (opt_prompt)
opt_prompt = percent_plus_unescape (opt_prompt, 0);
if (cmd == oProtect)
read_and_protect (fname);
else if (cmd == oUnprotect)
read_and_unprotect (ctrl, fname);
else if (cmd == oShadow)
read_and_shadow (fname);
else if (cmd == oShowShadowInfo)
show_shadow_info (fname);
else if (cmd == oShowKeygrip)
show_keygrip (fname);
else if (cmd == oS2Kcalibration)
{
if (!opt.verbose)
opt.verbose++; /* We need to see something. */
get_standard_s2k_count ();
}
else
show_file (fname);
xfree (ctrl);
agent_exit (0);
return 8; /*NOTREACHED*/
}
void
agent_exit (int rc)
{
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* Return the passphrase string and ask the agent if it has not been
set from the command line PROMPTNO select the prompt to display:
0 = default
1 = taken from the option --prompt
2 = for unprotecting a pkcs#12 object
3 = for protecting a new pkcs#12 object
4 = for protecting an imported pkcs#12 in our system
*/
static char *
get_passphrase (int promptno)
{
char *pw;
int err;
const char *desc;
char *orig_codeset;
int repeat = 0;
if (opt_passphrase)
return xstrdup (opt_passphrase);
orig_codeset = i18n_switchto_utf8 ();
if (promptno == 1 && opt_prompt)
{
desc = opt_prompt;
}
else if (promptno == 2)
{
desc = _("Please enter the passphrase to unprotect the "
"PKCS#12 object.");
}
else if (promptno == 3)
{
desc = _("Please enter the passphrase to protect the "
"new PKCS#12 object.");
repeat = 1;
}
else if (promptno == 4)
{
desc = _("Please enter the passphrase to protect the "
"imported object within the GnuPG system.");
repeat = 1;
}
else
desc = _("Please enter the passphrase or the PIN\n"
"needed to complete this operation.");
i18n_switchback (orig_codeset);
err = gnupg_get_passphrase (NULL, NULL, _("Passphrase:"), desc,
repeat, repeat, 1, &pw);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
log_info (_("cancelled\n"));
else
log_error (_("error while asking for the passphrase: %s\n"),
gpg_strerror (err));
agent_exit (0);
}
log_assert (pw);
return pw;
}
static void
release_passphrase (char *pw)
{
if (pw)
{
wipememory (pw, strlen (pw));
xfree (pw);
}
}
/* Stub function. */
int
agent_key_available (ctrl_t ctrl, const unsigned char *grip)
{
(void)ctrl;
(void)grip;
return -1; /* Not available. */
}
char *
agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode)
{
(void)ctrl;
(void)key;
(void)cache_mode;
return NULL;
}
gpg_error_t
agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *initial_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode)
{
gpg_error_t err;
unsigned char *passphrase;
size_t size;
(void)ctrl;
(void)desc_text;
(void)prompt_text;
(void)initial_errtext;
(void)keyinfo;
(void)cache_mode;
*pininfo->pin = 0; /* Reset the PIN. */
passphrase = get_passphrase (0);
size = strlen (passphrase);
if (size >= pininfo->max_length)
{
xfree (passphrase);
return gpg_error (GPG_ERR_TOO_LARGE);
}
memcpy (&pininfo->pin, passphrase, size);
xfree (passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
err = pininfo->check_cb (pininfo);
}
else
err = 0;
return err;
}
/* Replacement for the function in findkey.c. Here we write the key
* to stdout. */
gpg_error_t
agent_write_private_key (ctrl_t ctrl, const unsigned char *grip,
const void *buffer, size_t length, int force,
const char *serialno, const char *keyref,
- const char *dispserialno, time_t timestamp)
+ const char *dispserialno, time_t timestamp,
+ const char *linkattr)
{
char hexgrip[40+4+1];
char *p;
(void)ctrl;
(void)force;
(void)serialno;
(void)keyref;
(void)timestamp;
(void)dispserialno;
+ (void)linkattr;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
p = make_advanced (buffer, length);
if (p)
{
printf ("# Begin dump of %s\n%s%s# End dump of %s\n",
hexgrip, p, (*p && p[strlen(p)-1] == '\n')? "":"\n", hexgrip);
xfree (p);
}
return 0;
}
diff --git a/common/sexputil.c b/common/sexputil.c
index fcd15ebc6..83c3176ea 100644
--- a/common/sexputil.c
+++ b/common/sexputil.c
@@ -1,1239 +1,1239 @@
/* sexputil.c - Utility functions for S-expressions.
* Copyright (C) 2005, 2007, 2009 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* This file implements a few utility functions useful when working
with canonical encrypted S-expressions (i.e. not the S-exprssion
objects from libgcrypt). */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "util.h"
#include "tlv.h"
#include "sexp-parse.h"
#include "openpgpdefs.h" /* for pubkey_algo_t */
/* Return a malloced string with the S-expression CANON in advanced
format. Returns NULL on error. */
static char *
sexp_to_string (gcry_sexp_t sexp)
{
size_t n;
char *result;
if (!sexp)
return NULL;
n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
if (!n)
return NULL;
result = xtrymalloc (n);
if (!result)
return NULL;
n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, n);
if (!n)
BUG ();
return result;
}
/* Return a malloced string with the S-expression CANON in advanced
format. Returns NULL on error. */
char *
canon_sexp_to_string (const unsigned char *canon, size_t canonlen)
{
size_t n;
gcry_sexp_t sexp;
char *result;
n = gcry_sexp_canon_len (canon, canonlen, NULL, NULL);
if (!n)
return NULL;
if (gcry_sexp_sscan (&sexp, NULL, canon, n))
return NULL;
result = sexp_to_string (sexp);
gcry_sexp_release (sexp);
return result;
}
/* Print the canonical encoded S-expression in SEXP in advanced
format. SEXPLEN may be passed as 0 is SEXP is known to be valid.
With TEXT of NULL print just the raw S-expression, with TEXT just
an empty string, print a trailing linefeed, otherwise print an
entire debug line. */
void
log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen)
{
if (text && *text)
log_debug ("%s ", text);
if (sexp)
{
char *buf = canon_sexp_to_string (sexp, sexplen);
log_printf ("%s", buf? buf : "[invalid S-expression]");
xfree (buf);
}
if (text)
log_printf ("\n");
}
/* Print the gcrypt S-expression SEXP in advanced format. With TEXT
of NULL print just the raw S-expression, with TEXT just an empty
string, print a trailing linefeed, otherwise print an entire debug
line. */
void
log_printsexp (const char *text, gcry_sexp_t sexp)
{
if (text && *text)
log_debug ("%s ", text);
if (sexp)
{
char *buf = sexp_to_string (sexp);
log_printf ("%s", buf? buf : "[invalid S-expression]");
xfree (buf);
}
if (text)
log_printf ("\n");
}
/* Helper function to create a canonical encoded S-expression from a
Libgcrypt S-expression object. The function returns 0 on success
and the malloced canonical S-expression is stored at R_BUFFER and
the allocated length at R_BUFLEN. On error an error code is
returned and (NULL, 0) stored at R_BUFFER and R_BUFLEN. If the
allocated buffer length is not required, NULL by be used for
R_BUFLEN. */
gpg_error_t
make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen)
{
size_t len;
unsigned char *buf;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;;
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
if (!len)
return gpg_error (GPG_ERR_BUG);
- buf = xtrymalloc (len);
+ buf = gcry_is_secure (sexp)? xtrymalloc_secure (len) : xtrymalloc (len);
if (!buf)
return gpg_error_from_syserror ();
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len);
if (!len)
return gpg_error (GPG_ERR_BUG);
*r_buffer = buf;
if (r_buflen)
*r_buflen = len;
return 0;
}
/* Same as make_canon_sexp but pad the buffer to multiple of 64
bits. If SECURE is set, secure memory will be allocated. */
gpg_error_t
make_canon_sexp_pad (gcry_sexp_t sexp, int secure,
unsigned char **r_buffer, size_t *r_buflen)
{
size_t len;
unsigned char *buf;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;;
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
if (!len)
return gpg_error (GPG_ERR_BUG);
len += (8 - len % 8) % 8;
buf = secure? xtrycalloc_secure (1, len) : xtrycalloc (1, len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len))
return gpg_error (GPG_ERR_BUG);
*r_buffer = buf;
if (r_buflen)
*r_buflen = len;
return 0;
}
/* Return the so called "keygrip" which is the SHA-1 hash of the
public key parameters expressed in a way dependent on the algorithm.
KEY is expected to be an canonical encoded S-expression with a
public or private key. KEYLEN is the length of that buffer.
GRIP must be at least 20 bytes long. On success 0 is returned, on
error an error code. */
gpg_error_t
keygrip_from_canon_sexp (const unsigned char *key, size_t keylen,
unsigned char *grip)
{
gpg_error_t err;
gcry_sexp_t sexp;
if (!grip)
return gpg_error (GPG_ERR_INV_VALUE);
err = gcry_sexp_sscan (&sexp, NULL, (const char *)key, keylen);
if (err)
return err;
if (!gcry_pk_get_keygrip (sexp, grip))
err = gpg_error (GPG_ERR_INTERNAL);
gcry_sexp_release (sexp);
return err;
}
/* Compare two simple S-expressions like "(3:foo)". Returns 0 if they
are identical or !0 if they are not. Note that this function can't
be used for sorting. */
int
cmp_simple_canon_sexp (const unsigned char *a_orig,
const unsigned char *b_orig)
{
const char *a = (const char *)a_orig;
const char *b = (const char *)b_orig;
unsigned long n1, n2;
char *endp;
if (!a && !b)
return 0; /* Both are NULL, they are identical. */
if (!a || !b)
return 1; /* One is NULL, they are not identical. */
if (*a != '(' || *b != '(')
log_bug ("invalid S-exp in cmp_simple_canon_sexp\n");
a++;
n1 = strtoul (a, &endp, 10);
a = endp;
b++;
n2 = strtoul (b, &endp, 10);
b = endp;
if (*a != ':' || *b != ':' )
log_bug ("invalid S-exp in cmp_simple_canon_sexp\n");
if (n1 != n2)
return 1; /* Not the same. */
for (a++, b++; n1; n1--, a++, b++)
if (*a != *b)
return 1; /* Not the same. */
return 0;
}
/* Helper for cmp_canon_sexp. */
static int
cmp_canon_sexp_def_tcmp (void *ctx, int depth,
const unsigned char *aval, size_t alen,
const unsigned char *bval, size_t blen)
{
(void)ctx;
(void)depth;
if (alen > blen)
return 1;
else if (alen < blen)
return -1;
else
return memcmp (aval, bval, alen);
}
/* Compare the two canonical encoded s-expressions A with maximum
* length ALEN and B with maximum length BLEN.
*
* Returns 0 if they match.
*
* If TCMP is NULL, this is not different really different from a
* memcmp but does not consider any garbage after the last closing
* parentheses.
*
* If TCMP is not NULL, it is expected to be a function to compare the
* values of each token. TCMP is called for each token while parsing
* the s-expressions until TCMP return a non-zero value. Here the CTX
* receives the provided value TCMPCTX, DEPTH is the number of
* currently open parentheses and (AVAL,ALEN) and (BVAL,BLEN) the
* values of the current token. TCMP needs to return zero to indicate
* that the tokens match. */
int
cmp_canon_sexp (const unsigned char *a, size_t alen,
const unsigned char *b, size_t blen,
int (*tcmp)(void *ctx, int depth,
const unsigned char *aval, size_t avallen,
const unsigned char *bval, size_t bvallen),
void *tcmpctx)
{
const unsigned char *a_buf, *a_tok;
const unsigned char *b_buf, *b_tok;
size_t a_buflen, a_toklen;
size_t b_buflen, b_toklen;
int a_depth, b_depth, ret;
if ((!a && !b) || (!alen && !blen))
return 0; /* Both are NULL, they are identical. */
if (!a || !b)
return !!a - !!b; /* One is NULL, they are not identical. */
if (*a != '(' || *b != '(')
log_bug ("invalid S-exp in %s\n", __func__);
if (!tcmp)
tcmp = cmp_canon_sexp_def_tcmp;
a_depth = 0;
a_buf = a;
a_buflen = alen;
b_depth = 0;
b_buf = b;
b_buflen = blen;
for (;;)
{
if (parse_sexp (&a_buf, &a_buflen, &a_depth, &a_tok, &a_toklen))
return -1; /* A is invalid. */
if (parse_sexp (&b_buf, &b_buflen, &b_depth, &b_tok, &b_toklen))
return -1; /* B is invalid. */
if (!a_depth && !b_depth)
return 0; /* End of both expressions - they match. */
if (a_depth != b_depth)
return a_depth - b_depth; /* Not the same structure */
if (!a_tok && !b_tok)
; /* parens */
else if (a_tok && b_tok)
{
ret = tcmp (tcmpctx, a_depth, a_tok, a_toklen, b_tok, b_toklen);
if (ret)
return ret; /* Mismatch */
}
else /* One has a paren other has not. */
return !!a_tok - !!b_tok;
}
}
/* Create a simple S-expression from the hex string at LINE. Returns
a newly allocated buffer with that canonical encoded S-expression
or NULL in case of an error. On return the number of characters
scanned in LINE will be stored at NSCANNED. This functions stops
converting at the first character not representing a hexdigit. Odd
numbers of hex digits are allowed; a leading zero is then
assumed. If no characters have been found, NULL is returned.*/
unsigned char *
make_simple_sexp_from_hexstr (const char *line, size_t *nscanned)
{
size_t n, len;
const char *s;
unsigned char *buf;
unsigned char *p;
char numbuf[50], *numbufp;
size_t numbuflen;
for (n=0, s=line; hexdigitp (s); s++, n++)
;
if (nscanned)
*nscanned = n;
if (!n)
return NULL;
len = ((n+1) & ~0x01)/2;
numbufp = smklen (numbuf, sizeof numbuf, len, &numbuflen);
buf = xtrymalloc (1 + numbuflen + len + 1 + 1);
if (!buf)
return NULL;
buf[0] = '(';
p = (unsigned char *)stpcpy ((char *)buf+1, numbufp);
s = line;
if ((n&1))
{
*p++ = xtoi_1 (s);
s++;
n--;
}
for (; n > 1; n -=2, s += 2)
*p++ = xtoi_2 (s);
*p++ = ')';
*p = 0; /* (Not really needed.) */
return buf;
}
/* Return the hash algorithm from a KSBA sig-val. SIGVAL is a
canonical encoded S-expression. Return 0 if the hash algorithm is
not encoded in SIG-VAL or it is not supported by libgcrypt. */
int
hash_algo_from_sigval (const unsigned char *sigval)
{
const unsigned char *s = sigval;
size_t n;
int depth;
char buffer[50];
if (!s || *s != '(')
return 0; /* Invalid S-expression. */
s++;
n = snext (&s);
if (!n)
return 0; /* Invalid S-expression. */
if (!smatch (&s, n, "sig-val"))
return 0; /* Not a sig-val. */
if (*s != '(')
return 0; /* Invalid S-expression. */
s++;
/* Skip over the algo+parameter list. */
depth = 1;
if (sskip (&s, &depth) || depth)
return 0; /* Invalid S-expression. */
if (*s != '(')
return 0; /* No further list. */
/* Check whether this is (hash ALGO). */
s++;
n = snext (&s);
if (!n)
return 0; /* Invalid S-expression. */
if (!smatch (&s, n, "hash"))
return 0; /* Not a "hash" keyword. */
n = snext (&s);
if (!n || n+1 >= sizeof (buffer))
return 0; /* Algorithm string is missing or too long. */
memcpy (buffer, s, n);
buffer[n] = 0;
return gcry_md_map_name (buffer);
}
/* Create a public key S-expression for an RSA public key from the
modulus M with length MLEN and the public exponent E with length
ELEN. Returns a newly allocated buffer of NULL in case of a memory
allocation problem. If R_LEN is not NULL, the length of the
canonical S-expression is stored there. */
unsigned char *
make_canon_sexp_from_rsa_pk (const void *m_arg, size_t mlen,
const void *e_arg, size_t elen,
size_t *r_len)
{
const unsigned char *m = m_arg;
const unsigned char *e = e_arg;
int m_extra = 0;
int e_extra = 0;
char mlen_str[35];
char elen_str[35];
unsigned char *keybuf, *p;
const char part1[] = "(10:public-key(3:rsa(1:n";
const char part2[] = ")(1:e";
const char part3[] = ")))";
/* Remove leading zeroes. */
for (; mlen && !*m; mlen--, m++)
;
for (; elen && !*e; elen--, e++)
;
/* Insert a leading zero if the number would be zero or interpreted
as negative. */
if (!mlen || (m[0] & 0x80))
m_extra = 1;
if (!elen || (e[0] & 0x80))
e_extra = 1;
/* Build the S-expression. */
snprintf (mlen_str, sizeof mlen_str, "%u:", (unsigned int)mlen+m_extra);
snprintf (elen_str, sizeof elen_str, "%u:", (unsigned int)elen+e_extra);
keybuf = xtrymalloc (strlen (part1) + strlen (mlen_str) + mlen + m_extra
+ strlen (part2) + strlen (elen_str) + elen + e_extra
+ strlen (part3) + 1);
if (!keybuf)
return NULL;
p = stpcpy (keybuf, part1);
p = stpcpy (p, mlen_str);
if (m_extra)
*p++ = 0;
memcpy (p, m, mlen);
p += mlen;
p = stpcpy (p, part2);
p = stpcpy (p, elen_str);
if (e_extra)
*p++ = 0;
memcpy (p, e, elen);
p += elen;
p = stpcpy (p, part3);
if (r_len)
*r_len = p - keybuf;
return keybuf;
}
/* Return the parameters of a public RSA key expressed as an
canonical encoded S-expression. */
gpg_error_t
get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
unsigned char const **r_n, size_t *r_nlen,
unsigned char const **r_e, size_t *r_elen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
*r_n = NULL;
*r_nlen = 0;
*r_e = NULL;
*r_elen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || !((toklen == 10 && !memcmp ("public-key", tok, toklen))
|| (toklen == 11 && !memcmp ("private-key", tok, toklen))))
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen))
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!rsa_n || !rsa_n_len || !rsa_e || !rsa_e_len)
return gpg_error (GPG_ERR_BAD_PUBKEY);
*r_n = rsa_n;
*r_nlen = rsa_n_len;
*r_e = rsa_e;
*r_elen = rsa_e_len;
return 0;
}
/* Return the public key parameter Q of a public RSA or ECC key
* expressed as an canonical encoded S-expression. */
gpg_error_t
get_ecc_q_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
unsigned char const **r_q, size_t *r_qlen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
const unsigned char *ecc_q = NULL;
size_t ecc_q_len = 0;
*r_q = NULL;
*r_qlen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen))
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 3 && !memcmp ("ecc", tok, toklen))
;
else if (tok && toklen == 5 && (!memcmp ("ecdsa", tok, toklen)
|| !memcmp ("eddsa", tok, toklen)))
;
else
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!ecc_q || !ecc_q_len)
return gpg_error (GPG_ERR_BAD_PUBKEY);
*r_q = ecc_q;
*r_qlen = ecc_q_len;
return 0;
}
/* Return an uncompressed point (X,Y) in P at R_BUF as a malloced
* buffer with its byte length stored at R_BUFLEN. May not be used
* for sensitive data. */
static gpg_error_t
ec2os (gcry_mpi_t x, gcry_mpi_t y, gcry_mpi_t p,
unsigned char **r_buf, unsigned int *r_buflen)
{
gpg_error_t err;
int pbytes = (mpi_get_nbits (p)+7)/8;
size_t n;
unsigned char *buf, *ptr;
*r_buf = NULL;
*r_buflen = 0;
buf = xtrymalloc (1 + 2*pbytes);
if (!buf)
return gpg_error_from_syserror ();
*buf = 04; /* Uncompressed point. */
ptr = buf+1;
err = gcry_mpi_print (GCRYMPI_FMT_USG, ptr, pbytes, &n, x);
if (err)
{
xfree (buf);
return err;
}
if (n < pbytes)
{
memmove (ptr+(pbytes-n), ptr, n);
memset (ptr, 0, (pbytes-n));
}
ptr += pbytes;
err = gcry_mpi_print (GCRYMPI_FMT_USG, ptr, pbytes, &n, y);
if (err)
{
xfree (buf);
return err;
}
if (n < pbytes)
{
memmove (ptr+(pbytes-n), ptr, n);
memset (ptr, 0, (pbytes-n));
}
*r_buf = buf;
*r_buflen = 1 + 2*pbytes;
return 0;
}
/* Convert the ECC parameter Q in the canonical s-expression
* (KEYDATA,KEYDATALEN) to uncompressed form. On success and if a
* conversion was done, the new canonical encoded s-expression is
* returned at (R_NEWKEYDAT,R_NEWKEYDATALEN); if a conversion was not
* required (NULL,0) is stored there. On error an error code is
* returned. The function may take any kind of key but will only do
* the conversion for ECC curves where compression is supported. */
gpg_error_t
uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char **r_newkeydata,
size_t *r_newkeydatalen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen, n;
int depth, last_depth1, last_depth2;
const unsigned char *q_ptr; /* Points to the value of "q". */
size_t q_ptrlen; /* Remaining length in KEYDATA. */
size_t q_toklen; /* Q's length including prefix. */
const unsigned char *curve_ptr; /* Points to the value of "curve". */
size_t curve_ptrlen; /* Remaining length in KEYDATA. */
gcry_mpi_t x, y; /* Point Q */
gcry_mpi_t p, a, b; /* Curve parameters. */
gcry_mpi_t x3, t, p1_4; /* Helper */
int y_bit;
unsigned char *qvalue; /* Q in uncompressed form. */
unsigned int qvaluelen;
unsigned char *dst; /* Helper */
char lenstr[35]; /* Helper for a length prefix. */
*r_newkeydata = NULL;
*r_newkeydatalen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok)
return gpg_error (GPG_ERR_BAD_PUBKEY);
else if (toklen == 10 && !memcmp ("public-key", tok, toklen))
;
else if (toklen == 11 && !memcmp ("private-key", tok, toklen))
;
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
;
else
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 3 && !memcmp ("ecc", tok, toklen))
;
else if (tok && toklen == 5 && !memcmp ("ecdsa", tok, toklen))
;
else
return 0; /* Other algo - no need for conversion. */
last_depth1 = depth;
q_ptr = curve_ptr = NULL;
q_ptrlen = 0; /*(silence cc warning)*/
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1 && *tok == 'q' && !q_ptr)
{
q_ptr = buf;
q_ptrlen = buflen;
}
else if (tok && toklen == 5 && !memcmp (tok, "curve", 5) && !curve_ptr)
{
curve_ptr = buf;
curve_ptrlen = buflen;
}
if (q_ptr && curve_ptr)
break; /* We got all what we need. */
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!q_ptr)
return 0; /* No Q - nothing to do. */
/* Get Q's value and check whether uncompressing is at all required. */
buf = q_ptr;
buflen = q_ptrlen;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (toklen < 2 || !(*tok == 0x02 || *tok == 0x03))
return 0; /* Invalid length or not compressed. */
q_toklen = buf - q_ptr; /* We want the length with the prefix. */
/* Put the x-coordinate of q into X and remember the y bit */
y_bit = (*tok == 0x03);
err = gcry_mpi_scan (&x, GCRYMPI_FMT_USG, tok+1, toklen-1, NULL);
if (err)
return err;
/* For uncompressing we need to know the curve. */
if (!curve_ptr)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_INV_CURVE);
}
buf = curve_ptr;
buflen = curve_ptrlen;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
{
gcry_mpi_release (x);
return err;
}
{
char name[50];
gcry_sexp_t curveparam;
if (toklen + 1 > sizeof name)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_TOO_LARGE);
}
mem2str (name, tok, toklen+1);
curveparam = gcry_pk_get_param (GCRY_PK_ECC, name);
if (!curveparam)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
err = gcry_sexp_extract_param (curveparam, NULL, "pab", &p, &a, &b, NULL);
gcry_sexp_release (curveparam);
if (err)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_INTERNAL);
}
}
if (!mpi_test_bit (p, 1))
{
/* No support for point compression for this curve. */
gcry_mpi_release (x);
gcry_mpi_release (p);
gcry_mpi_release (a);
gcry_mpi_release (b);
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
/*
* Recover Y. The Weierstrass curve: y^2 = x^3 + a*x + b
*/
x3 = mpi_new (0);
t = mpi_new (0);
p1_4 = mpi_new (0);
y = mpi_new (0);
/* Compute right hand side. */
mpi_powm (x3, x, GCRYMPI_CONST_THREE, p);
mpi_mul (t, a, x);
mpi_mod (t, t, p);
mpi_add (t, t, b);
mpi_mod (t, t, p);
mpi_add (t, t, x3);
mpi_mod (t, t, p);
/*
* When p mod 4 = 3, modular square root of A can be computed by
* A^((p+1)/4) mod p
*/
/* Compute (p+1)/4 into p1_4 */
mpi_rshift (p1_4, p, 2);
mpi_add_ui (p1_4, p1_4, 1);
mpi_powm (y, t, p1_4, p);
if (y_bit != mpi_test_bit (y, 0))
mpi_sub (y, p, y);
gcry_mpi_release (p1_4);
gcry_mpi_release (t);
gcry_mpi_release (x3);
gcry_mpi_release (a);
gcry_mpi_release (b);
err = ec2os (x, y, p, &qvalue, &qvaluelen);
gcry_mpi_release (x);
gcry_mpi_release (y);
gcry_mpi_release (p);
if (err)
return err;
snprintf (lenstr, sizeof lenstr, "%u:", (unsigned int)qvaluelen);
/* Note that for simplicity we do not subtract the old length of Q
* for the new buffer. */
*r_newkeydata = xtrymalloc (qvaluelen + strlen(lenstr) + qvaluelen);
if (!*r_newkeydata)
return gpg_error_from_syserror ();
dst = *r_newkeydata;
n = q_ptr - keydata;
memcpy (dst, keydata, n); /* Copy first part of original data. */
dst += n;
n = strlen (lenstr);
memcpy (dst, lenstr, n); /* Copy new prefix of Q's value. */
dst += n;
memcpy (dst, qvalue, qvaluelen); /* Copy new value of Q. */
dst += qvaluelen;
log_assert (q_toklen < q_ptrlen);
n = q_ptrlen - q_toklen;
memcpy (dst, q_ptr + q_toklen, n);/* Copy rest of original data. */
dst += n;
*r_newkeydatalen = dst - *r_newkeydata;
xfree (qvalue);
return 0;
}
/* Return the algo of a public KEY of SEXP. */
int
get_pk_algo_from_key (gcry_sexp_t key)
{
gcry_sexp_t list;
const char *s;
size_t n;
char algoname[10];
int algo = 0;
list = gcry_sexp_nth (key, 1);
if (!list)
goto out;
s = gcry_sexp_nth_data (list, 0, &n);
if (!s)
goto out;
if (n >= sizeof (algoname))
goto out;
memcpy (algoname, s, n);
algoname[n] = 0;
algo = gcry_pk_map_name (algoname);
if (algo == GCRY_PK_ECC)
{
gcry_sexp_t l1;
int i;
l1 = gcry_sexp_find_token (list, "flags", 0);
for (i = l1 ? gcry_sexp_length (l1)-1 : 0; i > 0; i--)
{
s = gcry_sexp_nth_data (l1, i, &n);
if (!s)
continue; /* Not a data element. */
if (n == 5 && !memcmp (s, "eddsa", 5))
{
algo = GCRY_PK_EDDSA;
break;
}
}
gcry_sexp_release (l1);
l1 = gcry_sexp_find_token (list, "curve", 0);
s = gcry_sexp_nth_data (l1, 1, &n);
if (n == 5 && !memcmp (s, "Ed448", 5))
algo = GCRY_PK_EDDSA;
gcry_sexp_release (l1);
}
out:
gcry_sexp_release (list);
return algo;
}
/* This is a variant of get_pk_algo_from_key but takes an canonical
* encoded S-expression as input. Returns a GCRYPT public key
* identiier or 0 on error. */
int
get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen)
{
gcry_sexp_t sexp;
int algo;
if (gcry_sexp_sscan (&sexp, NULL, keydata, keydatalen))
return 0;
algo = get_pk_algo_from_key (sexp);
gcry_sexp_release (sexp);
return algo;
}
/* Given the public key S_PKEY, return a new buffer with a descriptive
* string for its algorithm. This function may return NULL on memory
* error. If R_ALGOID is not NULL the gcrypt algo id is stored there. */
char *
pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid)
{
const char *prefix;
gcry_sexp_t l1;
char *algoname;
int algo;
char *result;
if (r_algoid)
*r_algoid = 0;
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (s_pkey, "private-key", 0);
if (!l1)
return xtrystrdup ("E_no_key");
{
gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l_tmp;
}
algoname = gcry_sexp_nth_string (l1, 0);
gcry_sexp_release (l1);
if (!algoname)
return xtrystrdup ("E_no_algo");
algo = gcry_pk_map_name (algoname);
switch (algo)
{
case GCRY_PK_RSA: prefix = "rsa"; break;
case GCRY_PK_ELG: prefix = "elg"; break;
case GCRY_PK_DSA: prefix = "dsa"; break;
case GCRY_PK_ECC: prefix = ""; break;
default: prefix = NULL; break;
}
if (prefix && *prefix)
result = xtryasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey));
else if (prefix)
{
const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL);
const char *name = openpgp_oid_or_name_to_curve (curve, 0);
if (name)
result = xtrystrdup (name);
else if (curve)
result = xtryasprintf ("X_%s", curve);
else
result = xtrystrdup ("E_unknown");
}
else
result = xtryasprintf ("X_algo_%d", algo);
if (r_algoid)
*r_algoid = algo;
xfree (algoname);
return result;
}
/* Map a pubkey algo id from gcrypt to a string. This is the same as
* gcry_pk_algo_name but makes sure that the ECC algo identifiers are
* not all mapped to "ECC". */
const char *
pubkey_algo_to_string (int algo)
{
if (algo == GCRY_PK_ECDSA)
return "ECDSA";
else if (algo == GCRY_PK_ECDH)
return "ECDH";
else if (algo == GCRY_PK_EDDSA)
return "EdDSA";
else
return gcry_pk_algo_name (algo);
}
/* Map a hash algo id from gcrypt to a string. This is the same as
* gcry_md_algo_name but the returned string is lower case, as
* expected by libksba and it avoids some overhead. */
const char *
hash_algo_to_string (int algo)
{
static const struct
{
const char *name;
int algo;
} hashnames[] =
{
{ "sha256", GCRY_MD_SHA256 },
{ "sha512", GCRY_MD_SHA512 },
{ "sha1", GCRY_MD_SHA1 },
{ "sha384", GCRY_MD_SHA384 },
{ "sha224", GCRY_MD_SHA224 },
{ "sha3-224", GCRY_MD_SHA3_224 },
{ "sha3-256", GCRY_MD_SHA3_256 },
{ "sha3-384", GCRY_MD_SHA3_384 },
{ "sha3-512", GCRY_MD_SHA3_512 },
{ "ripemd160", GCRY_MD_RMD160 },
{ "rmd160", GCRY_MD_RMD160 },
{ "md2", GCRY_MD_MD2 },
{ "md4", GCRY_MD_MD4 },
{ "tiger", GCRY_MD_TIGER },
{ "haval", GCRY_MD_HAVAL },
{ "sm3", GCRY_MD_SM3 },
{ "md5", GCRY_MD_MD5 }
};
int i;
for (i=0; i < DIM (hashnames); i++)
if (algo == hashnames[i].algo)
return hashnames[i].name;
return "?";
}
/* Map cipher modes to a string. */
const char *
cipher_mode_to_string (int mode)
{
switch (mode)
{
case GCRY_CIPHER_MODE_CFB: return "CFB";
case GCRY_CIPHER_MODE_CBC: return "CBC";
case GCRY_CIPHER_MODE_GCM: return "GCM";
case GCRY_CIPHER_MODE_OCB: return "OCB";
case 14: return "EAX"; /* Only in gcrypt 1.9 */
default: return "[?]";
}
}
/* Return the canonical name of the ECC curve in KEY. */
const char *
get_ecc_curve_from_key (gcry_sexp_t key)
{
gcry_sexp_t list = NULL;
gcry_sexp_t l2 = NULL;
const char *curve_name = NULL;
char *name = NULL;
/* Check that the first element is valid. */
list = gcry_sexp_find_token (key, "public-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "private-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "protected-private-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
if (!list)
goto leave;
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
l2 = NULL;
name = gcry_sexp_nth_string (list, 0);
if (!name)
goto leave;
if (gcry_pk_map_name (name) != GCRY_PK_ECC)
goto leave;
l2 = gcry_sexp_find_token (list, "curve", 0);
xfree (name);
name = gcry_sexp_nth_string (l2, 1);
curve_name = openpgp_oid_or_name_to_curve (name, 1);
gcry_sexp_release (l2);
leave:
xfree (name);
gcry_sexp_release (list);
return curve_name;
}
diff --git a/g10/call-agent.c b/g10/call-agent.c
index bba6fa833..a1a48c75c 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -1,3439 +1,3447 @@
/* call-agent.c - Divert GPG operations to the agent.
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "call-agent.h"
#include "../common/status.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
#include "../common/ttyio.h"
#define CONTROL_D ('D' - 'A' + 1)
static assuan_context_t agent_ctx = NULL;
static int did_early_card_test;
struct confirm_parm_s
{
char *desc;
char *ok;
char *notok;
};
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
struct confirm_parm_s *confirm;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* If RC is not 0, write an appropriate status message. */
static void
status_sc_op_failure (int rc)
{
switch (gpg_err_code (rc))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
case GPG_ERR_BAD_RESET_CODE:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
}
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
const char *s;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpg_proxy_pinentry_notify (parm->ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
assuan_begin_confidential (parm->ctx);
if (have_static_passphrase ())
{
s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
{
char *pw;
char buf[32];
if (parm->keyinfo.keyid)
emit_status_need_passphrase (parm->ctrl,
parm->keyinfo.keyid,
parm->keyinfo.mainkeyid,
parm->keyinfo.pubkey_algo);
snprintf (buf, sizeof (buf), "%u", 100);
write_status_text (STATUS_INQUIRE_MAXLEN, buf);
pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: "));
cpr_kill_prompt ();
if (*pw == CONTROL_D && !pw[1])
err = gpg_error (GPG_ERR_CANCELED);
else
err = assuan_send_data (parm->ctx, pw, strlen (pw));
xfree (pw);
}
assuan_end_confidential (parm->ctx);
}
else if ((s = has_leading_keyword (line, "CONFIRM"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
&& parm->confirm)
{
int ask = atoi (s);
int yes;
if (ask)
{
yes = cpr_get_answer_is_yes (NULL, parm->confirm->desc);
if (yes)
err = assuan_send_data (parm->ctx, NULL, 0);
else
err = gpg_error (GPG_ERR_NOT_CONFIRMED);
}
else
{
tty_printf ("%s", parm->confirm->desc);
err = assuan_send_data (parm->ctx, NULL, 0);
}
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
return warn_server_version_mismatch (ctx, servername, mode,
write_status_strings2, NULL,
!opt.quiet);
}
#define FLAG_FOR_CARD_SUPPRESS_ERRORS 2
/* Try to connect to the agent via socket or fork it off and work by
pipes. Handle the server's initial greeting */
static int
start_agent (ctrl_t ctrl, int flag_for_card)
{
int rc;
(void)ctrl; /* Not yet used. */
/* Fixme: We need a context for each thread or serialize the access
to the agent. */
if (agent_ctx)
rc = 0;
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc
&& !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
write_status_error ("set_pinentry_mode", rc);
}
}
/* Pass on the request origin. */
if (opt.request_origin)
{
char *tmp = xasprintf ("OPTION pretend-request-origin=%s",
str_request_origin (opt.request_origin));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting request origin '%s' failed: %s\n",
str_request_origin (opt.request_origin),
gpg_strerror (rc));
write_status_error ("set_request_origin", rc);
}
}
/* In DE_VS mode under Windows we require that the JENT RNG
* is active. */
#ifdef HAVE_W32_SYSTEM
if (!rc && opt.compliance == CO_DE_VS)
{
if (assuan_transact (agent_ctx, "GETINFO jent_active",
NULL, NULL, NULL, NULL, NULL, NULL))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
GPG_AGENT_NAME,
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", rc);
}
}
#endif /*HAVE_W32_SYSTEM*/
}
}
if (!rc && flag_for_card && !did_early_card_test)
{
/* Request the serial number of the card for an early test. */
struct agent_card_info_s info;
memset (&info, 0, sizeof info);
if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!rc)
rc = assuan_transact (agent_ctx,
opt.flags.use_only_openpgp_card?
"SCD SERIALNO openpgp" : "SCD SERIALNO",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
{
switch (gpg_err_code (rc))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
write_status_text (STATUS_CARDCTRL, "6");
break;
case GPG_ERR_OBJ_TERM_STATE:
write_status_text (STATUS_CARDCTRL, "7");
break;
default:
write_status_text (STATUS_CARDCTRL, "4");
log_info ("selecting card failed: %s\n", gpg_strerror (rc));
break;
}
}
if (!rc && is_status_enabled () && info.serialno)
{
char *buf;
buf = xasprintf ("3 %s", info.serialno);
write_status_text (STATUS_CARDCTRL, buf);
xfree (buf);
}
agent_release_card_info (&info);
if (!rc)
did_early_card_test = 1;
}
return rc;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Release the card info structure INFO. */
void
agent_release_card_info (struct agent_card_info_s *info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->manufacturer_name); info->manufacturer_name = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->apptype); info->apptype = NULL;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
info->fpr1len = info->fpr2len = info->fpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
for (i=0; i < DIM(info->supported_keyalgo); i++)
{
free_strlist (info->supported_keyalgo[i]);
info->supported_keyalgo[i] = NULL;
}
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct agent_card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
int i;
char *endp;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& (xtoi_2 (parm->serialno+12) == 0 /* Yubikey */
|| xtoi_2 (parm->serialno+12) >= 2));
}
else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptype);
parm->apptype = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "APPVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->appversion = val;
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
{
while (spacep (p))
p++;
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvretry[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
xfree (buf);
}
}
else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1);
else if (no == 2)
parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2);
else if (no == 3)
parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
{
int no = atoi (line);
while (* line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1time = strtoul (line, NULL, 10);
else if (no == 2)
parm->fpr2time = strtoul (line, NULL, 10);
else if (no == 3)
parm->fpr3time = strtoul (line, NULL, 10);
}
else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
const char *hexgrp = line;
int no;
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (strncmp (line, "OPENPGP.", 8))
;
else if ((no = atoi (line+8)) == 1)
unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1);
else if (no == 2)
unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2);
else if (no == 3)
unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3);
}
else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,sizeof parm->cafpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen))
{
int keyno = 0;
int algo = PUBKEY_ALGO_RSA;
int n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
keyno--;
if (keyno < 0 || keyno >= DIM (parm->key_attr))
return 0;
parm->key_attr[keyno].algo = algo;
if (algo == PUBKEY_ALGO_RSA)
parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n,
NULL, NULL);
}
else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
else if (keywordlen == 12 && !memcmp (keyword, "MANUFACTURER", 12))
{
xfree (parm->manufacturer_name);
parm->manufacturer_name = NULL;
parm->manufacturer_id = strtoul (line, &endp, 0);
while (endp && spacep (endp))
endp++;
if (endp && *endp)
parm->manufacturer_name = xstrdup (endp);
}
else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3))
{
unsigned char *data = unescape_status_string (line);
if (data[2] != 0x03)
parm->kdf_do_enabled = 0;
else if (data[22] != 0x85)
parm->kdf_do_enabled = 1;
else
parm->kdf_do_enabled = 2;
xfree (data);
}
else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
parm->uif[no] = (data[0] != 0xff);
xfree (data);
}
else if (keywordlen == 13 && !memcmp (keyword, "KEY-ATTR-INFO", 13))
{
if (!strncmp (line, "OPENPGP.", 8))
{
int no;
line += 8;
no = atoi (line);
if (no >= 1 && no <= 3)
{
no--;
line++;
while (spacep (line))
line++;
append_to_strlist (&parm->supported_keyalgo[no], xstrdup (line));
}
}
/* Skip when it's not "OPENPGP.[123]". */
}
return 0;
}
/* Call the scdaemon to learn about a smartcard. Note that in
* contradiction to the function's name, gpg-agent's LEARN command is
* used and not the low-level "SCD LEARN".
* Used by:
* card-util.c
* keyedit_menu
* card_store_key_with_backup (With force to remove secret key data)
*/
int
agent_scd_learn (struct agent_card_info_s *info, int force)
{
int rc;
struct default_inq_parm_s parm;
struct agent_card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx,
force ? "LEARN --sendinfo --force" : "LEARN --sendinfo",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get the key attributes. */
if (!rc)
agent_scd_getattr ("KEY-ATTR", info);
if (info == &dummyinfo)
agent_release_card_info (info);
return rc;
}
struct keypairinfo_cb_parm_s
{
keypair_info_t kpinfo;
keypair_info_t *kpinfo_tail;
};
/* Callback for the agent_scd_keypairinfo function. */
static gpg_error_t
scd_keypairinfo_status_cb (void *opaque, const char *line)
{
struct keypairinfo_cb_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *keyword = line;
int keywordlen;
char *line_buffer = NULL;
keypair_info_t kpi = NULL;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime] [algostr]
*/
const char *fields[4];
int nfields;
const char *hexgrp, *keyref, *usage;
time_t atime;
u32 keytime;
line_buffer = xtrystrdup (line);
if (!line_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2)
goto leave; /* not enough args - invalid status line - ignore */
hexgrp = fields[0];
keyref = fields[1];
if (nfields > 2)
usage = fields[2];
else
usage = "";
if (nfields > 3)
{
atime = parse_timestamp (fields[3], NULL);
if (atime == (time_t)(-1))
atime = 0;
keytime = atime;
}
else
keytime = 0;
kpi = xtrycalloc (1, sizeof *kpi);
if (!kpi)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (*hexgrp == 'X' && !hexgrp[1])
*kpi->keygrip = 0; /* No hexgrip. */
else if (strlen (hexgrp) == 2*KEYGRIP_LEN)
mem2str (kpi->keygrip, hexgrp, sizeof kpi->keygrip);
else
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (!*keyref)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
kpi->idstr = xtrystrdup (keyref);
if (!kpi->idstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Parse and set the usage. */
for (; *usage; usage++)
{
switch (*usage)
{
case 's': kpi->usage |= GCRY_PK_USAGE_SIGN; break;
case 'c': kpi->usage |= GCRY_PK_USAGE_CERT; break;
case 'a': kpi->usage |= GCRY_PK_USAGE_AUTH; break;
case 'e': kpi->usage |= GCRY_PK_USAGE_ENCR; break;
}
}
kpi->keytime = keytime;
/* Append to the list. */
*parm->kpinfo_tail = kpi;
parm->kpinfo_tail = &kpi->next;
kpi = NULL;
}
leave:
free_keypair_info (kpi);
xfree (line_buffer);
return err;
}
/* Read the keypairinfo lines of the current card directly from
* scdaemon. The list is returned as a string made up of the keygrip,
* a space and the keyref. The flags of the string carry the usage
* bits. If KEYREF is not NULL, only a single string is returned
* which matches the given keyref. */
gpg_error_t
agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref, keypair_info_t *r_list)
{
gpg_error_t err;
struct keypairinfo_cb_parm_s parm;
struct default_inq_parm_s inq_parm;
char line[ASSUAN_LINELENGTH];
*r_list = NULL;
err= start_agent (ctrl, 1);
if (err)
return err;
memset (&inq_parm, 0, sizeof inq_parm);
inq_parm.ctx = agent_ctx;
parm.kpinfo = NULL;
parm.kpinfo_tail = &parm.kpinfo;
if (keyref)
snprintf (line, DIM(line), "SCD READKEY --info-only %s", keyref);
else
snprintf (line, DIM(line), "SCD LEARN --keypairinfo");
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &inq_parm,
scd_keypairinfo_status_cb, &parm);
if (!err && !parm.kpinfo)
err = gpg_error (GPG_ERR_NO_DATA);
if (err)
free_keypair_info (parm.kpinfo);
else
*r_list = parm.kpinfo;
return err;
}
/* Send an APDU to the current card. On success the status word is
* stored at R_SW unless R_SQ is NULL. With HEXAPDU being NULL only a
* RESET command is send to scd. HEXAPDU may also be one of theseo
* special strings:
*
* "undefined" :: Send the command "SCD SERIALNO undefined"
* "lock" :: Send the command "SCD LOCK --wait"
* "trylock" :: Send the command "SCD LOCK"
* "unlock" :: Send the command "SCD UNLOCK"
* "reset-keep-lock" :: Send the command "SCD RESET --keep-lock"
*
* Used by:
* card-util.c
*/
gpg_error_t
agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
{
gpg_error_t err;
/* Start the agent but not with the card flag so that we do not
autoselect the openpgp application. */
err = start_agent (NULL, 0);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "reset-keep-lock"))
{
err = assuan_transact (agent_ctx, "SCD RESET --keep-lock",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "lock"))
{
err = assuan_transact (agent_ctx, "SCD LOCK --wait",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "trylock"))
{
err = assuan_transact (agent_ctx, "SCD LOCK",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "unlock"))
{
err = assuan_transact (agent_ctx, "SCD UNLOCK",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
*r_sw = buf16_to_uint (data+datalen-2);
}
xfree (data);
}
}
return err;
}
int
agent_keytotpm (ctrl_t ctrl, const char *hexgrip)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
snprintf(line, DIM(line), "KEYTOTPM %s\n", hexgrip);
if (strchr (hexgrip, ','))
{
log_error ("storing a part of a dual key is not yet supported\n");
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
rc = start_agent (ctrl, 0);
if (rc)
return rc;
parm.ctx = agent_ctx;
parm.ctrl = ctrl;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
if (rc)
log_log (GPGRT_LOGLVL_ERROR, _("error from TPM: %s\n"), gpg_strerror (rc));
return rc;
}
/* Used by:
* card_store_subkey
* card_store_key_with_backup
*/
int
agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp,
const char *ecdh_param_str)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (strchr (hexgrip, ','))
{
log_error ("storing a part of a dual key is not yet supported\n");
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s%s%s",
force?"--force ": "", hexgrip, serialno, keyno, timestamp,
ecdh_param_str? " ":"", ecdh_param_str? ecdh_param_str:"");
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Object used with the agent_scd_getattr_one. */
struct getattr_one_parm_s {
const char *keyword; /* Keyword to look for. */
char *data; /* Malloced and unescaped data. */
gpg_error_t err; /* Error code or 0 on success. */
};
/* Callback for agent_scd_getattr_one. */
static gpg_error_t
getattr_one_status_cb (void *opaque, const char *line)
{
struct getattr_one_parm_s *parm = opaque;
const char *s;
if (parm->data)
return 0; /* We want only the first occurrence. */
if ((s=has_leading_keyword (line, parm->keyword)))
{
parm->data = percent_plus_unescape (s, 0xff);
if (!parm->data)
parm->err = gpg_error_from_syserror ();
}
return 0;
}
/* Simplified version of agent_scd_getattr. This function returns
* only the first occurrence of the attribute NAME and stores it at
* R_VALUE. A nul in the result is silennly replaced by 0xff. On
* error NULL is stored at R_VALUE. */
gpg_error_t
agent_scd_getattr_one (const char *name, char **r_value)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inqparm;
struct getattr_one_parm_s parm;
*r_value = NULL;
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
memset (&inqparm, 0, sizeof inqparm);
inqparm.ctx = agent_ctx;
memset (&parm, 0, sizeof parm);
parm.keyword = name;
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
err = start_agent (NULL, 1);
if (err)
return err;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &inqparm,
getattr_one_status_cb, &parm);
if (!err && parm.err)
err = parm.err;
else if (!err && !parm.data)
err = gpg_error (GPG_ERR_NO_DATA);
if (!err)
*r_value = parm.data;
else
xfree (parm.data);
return err;
}
/* Call the agent to retrieve a data object. This function returns
* the data in the same structure as used by the learn command. It is
* allowed to update such a structure using this command.
*
* Used by:
* build_sk_list
* enum_secret_keys
* get_signature_count
* card-util.c
* generate_keypair (KEY-ATTR)
* card_store_key_with_backup (SERIALNO)
* generate_card_subkeypair (KEY-ATTR)
*/
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
if (!rc && !strcmp (name, "KEY-FPR"))
{
/* Let the agent create the shadow keys if not yet done. */
if (info->fpr1len)
assuan_transact (agent_ctx, "READKEY --card --no-data -- $SIGNKEYID",
NULL, NULL, NULL, NULL, NULL, NULL);
if (info->fpr2len)
assuan_transact (agent_ctx, "READKEY --card --no-data -- $ENCRKEYID",
NULL, NULL, NULL, NULL, NULL, NULL);
}
return rc;
}
/* Send an setattr command to the SCdaemon.
* Used by:
* card-util.c
*/
gpg_error_t
agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen)
{
gpg_error_t err;
const unsigned char *value = value_arg;
char line[ASSUAN_LINELENGTH];
char *p;
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
*p++ = ' ';
for (; valuelen; value++, valuelen--)
{
if (p >= line + DIM(line)-5 )
return gpg_error (GPG_ERR_TOO_LARGE);
if (*value < ' ' || *value == '+' || *value == '%')
{
sprintf (p, "%%%02X", *value);
p += 3;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
*p = 0;
err = start_agent (NULL, 1);
if (!err)
{
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
}
status_sc_op_failure (err);
return err;
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END
command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
int rc;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
rc = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Send a WRITECERT command to the SCdaemon.
* Used by:
* card-util.c
*/
int
agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
memset (&parms, 0, sizeof parms);
snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return rc;
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
write_status_text (STATUS_PROGRESS, line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
* the value will be passed to SCDAEMON with --timestamp option so that
* the key is created with this. Otherwise, timestamp was generated by
* SCDEAMON. On success, creation time is stored back to
* CREATETIME.
* Used by:
* gen_card_key
*/
int
agent_scd_genkey (int keyno, int force, u32 *createtime)
{
int rc;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
if (*createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
keyno);
dfltparm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
status_sc_op_failure (rc);
return rc;
}
/* Return the serial number of the card or an appropriate error. The
* serial number is returned as a hexstring. With DEMAND the active
* card is switched to the card with that serialno.
* Used by:
* card-util.c
* build_sk_list
* enum_secret_keys
*/
int
agent_scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
if (r_serialno)
*r_serialno = NULL;
err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS));
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
if (r_serialno)
*r_serialno = serialno;
else
xfree (serialno);
return 0;
}
/* Send a READCERT command to the SCdaemon.
* Used by:
* card-util.c
*/
int
agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, DIM(line), "SCD READCERT %s", certidstr);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error (GPG_ERR_ENOMEM);
return 0;
}
/* Callback for the agent_scd_readkey function. */
static gpg_error_t
readkey_status_cb (void *opaque, const char *line)
{
u32 *keytimep = opaque;
gpg_error_t err = 0;
const char *args;
char *line_buffer = NULL;
/* FIXME: Get that info from the KEYPAIRINFO line. */
if ((args = has_leading_keyword (line, "KEYPAIRINFO"))
&& !*keytimep)
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime]
*
* Note that we use only the first valid KEYPAIRINFO line. More
* lines are possible if a second card carries the same key.
*/
const char *fields[4];
int nfields;
time_t atime;
line_buffer = xtrystrdup (line);
if (!line_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 4)
goto leave; /* not enough args - ignore */
if (nfields > 3)
{
atime = parse_timestamp (fields[3], NULL);
if (atime == (time_t)(-1))
atime = 0;
*keytimep = atime;
}
else
*keytimep = 0;
}
leave:
xfree (line_buffer);
return err;
}
/* This is a variant of agent_readkey which sends a READKEY command
* directly Scdaemon. On success a new s-expression is stored at
* R_RESULT. If R_KEYTIME is not NULL the key cresation time of an
* OpenPGP card is stored there - if that is not known 0 is stored.
* In the latter case it is allowed to pass NULL for R_RESULT. */
gpg_error_t
agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr,
gcry_sexp_t *r_result, u32 *r_keytime)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
unsigned char *buf;
size_t len, buflen;
struct default_inq_parm_s dfltparm;
u32 keytime;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctx = agent_ctx;
if (r_result)
*r_result = NULL;
if (r_keytime)
*r_keytime = 0;
err = start_agent (ctrl, 1);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, DIM(line),
"SCD READKEY --info%s -- %s",
r_result? "":"-only", keyrefstr);
keytime = 0;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
readkey_status_cb, &keytime);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &buflen);
if (!buf)
return gpg_error_from_syserror ();
if (r_result)
err = gcry_sexp_new (r_result, buf, buflen, 0);
else
err = 0;
xfree (buf);
if (!err && r_keytime)
*r_keytime = keytime;
return err;
}
struct card_cardlist_parm_s {
int error;
strlist_t list;
};
/* Callback function for agent_card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1) || *s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
return 0;
}
/* Return a list of currently available cards.
* Used by:
* card-util.c
* skclist.c
*/
int
agent_scd_cardlist (strlist_t *result)
{
int err;
char line[ASSUAN_LINELENGTH];
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
strcpy (line, "SCD GETINFO card_list");
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return 0;
}
/* Make the app APPNAME the one on the card. This is sometimes
* required to make sure no other process has switched a card to
* another application. The only useful APPNAME is "openpgp". */
gpg_error_t
agent_scd_switchapp (const char *appname)
{
int err;
char line[ASSUAN_LINELENGTH];
if (appname && !*appname)
appname = NULL;
err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS));
if (err)
return err;
snprintf (line, DIM(line), "SCD SWITCHAPP --%s%s",
appname? " ":"", appname? appname:"");
return assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
NULL, NULL);
}
struct card_keyinfo_parm_s {
int error;
keypair_info_t list;
};
/* Callback function for agent_card_keylist. */
static gpg_error_t
card_keyinfo_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct card_keyinfo_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
keypair_info_t keyinfo = NULL;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen))
{
const char *s;
int n;
keypair_info_t *l_p = &parm->list;
while ((*l_p))
l_p = &(*l_p)->next;
keyinfo = xtrycalloc (1, sizeof *keyinfo);
if (!keyinfo)
goto alloc_error;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (n != 40)
goto parm_error;
memcpy (keyinfo->keygrip, line, 40);
keyinfo->keygrip[40] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (*line++ != 'T')
goto parm_error;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n)
goto parm_error;
keyinfo->serialno = xtrymalloc (n+1);
if (!keyinfo->serialno)
goto alloc_error;
memcpy (keyinfo->serialno, line, n);
keyinfo->serialno[n] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (!*line)
goto parm_error;
keyinfo->idstr = xtrystrdup (line);
if (!keyinfo->idstr)
goto alloc_error;
*l_p = keyinfo;
}
return err;
alloc_error:
xfree (keyinfo);
if (!parm->error)
parm->error = gpg_error_from_syserror ();
return 0;
parm_error:
xfree (keyinfo);
if (!parm->error)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
return 0;
}
/* Free a keypair info list. */
void
free_keypair_info (keypair_info_t l)
{
keypair_info_t l_next;
for (; l; l = l_next)
{
l_next = l->next;
xfree (l->serialno);
xfree (l->idstr);
xfree (l);
}
}
/* Call the scdaemon to check if a key of KEYGRIP is available, or
retrieve list of available keys on cards. With CAP, we can limit
keys with specified capability. On success, the allocated
structure is stored at RESULT. On error, an error code is returned
and NULL is stored at RESULT. */
gpg_error_t
agent_scd_keyinfo (const char *keygrip, int cap,
keypair_info_t *result)
{
int err;
struct card_keyinfo_parm_s parm;
char line[ASSUAN_LINELENGTH];
char *list_option;
*result = NULL;
switch (cap)
{
case 0: list_option = "--list"; break;
case GCRY_PK_USAGE_SIGN: list_option = "--list=sign"; break;
case GCRY_PK_USAGE_ENCR: list_option = "--list=encr"; break;
case GCRY_PK_USAGE_AUTH: list_option = "--list=auth"; break;
default: return gpg_error (GPG_ERR_INV_VALUE);
}
memset (&parm, 0, sizeof parm);
snprintf (line, sizeof line, "SCD KEYINFO %s",
keygrip ? keygrip : list_option);
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_keyinfo_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_keypair_info (parm.list);
return err;
}
/* Change the PIN of an OpenPGP card or reset the retry counter.
* CHVNO 1: Change the PIN
* 2: For v1 cards: Same as 1.
* For v2 cards: Reset the PIN using the Reset Code.
* 3: Change the admin PIN
* 101: Set a new PIN and reset the retry counter
* 102: For v1 cars: Same as 101.
* For v2 cards: Set a new Reset Code.
* SERIALNO is not used.
* Used by:
* card-util.c
*/
int
agent_scd_change_pin (int chvno, const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
const char *reset = "";
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
(void)serialno;
if (chvno >= 100)
reset = "--reset";
chvno %= 100;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
* number of the card - optionally followed by the fingerprint;
* however the fingerprint is ignored here.
* Used by:
* card-util.c
*/
int
agent_scd_checkpin (const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Note: All strings shall be UTF-8. On success the caller needs to
free the string stored at R_PASSPHRASE. On error NULL will be
stored at R_PASSPHRASE and an appropriate error code returned.
Only called from passphrase.c:passphrase_get - see there for more
comments on this ugly API. */
gpg_error_t
agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int newsymkey,
int repeat,
int check,
char **r_passphrase)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *arg1 = NULL;
char *arg2 = NULL;
char *arg3 = NULL;
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s dfltparm;
int have_newsymkey, wasconf;
memset (&dfltparm, 0, sizeof dfltparm);
*r_passphrase = NULL;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent understands the repeat option. */
if (assuan_transact (agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE repeat",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
have_newsymkey = !(assuan_transact
(agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE newsymkey",
NULL, NULL, NULL, NULL, NULL, NULL));
if (cache_id && *cache_id)
if (!(arg1 = percent_plus_escape (cache_id)))
goto no_mem;
if (err_msg && *err_msg)
if (!(arg2 = percent_plus_escape (err_msg)))
goto no_mem;
if (prompt && *prompt)
if (!(arg3 = percent_plus_escape (prompt)))
goto no_mem;
if (desc_msg && *desc_msg)
if (!(arg4 = percent_plus_escape (desc_msg)))
goto no_mem;
/* CHECK && REPEAT or NEWSYMKEY is here an indication that a new
* passphrase for symmetric encryption is requested; if the agent
* supports this we enable the modern API by also passing --newsymkey. */
snprintf (line, DIM(line),
"GET_PASSPHRASE --data --repeat=%d%s%s -- %s %s %s %s",
repeat,
((repeat && check) || newsymkey)? " --check":"",
(have_newsymkey && newsymkey)? " --newsymkey":"",
arg1? arg1:"X",
arg2? arg2:"X",
arg3? arg3:"X",
arg4? arg4:"X");
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
init_membuf_secure (&data, 64);
wasconf = assuan_get_flag (agent_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (agent_ctx);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (!wasconf)
assuan_end_confidential (agent_ctx);
if (rc)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
rc = gpg_error_from_syserror ();
}
return rc;
no_mem:
rc = gpg_error_from_syserror ();
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
return rc;
}
gpg_error_t
agent_clear_passphrase (const char *cache_id)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
if (!cache_id || !*cache_id)
return 0;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id);
return assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpg_agent_get_confirmation (const char *desc)
{
int rc;
char *tmp;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
tmp = percent_plus_escape (desc);
if (!tmp)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp);
xfree (tmp);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return rc;
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (NULL, 0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
struct keyinfo_data_parm_s
{
char *serialno;
int is_smartcard;
int passphrase_cached;
int cleartext;
int card_available;
};
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
struct keyinfo_data_parm_s *data = opaque;
char *s;
if ((s = has_leading_keyword (line, "KEYINFO")) && data)
{
/* Parse the arguments:
* 0 1 2 3 4 5
* <keygrip> <type> <serialno> <idstr> <cached> <protection>
*
* 6 7 8
* <sshfpr> <ttl> <flags>
*/
const char *fields[9];
if (split_fields (s, fields, DIM (fields)) == 9)
{
data->is_smartcard = (fields[1][0] == 'T');
if (data->is_smartcard && !data->serialno && strcmp (fields[2], "-"))
data->serialno = xtrystrdup (fields[2]);
/* '1' for cached */
data->passphrase_cached = (fields[4][0] == '1');
/* 'P' for protected, 'C' for clear */
data->cleartext = (fields[5][0] == 'C');
/* 'A' for card is available */
data->card_available = (fields[8][0] == 'A');
}
}
return 0;
}
/* Ask the agent whether a secret key for the given public key is
* available. Returns 0 if not available. Bigger value is preferred.
* Will never return a value less than 0. Defined return values are:
* 0 := No key or error
* 1 := Key available
* 2 := Key available on a smartcard
* 3 := Key available and passphrase cached
* 4 := Key available on current smartcard
*/
int
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *hexgrip, *p;
struct keyinfo_data_parm_s keyinfo;
int result, result2;
memset (&keyinfo, 0, sizeof keyinfo);
err = start_agent (ctrl, 0);
if (err)
return 0;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
return 0;
if ((p=strchr (hexgrip, ',')))
*p++ = 0;
snprintf (line, sizeof line, "KEYINFO %s", hexgrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
xfree (keyinfo.serialno);
if (err)
result = 0;
else if (keyinfo.card_available)
result = 4;
else if (keyinfo.passphrase_cached)
result = 3;
else if (keyinfo.is_smartcard)
result = 2;
else
result = 1;
if (!p)
{
xfree (hexgrip);
return result; /* Not a dual algo - we are ready. */
}
/* Now check the second keygrip. */
memset (&keyinfo, 0, sizeof keyinfo);
snprintf (line, sizeof line, "KEYINFO %s", p);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
xfree (keyinfo.serialno);
if (err)
result2 = 0;
else if (keyinfo.card_available)
result2 = 4;
else if (keyinfo.passphrase_cached)
result2 = 3;
else if (keyinfo.is_smartcard)
result2 = 2;
else
result2 = 1;
xfree (hexgrip);
if (result == result2)
return result; /* Both keys have the same status. */
else if (!result && result2)
return 0; /* Only first key available - return no key. */
else if (result && !result2)
return 0; /* Only second key not available - return no key. */
else if (result == 4 || result == 2)
return result; /* First key on card - don't care where the second is. */
else
return result;
}
/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *p;
kbnode_t kbctx, node;
int nkeys; /* (always zero in secret_keygrips mode) */
unsigned char grip[KEYGRIP_LEN];
unsigned char grip2[KEYGRIP_LEN];
int grip2_valid;
const unsigned char *s;
unsigned int n;
err = start_agent (ctrl, 0);
if (err)
return err;
/* If we have not yet issued a "HAVEKEY --list" do that now. We use
* a more or less arbitrary limit of 1000 keys. */
if (ctrl && !ctrl->secret_keygrips && !ctrl->no_more_secret_keygrips)
{
membuf_t data;
init_membuf (&data, 4096);
err = assuan_transact (agent_ctx, "HAVEKEY --list=1000",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
ctrl->secret_keygrips = get_membuf (&data,
&ctrl->secret_keygrips_len);
if (!ctrl->secret_keygrips)
err = gpg_error_from_syserror ();
if ((ctrl->secret_keygrips_len % 20))
{
err = gpg_error (GPG_ERR_INV_DATA);
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = NULL;
}
}
if (err)
{
if (!opt.quiet)
log_info ("problem with fast path key listing: %s - ignored\n",
gpg_strerror (err));
err = 0;
}
/* We want to do this only once. */
ctrl->no_more_secret_keygrips = 1;
}
err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was
found in KEYBLOCK. */
p = stpcpy (line, "HAVEKEY");
for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (ctrl && ctrl->secret_keygrips)
{
/* We got an array with all secret keygrips. Check this. */
err = keygrip_from_pk (node->pkt->pkt.public_key, grip, 0);
if (err)
return err;
err = keygrip_from_pk (node->pkt->pkt.public_key, grip2, 1);
if (err && gpg_err_code (err) != GPG_ERR_FALSE)
return err;
grip2_valid = !err;
for (s=ctrl->secret_keygrips, n = 0;
n < ctrl->secret_keygrips_len;
s += 20, n += 20)
{
if (!memcmp (s, grip, 20))
return 0;
if (grip2_valid && !memcmp (s, grip2, 20))
return 0;
}
err = gpg_error (GPG_ERR_NO_SECKEY);
/* Keep on looping over the keyblock. Never bump nkeys. */
}
else
{
if (nkeys
&& ((p - line) + 4*KEYGRIP_LEN+1+1) > (ASSUAN_LINELENGTH - 2))
{
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err != gpg_err_code (GPG_ERR_NO_SECKEY))
break; /* Seckey available or unexpected error - ready. */
p = stpcpy (line, "HAVEKEY");
nkeys = 0;
}
err = keygrip_from_pk (node->pkt->pkt.public_key, grip, 0);
if (err)
return err;
*p++ = ' ';
bin2hex (grip, 20, p);
p += 40;
nkeys++;
err = keygrip_from_pk (node->pkt->pkt.public_key, grip2, 1);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_FALSE) /* No second keygrip. */
err = 0;
else
return err;
}
else /* Add the second keygrip from dual algos. */
{
*p++ = ' ';
bin2hex (grip2, 20, p);
p += 40;
nkeys++;
}
}
}
if (!err && nkeys)
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO.
if r_cleartext is not NULL, the referenced int will be set to 1 if
the agent's copy of the key is stored in the clear, or 0 otherwise
*/
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct keyinfo_data_parm_s keyinfo;
const char *s;
memset (&keyinfo, 0,sizeof keyinfo);
*r_serialno = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
/* FIXME: Support dual keys. Maybe under the assumption that the
* first key might be on a card. */
if (!hexkeygrip)
return gpg_error (GPG_ERR_INV_VALUE);
s = strchr (hexkeygrip, ',');
if (!s)
s = hexkeygrip + strlen (hexkeygrip);
if (s - hexkeygrip != 40)
return gpg_error (GPG_ERR_INV_VALUE);
/* Note that for a dual algo we only get info for the first key.
* FIXME: We need to see how we can show the status of the second
* key in a key listing. */
snprintf (line, DIM(line), "KEYINFO %.40s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
if (!err && keyinfo.serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (keyinfo.serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (keyinfo.serialno);
else
{
*r_serialno = keyinfo.serialno;
if (r_cleartext)
*r_cleartext = keyinfo.cleartext;
}
return err;
}
/* Status callback for agent_import_key, agent_export_key and
agent_genkey. */
static gpg_error_t
cache_nonce_status_cb (void *opaque, const char *line)
{
struct cache_nonce_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "CACHE_NONCE")))
{
if (parm->cache_nonce_addr)
{
xfree (*parm->cache_nonce_addr);
*parm->cache_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PASSWD_NONCE")))
{
if (parm->passwd_nonce_addr)
{
xfree (*parm->passwd_nonce_addr);
*parm->passwd_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (opt.enable_progress_filter)
write_status_text (STATUS_PROGRESS, s);
}
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYPARAM"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->keyparms, strlen (parm->keyparms));
}
else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase)
{
err = assuan_send_data (parm->dflt->ctx,
parm->passphrase, strlen (parm->passphrase));
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to generate a new key. KEYPARMS is the usual
S-expression giving the parameters of the key. gpg-agent passes it
gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not
to protect the generated key. If NO_PROTECTION is not set and
PASSPHRASE is not NULL the agent is requested to protect the key
with that passphrase instead of asking for one. TIMESTAMP is the
creation time of the key or zero. */
gpg_error_t
agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase, time_t timestamp, gcry_sexp_t *r_pubkey)
{
gpg_error_t err;
struct genkey_parm_s gk_parm;
struct cache_nonce_parm_s cn_parm;
struct default_inq_parm_s dfltparm;
membuf_t data;
size_t len;
unsigned char *buf;
char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */
char line[ASSUAN_LINELENGTH];
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
/* Do not use our cache of secret keygrips anymore - this command
* would otherwise requiring to update that cache. */
if (ctrl && ctrl->secret_keygrips)
{
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = 0;
}
if (timestamp)
{
strcpy (timestamparg, " --timestamp=");
epoch2isotime (timestamparg+13, timestamp);
}
else
*timestamparg = 0;
if (passwd_nonce_addr && *passwd_nonce_addr)
; /* A RESET would flush the passwd nonce cache. */
else
{
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf (&data, 1024);
gk_parm.dflt = &dfltparm;
gk_parm.keyparms = keyparms;
gk_parm.passphrase = passphrase;
snprintf (line, sizeof line, "GENKEY%s%s%s%s%s%s",
*timestamparg? timestamparg : "",
no_protection? " --no-protection" :
passphrase ? " --inq-passwd" :
/* */ "",
passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_pubkey, NULL, buf, len);
xfree (buf);
}
return err;
}
/* Add the Link attribute to both given keys. */
gpg_error_t
agent_crosslink_keys (ctrl_t ctrl, const char *hexgrip1, const char *hexgrip2)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
err = start_agent (ctrl, 0);
if (err)
goto leave;
snprintf (line, sizeof line, "KEYATTR %s Link: %s", hexgrip1, hexgrip2);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
goto leave;
snprintf (line, sizeof line, "KEYATTR %s Link: %s", hexgrip2, hexgrip1);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
leave:
return err;
}
/* Call the agent to read the public key part for a given keygrip.
* Values from FROMCARD:
* 0 - Standard
* 1 - The key is read from the current card
* via the agent and a stub file is created.
*/
gpg_error_t
agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (fromcard)
snprintf (line, DIM(line), "READKEY --card -- %s", hexkeygrip);
else
snprintf (line, DIM(line), "READKEY -- %s", hexkeygrip);
init_membuf (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. DESC is a description of the key to be
displayed if the agent needs to ask for the PIN. DIGEST and
DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id
used to compute the digest. If CACHE_NONCE is used the agent is
advised to first try a passphrase associated with that nonce. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen, int digestalgo,
gcry_sexp_t *r_sigval)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_sigval = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, sizeof line, "SETHASH %d ", digestalgo);
bin2hex (digest, digestlen, line + strlen (line));
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, sizeof line, "PKSIGN%s%s",
cache_nonce? " -- ":"",
cache_nonce? cache_nonce:"");
if (DBG_CLOCK)
log_clock ("enter signing");
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (DBG_CLOCK)
log_clock ("leave signing");
if (err)
xfree (get_membuf (&data, NULL));
else
{
unsigned char *buf;
size_t len;
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_sigval, NULL, buf, len);
xfree (buf);
}
}
return err;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END. */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->dflt->ctx,
parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Check whether there is any padding info from the agent. */
static gpg_error_t
padding_info_cb (void *opaque, const char *line)
{
int *r_padding = opaque;
const char *s;
if ((s=has_leading_keyword (line, "PADDING")))
{
*r_padding = atoi (s);
}
return 0;
}
/* Call the agent to do a decrypt operation using the key identified
by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the
success the decoded value is stored verbatim at R_BUF and its
length at R_BUF; the callers needs to release it. KEYID, MAINKEYID
and PUBKEY_ALGO are used to construct additional promots or status
messages. The padding information is stored at R_PADDING with -1
for not known. */
gpg_error_t
agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen, int *r_padding)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t n, len;
char *p, *buf, *endp;
const char *keygrip2 = NULL;
struct default_inq_parm_s dfltparm;
const char *cmdline;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
if (!keygrip || !s_ciphertext || !r_buf || !r_buflen || !r_padding)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
*r_padding = -1;
/* Parse the keygrip in case of a dual algo. */
keygrip2 = strchr (keygrip, ',');
if (!keygrip2)
keygrip2 = keygrip + strlen (keygrip);
if (keygrip2 - keygrip != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (*keygrip2)
{
keygrip2++;
if (strlen (keygrip2) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
}
if (*keygrip2)
cmdline = "PKDECRYPT --kem=PQC-PGP";
else if (pubkey_algo == PUBKEY_ALGO_ECDH)
cmdline = "PKDECRYPT --kem=PGP";
else
cmdline = "PKDECRYPT";
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, sizeof line, "SETKEY %.40s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (*keygrip2)
{
snprintf (line, sizeof line, "SETKEY --another %.40s", keygrip2);
err = assuan_transact (agent_ctx, line, NULL, NULL,NULL,NULL,NULL,NULL);
if (err)
return err;
}
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf_secure (&data, 1024);
{
struct cipher_parm_s parm;
parm.dflt = &dfltparm;
parm.ctx = agent_ctx;
err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen);
if (err)
return err;
err = assuan_transact (agent_ctx, cmdline,
put_membuf_cb, &data,
inq_ciphertext_cb, &parm,
padding_info_cb, r_padding);
xfree (parm.ciphertext);
}
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (len == 0 || *buf != '(')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
while (buf[len-1] == 0)
len--;
if (buf[len-1] != ')')
return gpg_error (GPG_ERR_INV_SEXP);
len--; /* Drop the final close-paren. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
len -= 8; /* Count only the data of the second part. */
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
endp++;
if (endp-p+n > len)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
}
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be used for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_kek = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
-agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
+agent_import_key (ctrl_t ctrl, const char *desc, int mode1003,
+ char **cache_nonce_addr,
const void *key, size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp)
{
gpg_error_t err;
struct import_key_parm_s parm;
struct cache_nonce_parm_s cn_parm;
char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
+ /* Check that the gpg-agent supports the --mode1003 option. */
+ if (mode1003 && assuan_transact (agent_ctx,
+ "GETINFO cmd_has_option IMPORT_KEY mode1003",
+ NULL, NULL, NULL, NULL, NULL, NULL))
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
/* Do not use our cache of secret keygrips anymore - this command
* would otherwise requiring to update that cache. */
if (ctrl && ctrl->secret_keygrips)
{
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = 0;
}
if (timestamp)
{
strcpy (timestamparg, " --timestamp=");
epoch2isotime (timestamparg+13, timestamp);
}
else
*timestamparg = 0;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
parm.dflt = &dfltparm;
parm.key = key;
parm.keylen = keylen;
- snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s",
+ snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s%s",
*timestamparg? timestamparg : "",
unattended? " --unattended":"",
+ mode1003? " --mode1003":"",
force? " --force":"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
inq_import_key_parms, &parm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Receive a secret key from the agent. HEXKEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). if OPENPGP_PROTECTED
is not zero, ensure that the key material is returned in RFC
4880-compatible passphrased-protected form; if instead MODE1003 is
not zero the raw gpg-agent private key format is requested (either
protected or unprotected). If CACHE_NONCE_ADDR is not NULL the
agent is advised to first try a passphrase associated with that
nonce. On success the key is stored as a canonical S-expression at
R_RESULT and R_RESULTLEN. */
gpg_error_t
agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int openpgp_protected, int mode1003, char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_result = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent supports the --mode1003 option. */
if (mode1003 && assuan_transact (agent_ctx,
"GETINFO cmd_has_option EXPORT_KEY mode1003",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s",
mode1003? "--mode1003" : openpgp_protected ? "--openpgp ":"",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
init_membuf_secure (&data, 1024);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
/* Status callback for handling confirmation. */
static gpg_error_t
confirm_status_cb (void *opaque, const char *line)
{
struct confirm_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "SETDESC")))
{
xfree (parm->desc);
parm->desc = unescape_status_string (s);
}
else if ((s = has_leading_keyword (line, "SETOK")))
{
xfree (parm->ok);
parm->ok = unescape_status_string (s);
}
else if ((s = has_leading_keyword (line, "SETNOTOK")))
{
xfree (parm->notok);
parm->notok = unescape_status_string (s);
}
return 0;
}
/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC
is not NULL, display DESC instead of the default description
message. If FORCE is true the agent is advised not to ask for
confirmation. */
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int force)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
struct confirm_parm_s confirm_parm;
memset (&confirm_parm, 0, sizeof confirm_parm);
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.confirm = &confirm_parm;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
/* FIXME: Shall we add support to DELETE_KEY for dual keys? */
snprintf (line, DIM(line), "DELETE_KEY%s %s",
force? " --force":"", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
confirm_status_cb, &confirm_parm);
xfree (confirm_parm.desc);
xfree (confirm_parm.ok);
xfree (confirm_parm.notok);
return err;
}
/* Ask the agent to change the passphrase of the key identified by
* HEXKEYGRIP. If DESC is not NULL, display DESC instead of the
* default description message. If CACHE_NONCE_ADDR is not NULL the
* agent is advised to first try a passphrase associated with that
* nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use
* the passphrase associated with that nonce for the new passphrase.
* If VERIFY is true the passphrase is only verified. */
gpg_error_t
agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
if (verify)
snprintf (line, DIM(line), "PASSWD %s%s --verify %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
else
snprintf (line, DIM(line), "PASSWD %s%s %s%s %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
hexkeygrip);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = passwd_nonce_addr;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Enable or disable the ephemeral mode. In ephemeral mode keys are
* created,searched and used in a per-session key store and not in the
* on-disk file. Set ENABLE to 1 to enable this mode, to 0 to disable
* this mode and to -1 to only query the current mode. If R_PREVIOUS
* is given the previously used state of the ephemeral mode is stored
* at that address. */
gpg_error_t
agent_set_ephemeral_mode (ctrl_t ctrl, int enable, int *r_previous)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
goto leave;
if (r_previous)
{
err = assuan_transact (agent_ctx, "GETINFO ephemeral",
NULL, NULL, NULL, NULL, NULL, NULL);
if (!err)
*r_previous = 1;
else if (gpg_err_code (err) == GPG_ERR_FALSE)
*r_previous = 0;
else
goto leave;
}
/* Skip setting if we are only querying or if the mode is already set. */
if (enable == -1 || (r_previous && !!*r_previous == !!enable))
err = 0;
else
err = assuan_transact (agent_ctx,
enable? "OPTION ephemeral=1" : "OPTION ephemeral=0",
NULL, NULL, NULL, NULL, NULL, NULL);
leave:
return err;
}
/* Return the version reported by gpg-agent. */
gpg_error_t
agent_get_version (ctrl_t ctrl, char **r_version)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
return err;
err = get_assuan_server_version (agent_ctx, 0, r_version);
return err;
}
diff --git a/g10/call-agent.h b/g10/call-agent.h
index 282ba066b..7b4e4abda 100644
--- a/g10/call-agent.h
+++ b/g10/call-agent.h
@@ -1,262 +1,262 @@
/* call-agent.h - Divert operations to the agent
* Copyright (C) 2003 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_G10_CALL_AGENT_H
#define GNUPG_G10_CALL_AGENT_H
struct key_attr {
int algo; /* Algorithm identifier. */
union {
unsigned int nbits; /* Supported keysize. */
const char *curve; /* Name of curve. */
};
};
struct agent_card_info_s
{
int error; /* private. */
char *reader; /* Reader information. */
char *apptype; /* Malloced application type string. */
unsigned int appversion; /* Version of the application. */
unsigned int manufacturer_id;
char *manufacturer_name; /* malloced. */
char *serialno; /* malloced hex string. */
char *disp_name; /* malloced. */
char *disp_lang; /* malloced. */
int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */
char *pubkey_url; /* malloced. */
char *login_data; /* malloced. */
char *private_do[4]; /* malloced. */
char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */
char cafpr2len;
char cafpr3len;
char cafpr1[20];
char cafpr2[20];
char cafpr3[20];
unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid. */
unsigned char fpr2len;
unsigned char fpr3len;
char fpr1[20];
char fpr2[20];
char fpr3[20];
u32 fpr1time;
u32 fpr2time;
u32 fpr3time;
char grp1[20]; /* The keygrip for OPENPGP.1 */
char grp2[20]; /* The keygrip for OPENPGP.2 */
char grp3[20]; /* The keygrip for OPENPGP.3 */
unsigned long sig_counter;
int chv1_cached; /* True if a PIN is not required for each
signing. Note that the gpg-agent might cache
it anyway. */
int is_v2; /* True if this is a v2 card. */
int chvmaxlen[3]; /* Maximum allowed length of a CHV. */
int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */
struct key_attr key_attr[3];
struct {
unsigned int ki:1; /* Key import available. */
unsigned int aac:1; /* Algorithm attributes are changeable. */
unsigned int kdf:1; /* KDF object to support PIN hashing available. */
unsigned int bt:1; /* Button for confirmation available. */
} extcap;
unsigned int status_indicator;
int kdf_do_enabled; /* Non-zero if card has a KDF object, 0 if not. */
int uif[3]; /* True if User Interaction Flag is on. */
strlist_t supported_keyalgo[3];
};
/* Object to store information from the KEYPAIRINFO or the KEYINFO
* status lines. */
struct keypair_info_s
{
struct keypair_info_s *next;
char keygrip[2 * KEYGRIP_LEN + 1]; /* Stored in hex. */
char *serialno; /* NULL or the malloced serialno. */
char *idstr; /* Malloced keyref (e.g. "OPENPGP.1") */
unsigned int usage; /* Key usage flags. */
u32 keytime; /* Key creation time from the card's DO. */
int algo; /* Helper to store the pubkey algo. */
};
typedef struct keypair_info_s *keypair_info_t;
/* Release the card info structure. */
void agent_release_card_info (struct agent_card_info_s *info);
/* Return card info. */
int agent_scd_learn (struct agent_card_info_s *info, int force);
/* Get the keypariinfo directly from scdaemon. */
gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref,
keypair_info_t *r_list);
/* Return list of cards. */
int agent_scd_cardlist (strlist_t *result);
/* Switch/assure a certain application. */
gpg_error_t agent_scd_switchapp (const char *appname);
/* Free a keypair info list. */
void free_keypair_info (keypair_info_t l);
/* Return card key information. */
gpg_error_t agent_scd_keyinfo (const char *keygrip, int cap,
keypair_info_t *result);
/* Return the serial number, possibly select by DEMAND. */
int agent_scd_serialno (char **r_serialno, const char *demand);
/* Send an APDU to the card. */
gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw);
/* Get attribute NAME from the card and store at R_VALUE. */
gpg_error_t agent_scd_getattr_one (const char *name, char **r_value);
/* Update INFO with the attribute NAME. */
int agent_scd_getattr (const char *name, struct agent_card_info_s *info);
/* send the KEYTOTPM command */
int agent_keytotpm (ctrl_t ctrl, const char *hexgrip);
/* Send the KEYTOCARD command. */
int agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp,
const char *ecdh_param_str);
/* Send a SETATTR command to the SCdaemon. */
gpg_error_t agent_scd_setattr (const char *name,
const void *value, size_t valuelen);
/* Send a WRITECERT command to the SCdaemon. */
int agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen);
/* Send a GENKEY command to the SCdaemon. */
int agent_scd_genkey (int keyno, int force, u32 *createtime);
/* Send a READCERT command to the SCdaemon. */
int agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen);
/* Send a READKEY command to the SCdaemon. */
gpg_error_t agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr,
gcry_sexp_t *r_result, u32 *r_keytime);
/* Change the PIN of an OpenPGP card or reset the retry counter. */
int agent_scd_change_pin (int chvno, const char *serialno);
/* Send the CHECKPIN command to the SCdaemon. */
int agent_scd_checkpin (const char *serialno);
/* Send the GET_PASSPHRASE command to the agent. */
gpg_error_t agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int newsymkey,
int repeat,
int check,
char **r_passphrase);
/* Send the CLEAR_PASSPHRASE command to the agent. */
gpg_error_t agent_clear_passphrase (const char *cache_id);
/* Present the prompt DESC and ask the user to confirm. */
gpg_error_t gpg_agent_get_confirmation (const char *desc);
/* Return the S2K iteration count as computed by gpg-agent. */
unsigned long agent_get_s2k_count (void);
/* Check whether a secret key for public key PK is available. Returns
0 if not available, positive value if the secret key is available. */
int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk);
/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock);
/* Return infos about the secret key with HEXKEYGRIP. */
gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext);
/* Generate a new key. */
gpg_error_t agent_genkey (ctrl_t ctrl,
char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase, time_t timestamp,
gcry_sexp_t *r_pubkey);
/* Apply the Link attributes. */
gpg_error_t agent_crosslink_keys (ctrl_t ctrl,
const char *hexgrip1, const char *hexgrip2);
/* Read a public key. FROMCARD may be 0, 1, or 2. */
gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey);
/* Create a signature. */
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *hexkeygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen,
int digestalgo,
gcry_sexp_t *r_sigval);
/* Decrypt a ciphertext. */
gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen,
int *r_padding);
/* Retrieve a key encryption key. */
gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen);
/* Send a key to the agent. */
-gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc,
+gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, int mode1003,
char **cache_nonce_addr, const void *key,
size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
u32 timestamp);
/* Receive a key from the agent. */
gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip,
const char *desc, int openpgp_protected,
int mode1003, char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo);
/* Delete a key from the agent. */
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip,
const char *desc, int force);
/* Change the passphrase of a key. */
gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int verify,
char **cache_nonce_addr, char **passwd_nonce_addr);
/* Set or get the ephemeral mode. */
gpg_error_t agent_set_ephemeral_mode (ctrl_t ctrl, int enable, int *r_previous);
/* Get the version reported by gpg-agent. */
gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version);
#endif /*GNUPG_G10_CALL_AGENT_H*/
diff --git a/g10/import.c b/g10/import.c
index ebfd73805..9affe057c 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -1,4916 +1,4941 @@
/* import.c - import a key into our key storage.
* Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2016, 2017, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "trustdb.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/recsel.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/membuf.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "key-check.h"
#include "key-clean.h"
struct import_stats_s
{
ulong count;
ulong no_user_id;
ulong imported;
ulong n_uids;
ulong n_sigs;
ulong n_subk;
ulong unchanged;
ulong n_revoc;
ulong secret_read;
ulong secret_imported;
ulong secret_dups;
ulong skipped_new_keys;
ulong not_imported;
ulong n_sigs_cleaned;
ulong n_uids_cleaned;
ulong v3keys; /* Number of V3 keys seen. */
};
/* Node flag to indicate that a user ID or a subkey has a
* valid self-signature. */
#define NODE_GOOD_SELFSIG 1
/* Node flag to indicate that a user ID or subkey has
* an invalid self-signature. */
#define NODE_BAD_SELFSIG 2
/* Node flag to indicate that the node shall be deleted. */
#define NODE_DELETION_MARK 4
/* A node flag used to temporary mark a node. */
#define NODE_FLAG_A 8
/* A flag used by transfer_secret_keys. */
#define NODE_TRANSFER_SECKEY 16
/* An object and a global instance to store selectors created from
* --import-filter keep-uid=EXPR.
* --import-filter drop-sig=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now. For now we use save and restore
* function to temporary change them.
*/
/* Definition of the import filters. */
struct import_filter_s
{
recsel_expr_t keep_uid;
recsel_expr_t drop_sig;
};
/* The current instance. */
struct import_filter_s import_filter;
static int import (ctrl_t ctrl,
IOBUF inp, const char* fname, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url);
static int read_block (IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys);
static void revocation_present (ctrl_t ctrl, kbnode_t keyblock);
static gpg_error_t import_one (ctrl_t ctrl,
kbnode_t keyblock,
struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len,
unsigned int options, int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid);
static gpg_error_t import_matching_seckeys (
ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch);
static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic);
static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats);
static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
int *non_self);
static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock,
u32 *keyid, unsigned int options,
kbnode_t *r_otherrevsigs);
static int any_uid_left (kbnode_t keyblock);
static void remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid);
static int merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig,
kbnode_t keyblock, u32 *keyid,
u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk );
static gpg_error_t append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node,
u32 curtime, int origin, const char *url,
int *n_sigs);
static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs);
static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static void
release_import_filter (import_filter_t filt)
{
recsel_release (filt->keep_uid);
filt->keep_uid = NULL;
recsel_release (filt->drop_sig);
filt->drop_sig = NULL;
}
static void
cleanup_import_globals (void)
{
release_import_filter (&import_filter);
}
int
parse_import_options(char *str,unsigned int *options,int noisy)
{
struct parse_options import_opts[]=
{
{"import-local-sigs",IMPORT_LOCAL_SIGS,NULL,
N_("import signatures that are marked as local-only")},
{"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,
N_("repair damage from the pks keyserver during import")},
{"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL,
N_("do not clear the ownertrust values during import")},
{"fast-import",IMPORT_FAST,NULL,
N_("do not update the trustdb after import")},
{"bulk-import",IMPORT_BULK, NULL,
N_("enable bulk import mode")},
{"import-show",IMPORT_SHOW,NULL,
N_("show key during import")},
{"show-only", (IMPORT_SHOW | IMPORT_DRY_RUN), NULL,
N_("show key but do not actually import") },
{"merge-only",IMPORT_MERGE_ONLY,NULL,
N_("only accept updates to existing keys")},
{"import-clean",IMPORT_CLEAN,NULL,
N_("remove unusable parts from key after import")},
{"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL,
N_("remove as much as possible from key after import")},
{"self-sigs-only", IMPORT_SELF_SIGS_ONLY, NULL,
N_("ignore key-signatures which are not self-signatures")},
{"import-export", IMPORT_EXPORT, NULL,
N_("run import filters and export key immediately")},
{"restore", IMPORT_RESTORE, NULL,
N_("assume the GnuPG key backup format")},
{"import-restore", IMPORT_RESTORE, NULL, NULL},
{"repair-keys", IMPORT_REPAIR_KEYS, NULL,
N_("repair keys on import")},
/* New options. Right now, without description string. */
{"ignore-attributes", IMPORT_IGNORE_ATTRIBUTES, NULL, NULL},
{"only-pubkeys", IMPORT_ONLY_PUBKEYS, NULL,
N_("do not import secret keys")},
/* Hidden options which are enabled by default and are provided
* in case of problems with the respective implementation. */
{"collapse-uids", IMPORT_COLLAPSE_UIDS, NULL, NULL},
{"collapse-subkeys", IMPORT_COLLAPSE_SUBKEYS, NULL, NULL},
/* Aliases for backward compatibility */
{"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL},
{"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL},
/* dummy */
{"import-unusable-sigs",0,NULL,NULL},
{"import-clean-sigs",0,NULL,NULL},
{"import-clean-uids",0,NULL,NULL},
{"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to
the new design. */
{NULL,0,NULL,NULL}
};
int rc;
int saved_self_sigs_only, saved_import_clean;
/* We need to set flags indicating whether the user has set certain
* options or if they came from the default. */
saved_self_sigs_only = (*options & IMPORT_SELF_SIGS_ONLY);
saved_self_sigs_only &= ~IMPORT_SELF_SIGS_ONLY;
saved_import_clean = (*options & IMPORT_CLEAN);
saved_import_clean &= ~IMPORT_CLEAN;
rc = parse_options (str, options, import_opts, noisy);
if (rc && (*options & IMPORT_SELF_SIGS_ONLY))
opt.flags.expl_import_self_sigs_only = 1;
else
*options |= saved_self_sigs_only;
if (rc && (*options & IMPORT_CLEAN))
opt.flags.expl_import_clean = 1;
else
*options |= saved_import_clean;
if (rc && (*options & IMPORT_RESTORE))
{
/* Alter other options we want or don't want for restore. */
*options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST);
*options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN
| IMPORT_REPAIR_PKS_SUBKEY_BUG
| IMPORT_MERGE_ONLY);
}
return rc;
}
/* Parse and set an import filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is all called
* several times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be imported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*/
gpg_error_t
parse_and_set_import_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_import_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&import_filter.keep_uid, string+9);
else if (!strncmp (string, "drop-sig=", 9))
err = recsel_parse_expr (&import_filter.drop_sig, string+9);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Save the current import filters, return them, and clear the current
* filters. Returns NULL on error and sets ERRNO. */
import_filter_t
save_and_clear_import_filter (void)
{
import_filter_t filt;
filt = xtrycalloc (1, sizeof *filt);
if (!filt)
return NULL;
*filt = import_filter;
memset (&import_filter, 0, sizeof import_filter);
return filt;
}
/* Release the current import filters and restore them from NEWFILT.
* Ownership of NEWFILT is moved to this function. */
void
restore_import_filter (import_filter_t filt)
{
if (filt)
{
release_import_filter (&import_filter);
import_filter = *filt;
xfree (filt);
}
}
import_stats_t
import_new_stats_handle (void)
{
return xmalloc_clear ( sizeof (struct import_stats_s) );
}
void
import_release_stats_handle (import_stats_t p)
{
xfree (p);
}
/* Read a key from a file. Only the first key in the file is
* considered and stored at R_KEYBLOCK. FNAME is the name of the
* file.
*/
gpg_error_t
read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname,
const void *buffer, size_t buflen,
kbnode_t *r_keyblock)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL;
u32 keyid[2];
int v3keys; /* Dummy */
int non_self; /* Dummy */
(void)ctrl;
*r_keyblock = NULL;
log_assert (!!fname ^ !!buffer);
if (fname)
{
inp = iobuf_open (fname);
if (!inp)
err = gpg_error_from_syserror ();
else if (is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
err = gpg_error (GPG_ERR_EPERM);
}
else
err = 0;
if (err)
{
log_error (_("can't open '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
/* Push the armor filter. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
}
else /* Read from buffer (No armor expected). */
{
inp = iobuf_temp_with_content (buffer, buflen);
}
/* Read the first non-v3 keyblock. */
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
break;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
release_kbnode (keyblock);
keyblock = NULL;
}
if (err)
{
if (gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"),
fname? (iobuf_is_pipe_filename (fname)? "[stdin]": fname)
/* */ : "[buffer]",
gpg_strerror (err));
goto leave;
}
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
if (!find_next_kbnode (keyblock, PKT_USER_ID))
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
/* We do the collapsing unconditionally although it is expected that
* clean keys are provided here. */
collapse_uids (&keyblock);
collapse_subkeys (&keyblock);
clear_kbnode_flags (keyblock);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
{
err = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (!delete_inv_parts (ctrl, keyblock, keyid, 0, NULL) )
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
leave:
if (inp)
{
iobuf_close (inp);
/* Must invalidate that ugly cache to actually close the file. */
if (fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
}
release_kbnode (keyblock);
/* FIXME: Do we need to free PENDING_PKT ? */
return err;
}
/* Import an already checked public key which was included in a
* signature and the signature verified out using this key. */
gpg_error_t
import_included_key_block (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
struct import_stats_s *stats;
import_filter_t save_filt;
int save_armor = opt.armor;
opt.armor = 0;
stats = import_new_stats_handle ();
save_filt = save_and_clear_import_filter ();
if (!save_filt)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: Should we introduce a dedicated KEYORG ? */
err = import_one (ctrl, keyblock,
stats, NULL, 0, 0, 0, 0,
NULL, NULL, KEYORG_UNKNOWN, NULL, NULL);
leave:
restore_import_filter (save_filt);
import_release_stats_handle (stats);
opt.armor = save_armor;
return err;
}
/*
* Import the public keys from the given filename. Input may be armored.
* This function rejects all keys which are not validly self signed on at
* least one userid. Only user ids which are self signed will be imported.
* Other signatures are not checked.
*
* Actually this function does a merge. It works like this:
*
* - get the keyblock
* - check self-signatures and remove all userids and their signatures
* without/invalid self-signatures.
* - reject the keyblock, if we have no valid userid.
* - See whether we have this key already in one of our pubrings.
* If not, simply add it to the default keyring.
* - Compare the key and the self-signatures of the new and the one in
* our keyring. If they are different something weird is going on;
* ask what to do.
* - See whether we have only non-self-signature on one user id; if not
* ask the user what to do.
* - compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* (consider looking at the timestamp and use the newest?)
* - Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* - Proceed with next signature.
*
* Key revocation certificates have special handling.
*/
static gpg_error_t
import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
int i;
gpg_error_t err = 0;
struct import_stats_s *stats = stats_handle;
if (!stats)
stats = import_new_stats_handle ();
if (inp)
{
err = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
}
else
{
if (!fnames && !nnames)
nnames = 1; /* Ohh what a ugly hack to jump into the loop */
for (i=0; i < nnames; i++)
{
const char *fname = fnames? fnames[i] : NULL;
IOBUF inp2 = iobuf_open(fname);
if (!fname)
fname = "[stdin]";
if (inp2 && is_secured_file (iobuf_get_fd (inp2)))
{
iobuf_close (inp2);
inp2 = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp2)
log_error (_("can't open '%s': %s\n"), fname, strerror (errno));
else
{
err = import (ctrl, inp2, fname, stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp2);
/* Must invalidate that ugly cache to actually close it. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
if (err)
log_error ("import from '%s' failed: %s\n",
fname, gpg_strerror (err) );
}
if (!fname)
break;
}
}
if (!stats_handle)
{
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))
!= (IMPORT_SHOW | IMPORT_DRY_RUN))
import_print_stats (stats);
import_release_stats_handle (stats);
}
/* If no fast import and the trustdb is dirty (i.e. we added a key
or userID that had something other than a selfsig, a signature
that was other than a selfsig, or any revocation), then
update/check the trustdb if the user specified by setting
interactive or by not setting no-auto-check-trustdb */
if (!(options & IMPORT_FAST))
check_or_update_trustdb (ctrl);
return err;
}
void
import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_handle, unsigned int options,
int origin, const char *url)
{
import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle,
NULL, NULL, options, NULL, NULL, origin, url);
}
gpg_error_t
import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
gpg_error_t err;
iobuf_t inp;
inp = iobuf_esopen (fp, "rb", 1, 0);
if (!inp)
{
err = gpg_error_from_syserror ();
log_error ("iobuf_esopen failed: %s\n", gpg_strerror (err));
return err;
}
err = import_keys_internal (ctrl, inp, NULL, 0, stats_handle,
fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp);
return err;
}
static int
import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats,
unsigned char **fpr,size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */
int rc = 0;
int v3keys;
getkey_disable_caches ();
if (!opt.no_armor) /* Armored reading is not disabled. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
while (!(rc = read_block (inp, options, &pending_pkt, &keyblock, &v3keys)))
{
stats->v3keys += v3keys;
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
{
rc = import_one (ctrl, keyblock,
stats, fpr, fpr_len, options, 0, 0,
screener, screener_arg, origin, url, NULL);
if (secattic)
{
byte tmpfpr[MAX_FINGERPRINT_LEN];
size_t tmpfprlen;
if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
/* Kudge for PGP desktop - see below. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key,
tmpfpr, &tmpfprlen);
rc = import_matching_seckeys (ctrl, secattic,
tmpfpr, tmpfprlen,
stats, opt.batch);
}
release_kbnode (secattic);
secattic = NULL;
}
}
else if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
release_kbnode (secattic);
secattic = NULL;
rc = import_secret_one (ctrl, keyblock, stats,
opt.batch, options, 0,
screener, screener_arg, &secattic);
keyblock = NULL; /* Ownership was transferred. */
if (secattic)
{
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
rc = 0; /* Try import after the next pubkey. */
/* The attic is a workaround for the peculiar PGP
* Desktop method of exporting a secret key: The
* exported file is the concatenation of two armored
* keyblocks; first the private one and then the public
* one. The strange thing is that the secret one has no
* binding signatures at all and thus we have not
* imported it. The attic stores that secret keys and
* we try to import it once after the very next public
* keyblock. */
}
}
else if (keyblock->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (keyblock->pkt->pkt.signature) )
{
release_kbnode (secattic);
secattic = NULL;
rc = import_revoke_cert (ctrl, keyblock, options, stats);
}
else
{
release_kbnode (secattic);
secattic = NULL;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
}
release_kbnode (keyblock);
/* fixme: we should increment the not imported counter but
this does only make sense if we keep on going despite of
errors. For now we do this only if the imported key is too
large. */
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
stats->not_imported++;
}
else if (rc)
break;
if (!(++stats->count % 100) && !opt.quiet)
log_info (_("%lu keys processed so far\n"), stats->count );
if (origin == KEYORG_WKD && stats->count >= 5)
{
/* We limit the number of keys _received_ from the WKD to 5.
* In fact there should be only one key but some sites want
* to store a few expired keys there also. gpg's key
* selection will later figure out which key to use. Note
* that for WKD we always return the fingerprint of the
* first imported key. */
log_info ("import from WKD stopped after %d keys\n", 5);
break;
}
}
stats->v3keys += v3keys;
if (rc == -1)
rc = 0;
else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc));
release_kbnode (secattic);
/* When read_block loop was stopped by error, we have PENDING_PKT left. */
if (pending_pkt)
{
free_packet (pending_pkt, NULL);
xfree (pending_pkt);
}
return rc;
}
/* Helper to migrate secring.gpg to GnuPG 2.1. */
gpg_error_t
import_old_secring (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
struct import_stats_s *stats;
int v3keys;
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
getkey_disable_caches();
stats = import_new_stats_handle ();
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1,
NULL, NULL, NULL);
keyblock = NULL; /* Ownership was transferred. */
}
release_kbnode (keyblock);
if (err)
break;
}
import_release_stats_handle (stats);
if (err == -1)
err = 0;
else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
else if (err)
log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err));
iobuf_close (inp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
return err;
}
void
import_print_stats (import_stats_t stats)
{
if (!opt.quiet)
{
log_info(_("Total number processed: %lu\n"),
stats->count + stats->v3keys);
if (stats->v3keys)
log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys);
if (stats->skipped_new_keys )
log_info(_(" skipped new keys: %lu\n"),
stats->skipped_new_keys );
if (stats->no_user_id )
log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id );
if (stats->imported)
{
log_info(_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged )
log_info(_(" unchanged: %lu\n"), stats->unchanged );
if (stats->n_uids )
log_info(_(" new user IDs: %lu\n"), stats->n_uids );
if (stats->n_subk )
log_info(_(" new subkeys: %lu\n"), stats->n_subk );
if (stats->n_sigs )
log_info(_(" new signatures: %lu\n"), stats->n_sigs );
if (stats->n_revoc )
log_info(_(" new key revocations: %lu\n"), stats->n_revoc );
if (stats->secret_read )
log_info(_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported )
log_info(_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups )
log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported )
log_info(_(" not imported: %lu\n"), stats->not_imported );
if (stats->n_sigs_cleaned)
log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned);
if (stats->n_uids_cleaned)
log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned);
}
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf,
"%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count + stats->v3keys,
stats->no_user_id,
stats->imported,
stats->unchanged,
stats->n_uids,
stats->n_subk,
stats->n_sigs,
stats->n_revoc,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
stats->skipped_new_keys,
stats->not_imported,
stats->v3keys );
write_status_text (STATUS_IMPORT_RES, buf);
}
}
/* Return true if PKTTYPE is valid in a keyblock. */
static int
valid_keyblock_packet (int pkttype)
{
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_RING_TRUST:
return 1;
default:
return 0;
}
}
/* Read the next keyblock from stream A. Meta data (ring trust
* packets) are only considered if OPTIONS has the IMPORT_RESTORE flag
* set. PENDING_PKT should be initialized to NULL and not changed by
* the caller.
*
* Returns 0 for okay, -1 no more blocks, or any other errorcode. The
* integer at R_V3KEY counts the number of unsupported v3 keyblocks.
*/
static int
read_block( IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys)
{
int rc;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t root = NULL;
kbnode_t lastnode = NULL;
int in_cert, in_v3key, skip_sigs;
u32 keyid[2];
int got_keyid = 0;
unsigned int dropped_nonselfsigs = 0;
*r_v3keys = 0;
if (*pending_pkt)
{
root = lastnode = new_kbnode( *pending_pkt );
*pending_pkt = NULL;
log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY
|| root->pkt->pkttype == PKT_SECRET_KEY);
in_cert = 1;
keyid_from_pk (root->pkt->pkt.public_key, keyid);
got_keyid = 1;
}
else
in_cert = 0;
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
if (!(options & IMPORT_RESTORE))
parsectx.skip_meta = 1;
in_v3key = 0;
skip_sigs = 0;
while ((rc=parse_packet (&parsectx, pkt)) != -1)
{
if (rc && (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY
&& (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)))
{
in_v3key = 1;
++*r_v3keys;
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
else if (rc ) /* (ignore errors) */
{
skip_sigs = 0;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET)
; /* Do not show a diagnostic. */
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_USER_ID
|| pkt->pkttype == PKT_ATTRIBUTE))
{
/* This indicates a too large user id or attribute
* packet. We skip this packet and all following
* signatures. Sure, this won't allow to repair a
* garbled keyring in case one of the signatures belong
* to another user id. However, this better mitigates
* DoS using inserted user ids. */
skip_sigs = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_OLD_COMMENT
|| pkt->pkttype == PKT_COMMENT))
; /* Ignore too large comment packets. */
else
{
log_error("read_block: read error: %s\n", gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
goto ready;
}
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
else if ((opt.import_options & IMPORT_IGNORE_ATTRIBUTES)
&& (pkt->pkttype == PKT_USER_ID || pkt->pkttype == PKT_ATTRIBUTE)
&& pkt->pkt.user_id->attrib_data)
{
skip_sigs = 1;
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (skip_sigs)
{
if (pkt->pkttype == PKT_SIGNATURE)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
skip_sigs = 0;
}
if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
in_v3key = 0;
if (!root && pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (pkt->pkt.signature) )
{
/* This is a revocation certificate which is handled in a
* special way. */
root = new_kbnode( pkt );
pkt = NULL;
goto ready;
}
/* Make a linked list of all packets. */
switch (pkt->pkttype)
{
case PKT_COMPRESSED:
if (!(opt.compat_flags & COMPAT_COMPR_KEYS))
{
rc = GPG_ERR_UNEXPECTED_PACKET;
goto ready;
}
else if (check_compress_algo (pkt->pkt.compressed->algorithm))
{
rc = GPG_ERR_COMPR_ALGO;
goto ready;
}
else
{
compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx );
pkt->pkt.compressed->buf = NULL;
if (push_compress_filter2 (a, cfx,
pkt->pkt.compressed->algorithm, 1))
xfree (cfx); /* e.g. in case of compression_algo NONE. */
}
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_RING_TRUST:
/* Skip those packets unless we are in restore mode. */
if ((opt.import_options & IMPORT_RESTORE))
goto x_default;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_SIGNATURE:
if (!in_cert)
goto x_default;
if (!(options & IMPORT_SELF_SIGS_ONLY))
goto x_default;
log_assert (got_keyid);
if (pkt->pkt.signature->keyid[0] == keyid[0]
&& pkt->pkt.signature->keyid[1] == keyid[1])
{ /* This is likely a self-signature. We import this one.
* Eventually we should use the ISSUER_FPR to compare
* self-signatures, but that will work only for v5 keys
* which are currently not even deployed.
* Note that we do not do any crypto verify here because
* that would defeat this very mitigation of DoS by
* importing a key with a huge amount of faked
* key-signatures. A verification will be done later in
* the processing anyway. Here we want a cheap an early
* way to drop non-self-signatures. */
goto x_default;
}
/* Skip this signature. */
dropped_nonselfsigs++;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
if (!got_keyid)
{
keyid_from_pk (pkt->pkt.public_key, keyid);
got_keyid = 1;
}
if (in_cert) /* Store this packet. */
{
*pending_pkt = pkt;
pkt = NULL;
goto ready;
}
in_cert = 1;
goto x_default;
default:
x_default:
if (in_cert && valid_keyblock_packet (pkt->pkttype))
{
if (!root )
root = lastnode = new_kbnode (pkt);
else
{
lastnode->next = new_kbnode (pkt);
lastnode = lastnode->next;
}
pkt = xmalloc (sizeof *pkt);
}
else
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
}
}
ready:
if (rc == -1 && root )
rc = 0;
if (rc )
release_kbnode( root );
else
*ret_root = root;
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree( pkt );
if (!rc && dropped_nonselfsigs && opt.verbose)
log_info ("key %s: number of dropped non-self-signatures: %u\n",
keystr (keyid), dropped_nonselfsigs);
return rc;
}
/* Walk through the subkeys on a pk to find if we have the PKS
disease: multiple subkeys with their binding sigs stripped, and the
sig for the first subkey placed after the last subkey. That is,
instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have
"pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2
and sub3, as they are already lost, but we can try and rescue sub1
by reordering the keyblock so that it reads "pk uid sig sub1 bind1
sub2 sub3". Returns TRUE if the keyblock was modified. */
static int
fix_pks_corruption (ctrl_t ctrl, kbnode_t keyblock)
{
int changed = 0;
int keycount = 0;
kbnode_t node;
kbnode_t last = NULL;
kbnode_t sknode=NULL;
/* First determine if we have the problem at all. Look for 2 or
more subkeys in a row, followed by a single binding sig. */
for (node=keyblock; node; last=node, node=node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
keycount++;
if(!sknode)
sknode=node;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_SUBKEY_SIG (node->pkt->pkt.signature)
&& keycount >= 2
&& !node->next)
{
/* We might have the problem, as this key has two subkeys in
a row without any intervening packets. */
/* Sanity check */
if (!last)
break;
/* Temporarily attach node to sknode. */
node->next = sknode->next;
sknode->next = node;
last->next = NULL;
/* Note we aren't checking whether this binding sig is a
selfsig. This is not necessary here as the subkey and
binding sig will be rejected later if that is the
case. */
if (check_key_signature (ctrl, keyblock,node,NULL))
{
/* Not a match, so undo the changes. */
sknode->next = node->next;
last->next = node;
node->next = NULL;
break;
}
else
{
/* Mark it good so we don't need to check it again */
sknode->flag |= NODE_GOOD_SELFSIG;
changed = 1;
break;
}
}
else
keycount = 0;
}
return changed;
}
/* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus
direct key signatures. A side effect of this was that a later
import of the same good direct key signatures was not possible
because the cmp_signature check in merge_blocks considered them
equal. Although direct key signatures are now checked during
import, there might still be bogus signatures sitting in a keyring.
We need to detect and delete them before doing a merge. This
function returns the number of removed sigs. */
static int
fix_bad_direct_key_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid)
{
gpg_error_t err;
kbnode_t node;
int count = 0;
for (node = keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
err = check_key_signature (ctrl, keyblock, node, NULL);
if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO )
{
/* If we don't know the error, we can't decide; this is
not a problem because cmp_signature can't compare the
signature either. */
log_info ("key %s: invalid direct key signature removed\n",
keystr (keyid));
delete_kbnode (node);
count++;
}
}
}
return count;
}
static void
print_import_ok (PKT_public_key *pk, unsigned int reason)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char buf[MAX_FINGERPRINT_LEN*2+30], *p;
size_t i, n;
snprintf (buf, sizeof buf, "%u ", reason);
p = buf + strlen (buf);
fingerprint_from_pk (pk, array, &n);
s = array;
for (i=0; i < n ; i++, s++, p += 2)
sprintf (p, "%02X", *s);
write_status_text (STATUS_IMPORT_OK, buf);
}
static void
print_import_check (PKT_public_key * pk, PKT_user_id * id)
{
byte hexfpr[2*MAX_FINGERPRINT_LEN+1];
u32 keyid[2];
keyid_from_pk (pk, keyid);
hexfingerprint (pk, hexfpr, sizeof hexfpr);
write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s",
keyid[0], keyid[1], hexfpr, id->name);
}
static void
check_prefs_warning(PKT_public_key *pk)
{
log_info(_("WARNING: key %s contains preferences for unavailable\n"
"algorithms on these user IDs:\n"), keystr_from_pk(pk));
}
static void
check_prefs (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk;
int problem=0;
merge_keys_and_selfsig (ctrl, keyblock);
pk=keyblock->pkt->pkt.public_key;
for(node=keyblock;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->created
&& node->pkt->pkt.user_id->prefs)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
char *user = utf8_to_native(uid->name,strlen(uid->name),0);
for(;prefs->type;prefs++)
{
char num[10]; /* prefs->value is a byte, so we're over
safe here */
sprintf(num,"%u",prefs->value);
if(prefs->type==PREFTYPE_SYM)
{
if (openpgp_cipher_test_algo (prefs->value))
{
const char *algo =
(openpgp_cipher_test_algo (prefs->value)
? num
: openpgp_cipher_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for cipher"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_AEAD)
{
if (openpgp_aead_test_algo (prefs->value))
{
/* FIXME: The test below is wrong. We should
* check if ...algo_name yields a "?" and
* only in that case use NUM. */
const char *algo =
(openpgp_aead_test_algo (prefs->value)
? num
: openpgp_aead_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for AEAD"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_HASH)
{
if(openpgp_md_test_algo(prefs->value))
{
const char *algo =
(gcry_md_test_algo (prefs->value)
? num
: gcry_md_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for digest"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_ZIP)
{
if(check_compress_algo (prefs->value))
{
const char *algo=compress_algo_to_string(prefs->value);
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for compression"
" algorithm %s\n"),user,algo?algo:num);
problem=1;
}
}
}
xfree(user);
}
}
if(problem)
{
log_info(_("it is strongly suggested that you update"
" your preferences and\n"));
log_info(_("re-distribute this key to avoid potential algorithm"
" mismatch problems\n"));
if(!opt.batch)
{
strlist_t sl = NULL;
strlist_t locusr = NULL;
size_t fprlen=0;
byte fpr[MAX_FINGERPRINT_LEN], *p;
char username[(MAX_FINGERPRINT_LEN*2)+1];
unsigned int i;
p = fingerprint_from_pk (pk,fpr,&fprlen);
for(i=0;i<fprlen;i++,p++)
sprintf(username+2*i,"%02X",*p);
add_to_strlist(&locusr,username);
append_to_strlist(&sl,"updpref");
append_to_strlist(&sl,"save");
keyedit_menu (ctrl, username, locusr, sl, 1, 1 );
free_strlist(sl);
free_strlist(locusr);
}
else if(!opt.quiet)
log_info(_("you can update your preferences with:"
" gpg --edit-key %s updpref save\n"),keystr_from_pk(pk));
}
}
/* Helper for apply_*_filter in import.c and export.c and also used by
* keylist.c. */
const char *
impex_filter_getval (void *cookie, const char *propname)
{
/* FIXME: Malloc our static buffers and access them via PARM. */
struct impex_filter_parm_s *parm = cookie;
ctrl_t ctrl = parm->ctrl;
kbnode_t node = parm->node;
static char numbuf[20];
const char *result;
const char *s;
enum { scpNone = 0, scpPub, scpSub, scpUid, scpSig} scope = 0;
log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC);
/* We allow a prefix delimited by a slash to limit the scope of the
* keyword. Note that "pub" also includes "sec" and "sub" includes
* "ssb". */
if (DBG_RECSEL) /* Printing the packet type is useful. */
log_debug ("%s: pkttype=%s\n", __func__, pkttype_str (node->pkt->pkttype));
if ((s=strchr (propname, '/')) && s != propname)
{
size_t n = s - propname;
if (!strncmp (propname, "pub", n))
scope = scpPub;
else if (!strncmp (propname, "sub", n))
scope = scpSub;
else if (!strncmp (propname, "uid", n))
scope = scpUid;
else if (!strncmp (propname, "sig", n))
scope = scpSig;
propname = s + 1;
}
if ((node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
&& (!scope || scope == scpUid))
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!strcmp (propname, "uid"))
result = uid->name;
else if (!strcmp (propname, "mbox"))
{
if (!uid->mbox)
{
uid->mbox = mailbox_from_userid (uid->name, 0);
}
result = uid->mbox;
}
else if (!strcmp (propname, "primary"))
{
result = uid->flags.primary? "1":"0";
}
else if (!strcmp (propname, "expired"))
{
result = uid->flags.expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = uid->flags.revoked? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (!scope || scope == scpSig))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (!strcmp (propname, "sig_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "sig_created_d"))
{
result = dateonlystr_from_sig (sig);
}
else if (!strcmp (propname, "sig_expires"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->expiredate);
result = numbuf;
}
else if (!strcmp (propname, "sig_expires_d"))
{
static char exdatestr[MK_DATESTR_SIZE];
if (sig->expiredate)
result = mk_datestr (exdatestr, sizeof exdatestr, sig->expiredate);
else
result = "";
}
else if (!strcmp (propname, "sig_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "sig_digest_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo);
result = numbuf;
}
else if (!strcmp (propname, "expired"))
{
result = sig->flags.expired? "1":"0";
}
else
result = NULL;
}
else if (((node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
&& (!scope || scope == scpPub))
|| ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& (!scope || scope == scpSub)))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (!strcmp (propname, "secret"))
{
result = (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0";
}
else if (!strcmp (propname, "key_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "key_size"))
{
snprintf (numbuf, sizeof numbuf, "%u", nbits_from_pk (pk));
result = numbuf;
}
else if (!strcmp (propname, "algostr"))
{
pubkey_string (pk, parm->hexfpr, sizeof parm->hexfpr);
result = parm->hexfpr;
}
else if (!strcmp (propname, "key_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "key_created_d"))
{
result = dateonlystr_from_pk (pk);
}
else if (!strcmp (propname, "key_expires"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->expiredate);
result = numbuf;
}
else if (!strcmp (propname, "key_expires_d"))
{
static char exdatestr[MK_DATESTR_SIZE];
if (pk->expiredate)
result = mk_datestr (exdatestr, sizeof exdatestr, pk->expiredate);
else
result = "";
}
else if (!strcmp (propname, "expired"))
{
result = pk->has_expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = pk->flags.revoked? "1":"0";
}
else if (!strcmp (propname, "disabled"))
{
result = pk_is_disabled (pk)? "1":"0";
}
else if (!strcmp (propname, "usage"))
{
snprintf (numbuf, sizeof numbuf, "%s%s%s%s%s",
(pk->pubkey_usage & PUBKEY_USAGE_ENC)?"e":"",
(pk->pubkey_usage & PUBKEY_USAGE_SIG)?"s":"",
(pk->pubkey_usage & PUBKEY_USAGE_CERT)?"c":"",
(pk->pubkey_usage & PUBKEY_USAGE_AUTH)?"a":"",
(pk->pubkey_usage & PUBKEY_USAGE_UNKNOWN)?"?":"");
result = numbuf;
}
else if (!strcmp (propname, "fpr"))
{
hexfingerprint (pk, parm->hexfpr, sizeof parm->hexfpr);
result = parm->hexfpr;
}
else if (!strcmp (propname, "origin"))
{
result = key_origin_string (pk->keyorg);
}
else if (!strcmp (propname, "lastupd"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->keyupdate);
result = numbuf;
}
else if (!strcmp (propname, "url"))
{
if (pk->updateurl && *pk->updateurl)
{
/* Fixme: This might get truncated. */
mem2str (parm->hexfpr, pk->updateurl, sizeof parm->hexfpr);
result = parm->hexfpr;
}
else
result = "";
}
else
result = NULL;
}
else
result = NULL;
return result;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-sig filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_sig_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
int active = 0;
u32 main_keyid[2];
PKT_signature *sig;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid);
/* Loop over all signatures for user id and attribute packets which
* are not self signatures. */
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready. */
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
active = 1;
if (!active)
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1])
continue; /* Skip self-signatures. */
if (IS_UID_SIG(sig) || IS_UID_REV(sig))
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
delete_kbnode (node);
}
}
}
/* Insert a key origin into a public key packet. */
static gpg_error_t
insert_key_origin_pk (PKT_public_key *pk, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* For WKD and DANE we insert origin information also for the
* key but we don't record the URL because we have have no use
* for that: An update using a keyserver has higher precedence
* and will thus update this origin info. For refresh using WKD
* or DANE we need to go via the User ID anyway. Recall that we
* are only inserting a new key. */
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we add the meta information. Note that the use of a
* fingerprint needs to be enforced by the caller of the import
* function. This is commonly triggered by verifying a modern
* signature which has an Issuer Fingerprint signature
* subpacket. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
else if (origin == KEYORG_FILE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
if (url)
{
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/* Insert a key origin into a user id packet. */
static gpg_error_t
insert_key_origin_uid (PKT_user_id *uid, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We insert origin information on a UID only when we received
* them via the Web Key Directory or a DANE record. The key we
* receive here from the WKD has been filtered to contain only
* the user ID as looked up in the WKD. For a DANE origin
* this should also be the case. Thus we will see here only one
* user id. */
uid->keyorg = origin;
uid->keyupdate = curtime;
if (url)
{
xfree (uid->updateurl);
uid->updateurl = xtrystrdup (url);
if (!uid->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we mark that also in the user ID. However we do not
* store the keyserver URL in the UID. A later update (merge)
* from a more trusted source will replace this info. */
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_FILE)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
return 0;
}
/* Apply meta data to KEYBLOCK. This sets the origin of the key to
* ORIGIN and the updateurl to URL. Note that this function is only
* used for a new key, that is not when we are merging keys. */
static gpg_error_t
insert_key_origin (kbnode_t keyblock, int origin, const char *url)
{
gpg_error_t err;
kbnode_t node;
u32 curtime = make_timestamp ();
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
;
else if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
err = insert_key_origin_pk (node->pkt->pkt.public_key, curtime,
origin, url);
if (err)
return err;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
err = insert_key_origin_uid (node->pkt->pkt.user_id, curtime,
origin, url);
if (err)
return err;
}
}
return 0;
}
/* Update meta data on KEYBLOCK. This updates the key origin on the
* public key according to ORIGIN and URL. The UIDs are already
* updated when this function is called. */
static gpg_error_t
update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url)
{
PKT_public_key *pk;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = keyblock->pkt->pkt.public_key;
if (pk->keyupdate > curtime)
; /* Don't do it for a time warp. */
else if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We only update the origin info if they either have never been
* set or are the origin was the same as the new one. If this
* is WKD we also update the UID to show from which user id this
* was updated. */
if (!pk->keyorg || pk->keyorg == KEYORG_WKD || pk->keyorg == KEYORG_DANE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (origin == KEYORG_WKD && url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
}
else if (origin == KEYORG_KS)
{
/* All updates from a keyserver are considered to have the
* freshed key. Thus we always set the new key origin. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_FILE)
{
/* Updates from a file are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
}
else if (origin == KEYORG_URL)
{
/* Updates from a URL are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/*
* Try to import one keyblock. Return an error only in serious cases,
* but never for an invalid keyblock. It uses log_error to increase
* the internal errorcount, so that invalid input can be detected by
* programs which called gpg. If SILENT is no messages are printed -
* even most error messages are suppressed. ORIGIN is the origin of
* the key (0 for unknown) and URL the corresponding URL. FROM_SK
* indicates that the key has been made from a secret key. If R_SAVED
* is not NULL a boolean will be stored indicating whether the
* keyblock has valid parts. Unless OTHERREVSIGS is NULL it is
* updated with encountered new revocation signatures.
*/
static gpg_error_t
import_one_real (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid,
kbnode_t *otherrevsigs)
{
gpg_error_t err = 0;
PKT_public_key *pk;
kbnode_t node, uidnode;
kbnode_t keyblock_orig = NULL;
byte fpr2[MAX_FINGERPRINT_LEN];
size_t fpr2len;
u32 keyid[2];
int new_key = 0;
int mod_key = 0;
int same_key = 0;
int non_self_or_utk = 0;
char pkstrbuf[PUBKEY_STRING_SIZE];
int merge_keys_done = 0;
int any_filter = 0;
KEYDB_HANDLE hd = NULL;
if (r_valid)
*r_valid = 0;
/* If show-only is active we don't won't any extra output. */
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN)))
silent = 1;
/* Get the key and print some info about it. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node )
BUG();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr2, &fpr2len);
if (MAX_FINGERPRINT_LEN > fpr2len)
memset (fpr2+fpr2len, 0, MAX_FINGERPRINT_LEN - fpr2len);
keyid_from_pk( pk, keyid );
uidnode = find_next_kbnode( keyblock, PKT_USER_ID );
if (opt.verbose && !opt.interactive && !silent && !from_sk)
{
/* Note that we do not print this info in FROM_SK mode
* because import_secret_one already printed that. */
log_info ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk), datestr_from_pk(pk) );
if (uidnode)
print_utf8_buffer (log_get_stream (),
uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len );
log_printf ("\n");
}
if (!uidnode)
{
if (!silent)
log_error( _("key %s: no user ID\n"), keystr_from_pk(pk));
return 0;
}
if (screener && screener (keyblock, screener_arg))
{
log_error (_("key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
return 0;
}
if (opt.interactive && !silent)
{
if (is_status_enabled())
print_import_check (pk, uidnode->pkt->pkt.user_id);
merge_keys_and_selfsig (ctrl, keyblock);
tty_printf ("\n");
show_basic_key_info (ctrl, keyblock, from_sk);
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("import.okay",
"Do you want to import this key? (y/N) "))
return 0;
}
/* Remove all non-self-sigs if requested. Note that this is a NOP if
* that option has been globally set but we may also be called
* latter with the already parsed keyblock and a locally changed
* option. This is why we need to remove them here as well. */
if ((options & IMPORT_SELF_SIGS_ONLY))
remove_all_non_self_sigs (&keyblock, keyid);
/* Remove or collapse the user ids. */
if ((options & IMPORT_COLLAPSE_UIDS))
collapse_uids (&keyblock);
if ((options & IMPORT_COLLAPSE_SUBKEYS))
collapse_subkeys (&keyblock);
/* Clean the key that we're about to import, to cut down on things
that we have to clean later. This has no practical impact on the
end result, but does result in less logging which might confuse
the user. */
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock,
opt.verbose,
(options&IMPORT_MINIMAL)? EXPORT_MINIMAL : 0,
NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
clear_kbnode_flags( keyblock );
if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG)
&& fix_pks_corruption (ctrl, keyblock)
&& opt.verbose)
log_info (_("key %s: PKS subkey corruption repaired\n"),
keystr_from_pk(pk));
if ((options & IMPORT_REPAIR_KEYS))
key_check_all_keysigs (ctrl, 1, keyblock, 0, 0);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self_or_utk))
return 0; /* Invalid keyblock - error already printed. */
/* If the imported key is marked as ultimately trusted key (using
* --trusted-key), we set the flag so that we can later set the
* revalidation mark. */
if (!non_self_or_utk)
{
/* Make sure the trustdb is initialized so that the UTK list is
* available. */
init_trustdb (ctrl, 1);
if (tdb_keyid_is_utk (keyid))
non_self_or_utk = 2;
}
/* If we allow such a thing, mark unsigned uids as valid */
if (opt.allow_non_selfsigned_uid)
{
for (node=keyblock; node; node = node->next )
if (node->pkt->pkttype == PKT_USER_ID
&& !(node->flag & NODE_GOOD_SELFSIG)
&& !(node->flag & NODE_BAD_SELFSIG) )
{
char *user=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
/* Fake a good signature status for the user id. */
node->flag |= NODE_GOOD_SELFSIG;
log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"),
keystr_from_pk(pk),user);
xfree(user);
}
}
/* Delete invalid parts and bail out if there are no user ids left. */
if (!delete_inv_parts (ctrl, keyblock, keyid, options, otherrevsigs))
{
if (!silent)
{
log_error ( _("key %s: no valid user IDs\n"), keystr_from_pk(pk));
if (!opt.quiet)
log_info(_("this may be caused by a missing self-signature\n"));
}
stats->no_user_id++;
return 0;
}
/* Get rid of deleted nodes. */
commit_kbnode (&keyblock);
/* Apply import filter. */
if (import_filter.keep_uid)
{
apply_keep_uid_filter (ctrl, keyblock, import_filter.keep_uid);
commit_kbnode (&keyblock);
any_filter = 1;
}
if (import_filter.drop_sig)
{
apply_drop_sig_filter (ctrl, keyblock, import_filter.drop_sig);
commit_kbnode (&keyblock);
any_filter = 1;
}
/* If we ran any filter we need to check that at least one user id
* is left in the keyring. Note that we do not use log_error in
* this case. */
if (any_filter && !any_uid_left (keyblock))
{
if (!opt.quiet )
log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk));
stats->no_user_id++;
return 0;
}
/* The keyblock is valid and ready for real import. */
if (r_valid)
*r_valid = 1;
/* Show the key in the form it is merged or inserted. We skip this
* if "import-export" is also active without --armor or the output
* file has explicily been given. */
if ((options & IMPORT_SHOW)
&& !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile))
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
/* Note that we do not want to show the validity because the key
* has not yet imported. */
err = list_keyblock_direct (ctrl, keyblock, from_sk, 0,
opt.fingerprint || opt.with_fingerprint, 1);
es_fflush (es_stdout);
no_usable_encr_subkeys_warning (keyblock);
if (err)
goto leave;
}
/* Write the keyblock to the output and do not actually import. */
if ((options & IMPORT_EXPORT))
{
if (!merge_keys_done)
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
}
err = write_keyblock_to_output (keyblock, opt.armor, opt.export_options);
goto leave;
}
if (opt.dry_run || (options & IMPORT_DRY_RUN))
goto leave;
/* Do we have this key already in one of our pubrings ? */
err = get_keyblock_byfpr_fast (ctrl, &keyblock_orig, &hd,
1 /*primary only */,
fpr2, fpr2len, 1/*locked*/);
if ((err
&& gpg_err_code (err) != GPG_ERR_NO_PUBKEY
&& gpg_err_code (err) != GPG_ERR_UNUSABLE_PUBKEY)
|| !hd)
{
/* The !hd above is to catch a misbehaving function which
* returns NO_PUBKEY for failing to allocate a handle. */
if (!silent)
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (err));
}
else if (err && ((opt.import_options|options)&IMPORT_MERGE_ONLY) )
{
if (opt.verbose && !silent )
log_info( _("key %s: new key - skipped\n"), keystr(keyid));
err = 0;
stats->skipped_new_keys++;
}
else if (err) /* Insert this key. */
{
/* Note: ERR can only be NO_PUBKEY or UNUSABLE_PUBKEY. */
int n_sigs_cleaned, n_uids_cleaned;
err = keydb_locate_writable (hd);
if (err)
{
log_error (_("no writable keyring found: %s\n"), gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (opt.verbose > 1 )
log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) );
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose,
(options&IMPORT_MINIMAL)? EXPORT_MINIMAL : 0,
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = insert_key_origin (keyblock, origin, url);
if (err)
{
log_error ("insert_key_origin failed: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
err = keydb_insert_keyblock (hd, keyblock);
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST))
{
/* This should not be possible since we delete the
ownertrust when a key is deleted, but it can happen if
the keyring and trustdb are out of sync. It can also
be made to happen with the trusted-key command and by
importing and locally exported key. */
clear_ownertrusts (ctrl, pk);
if (non_self_or_utk)
revalidation_mark (ctrl);
}
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. */
if (!err && !opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info (_("key %s: public key \"%s\" imported\n"),
keystr(keyid), p);
xfree(p);
}
if (!err && is_status_enabled())
{
char *us = get_long_user_id_string (ctrl, keyid);
write_status_text( STATUS_IMPORTED, us );
xfree(us);
print_import_ok (pk, 1);
}
if (!err)
{
stats->imported++;
new_key = 1;
}
}
else /* Key already exists - merge. */
{
int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned;
u32 curtime = make_timestamp ();
/* Compare the original against the new key; just to be sure nothing
* weird is going on */
if (cmp_public_keys (keyblock_orig->pkt->pkt.public_key, pk))
{
if (!silent)
log_error( _("key %s: doesn't match our copy\n"),keystr(keyid));
goto leave;
}
/* Make sure the original direct key sigs are all sane. */
n_sigs_cleaned = fix_bad_direct_key_sigs (ctrl, keyblock_orig, keyid);
if (n_sigs_cleaned)
commit_kbnode (&keyblock_orig);
/* Try to merge KEYBLOCK into KEYBLOCK_ORIG. */
clear_kbnode_flags( keyblock_orig );
clear_kbnode_flags( keyblock );
n_uids = n_sigs = n_subk = n_uids_cleaned = 0;
err = merge_blocks (ctrl, options, keyblock_orig, keyblock, keyid,
curtime, origin, url,
&n_uids, &n_sigs, &n_subk );
if (err)
goto leave;
/* Clean the final keyblock again if requested. we can't do
* this if only self-signatures are imported; see bug #4628. */
if ((options & IMPORT_CLEAN)
&& !(options & IMPORT_SELF_SIGS_ONLY))
{
merge_keys_and_selfsig (ctrl, keyblock_orig);
clean_all_uids (ctrl, keyblock_orig, opt.verbose,
(options&IMPORT_MINIMAL)? EXPORT_MINIMAL : 0,
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock_orig, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned)
{
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = update_key_origin (keyblock_orig, curtime, origin, url);
if (err)
{
log_error ("update_key_origin failed: %s\n",
gpg_strerror (err));
goto leave;
}
}
mod_key = 1;
/* KEYBLOCK_ORIG has been updated; write */
err = keydb_update_keyblock (ctrl, hd, keyblock_orig);
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (non_self_or_utk)
revalidation_mark (ctrl);
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. Print and update stats if we got no error.
* An error here comes from writing the keyblock and thus
* very likely means that no update happened. */
if (!err && !opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
if (n_uids == 1 )
log_info( _("key %s: \"%s\" 1 new user ID\n"),
keystr(keyid),p);
else if (n_uids )
log_info( _("key %s: \"%s\" %d new user IDs\n"),
keystr(keyid),p,n_uids);
if (n_sigs == 1 )
log_info( _("key %s: \"%s\" 1 new signature\n"),
keystr(keyid), p);
else if (n_sigs )
log_info( _("key %s: \"%s\" %d new signatures\n"),
keystr(keyid), p, n_sigs );
if (n_subk == 1 )
log_info( _("key %s: \"%s\" 1 new subkey\n"),
keystr(keyid), p);
else if (n_subk )
log_info( _("key %s: \"%s\" %d new subkeys\n"),
keystr(keyid), p, n_subk );
if (n_sigs_cleaned==1)
log_info(_("key %s: \"%s\" %d signature cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
else if (n_sigs_cleaned)
log_info(_("key %s: \"%s\" %d signatures cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
if (n_uids_cleaned==1)
log_info(_("key %s: \"%s\" %d user ID cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
else if (n_uids_cleaned)
log_info(_("key %s: \"%s\" %d user IDs cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
xfree(p);
}
if (!err)
{
stats->n_uids +=n_uids;
stats->n_sigs +=n_sigs;
stats->n_subk +=n_subk;
stats->n_sigs_cleaned +=n_sigs_cleaned;
stats->n_uids_cleaned +=n_uids_cleaned;
if (is_status_enabled () && !silent)
print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0)));
}
}
else
{
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* FIXME: We do not track the time we last checked a key for
* updates. To do this we would need to rewrite even the
* keys which have no changes. Adding this would be useful
* for the automatic update of expired keys via the WKD in
* case the WKD still carries the expired key. See
* get_best_pubkey_byname. */
same_key = 1;
if (is_status_enabled ())
print_import_ok (pk, 0);
if (!opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p);
xfree(p);
}
stats->unchanged++;
}
}
leave:
keydb_release (hd);
if (mod_key || new_key || same_key)
{
/* A little explanation for this: we fill in the fingerprint
when importing keys as it can be useful to know the
fingerprint in certain keyserver-related cases (a keyserver
asked for a particular name, but the key doesn't have that
name). However, in cases where we're importing more than
one key at a time, we cannot know which key to fingerprint.
In these cases, rather than guessing, we do not
fingerprinting at all, and we must hope the user ID on the
keys are useful. Note that we need to do this for new
keys, merged keys and even for unchanged keys. This is
required because for example the --auto-key-locate feature
may import an already imported key and needs to know the
fingerprint of the key in all cases. */
if (fpr)
{
/* Note that we need to compare against 0 here because
COUNT gets only incremented after returning from this
function. */
if (!stats->count)
{
xfree (*fpr);
*fpr = fingerprint_from_pk (pk, NULL, fpr_len);
}
else if (origin != KEYORG_WKD)
{
xfree (*fpr);
*fpr = NULL;
}
}
}
/* Now that the key is definitely incorporated into the keydb, we
need to check if a designated revocation is present or if the
prefs are not rational so we can warn the user. */
if (mod_key)
{
revocation_present (ctrl, keyblock_orig);
if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock_orig);
}
else if (new_key)
{
revocation_present (ctrl, keyblock);
if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock);
}
release_kbnode( keyblock_orig );
return err;
}
/* Wrapper around import_one_real to retry the import in some cases. */
static gpg_error_t
import_one (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid)
{
gpg_error_t err;
kbnode_t otherrevsigs = NULL;
kbnode_t node;
err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
from_sk, silent, screener, screener_arg,
origin, url, r_valid, &otherrevsigs);
if (gpg_err_code (err) == GPG_ERR_TOO_LARGE
&& gpg_err_source (err) == GPG_ERR_SOURCE_KEYBOX
&& ((options & (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN))
!= (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN)))
{
/* We hit the maximum image length. Ask the wrapper to do
* everything again but this time with some extra options. */
u32 keyid[2];
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
log_info ("key %s: keyblock too large, retrying with self-sigs-only\n",
keystr (keyid));
options |= IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN;
err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
from_sk, silent, screener, screener_arg,
origin, url, r_valid, &otherrevsigs);
}
/* Finally try to import other revocation certificates. For example
* those of a former key appended to the current key. */
if (!err)
{
for (node = otherrevsigs; node; node = node->next)
import_revoke_cert (ctrl, node, options, stats);
}
release_kbnode (otherrevsigs);
return err;
}
/* Convert our internal secret key object into an S-expression. PK is
* the public key. R_CURVE received an sexp with the name of the
* curve; caller must free this. R_SKEY will receive the result;
* caller must of course also free this. */
static gpg_error_t
internal_skey_object_to_sexp (PKT_public_key *pk, gcry_sexp_t *r_curve,
gcry_sexp_t *r_skey)
{
gpg_error_t err;
int nskey;
int i, j;
membuf_t mbuf;
const char *curvename;
char *curvestr = NULL;
void *format_args[2*PUBKEY_MAX_NSKEY];
*r_curve = NULL;
*r_skey = NULL;
init_membuf (&mbuf, 50);
nskey = pubkey_get_nskey (pk->pubkey_algo);
if (!nskey || nskey > PUBKEY_MAX_NSKEY)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
log_error ("internal error: %s\n", gpg_strerror (err));
goto leave;
}
put_membuf_str (&mbuf, "(skey");
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
/* The ECC case. */
curvestr = openpgp_oid_to_str (pk->pkey[0]);
if (!curvestr)
{
err = gpg_error_from_syserror ();
goto leave;
}
curvename = openpgp_oid_to_curve (curvestr, 1);
gcry_sexp_release (*r_curve);
err = gcry_sexp_build (r_curve, NULL, "(curve %s)",
curvename?curvename:curvestr);
if (err)
goto leave;
j = 0;
/* Append the public key element Q. */
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + 1;
/* Append the secret key element D. For ECDH we skip PKEY[2]
* because this holds the KEK which is not needed by gpg-agent. */
i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2;
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
/* Simple hack to print a warning for an invalid key in case of
* cv25519. We have only opaque MPIs here. */
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH
&& !strcmp (curvestr, "1.3.6.1.4.1.3029.1.5.1")
&& !gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1)
&& gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE))
{
const unsigned char *pp;
unsigned int nn;
pp = gcry_mpi_get_opaque (pk->pkey[i], &nn);
nn = (nn+7)/8;
if (pp && nn && (pp[nn-1] & 7))
log_info ("warning: lower 3 bits of the secret key"
" are not cleared\n");
}
}
else /* Standard case for the old (non-ECC) algorithms. */
{
for (i=j=0; i < nskey; i++)
{
if (!pk->pkey[i])
continue; /* Protected keys only have NPKEY+1 elements. */
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
put_membuf_str (&mbuf, ")");
put_membuf (&mbuf, "", 1);
/* Finally convert to an sexp and store that at R_SKEY. */
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (r_skey, NULL, format, format_args);
xfree (format);
}
leave:
xfree (curvestr);
xfree (get_membuf (&mbuf, NULL));
return err;
}
static gpg_error_t
build_classic_transfer_sexp (PKT_public_key *pk, gcry_sexp_t *result)
{
gpg_error_t err;
gcry_sexp_t skey;
gcry_sexp_t prot = NULL;
gcry_sexp_t curve = NULL;
struct seckey_info *ski = pk->seckey_info;
*result = NULL;
/* Convert our internal secret key object into an S-expression. */
err = internal_skey_object_to_sexp (pk, &curve, &skey);
if (err)
{
log_error ("error building skey array: %s\n", gpg_strerror (err));
goto leave;
}
log_assert (skey);
if (ski->is_protected)
{
char countbuf[35];
/* FIXME: Support AEAD */
/* Note that the IVLEN may be zero if we are working on a dummy
* key. We can't express that in an S-expression and thus we
* send dummy data for the IV. */
snprintf (countbuf, sizeof countbuf, "%lu",(unsigned long)ski->s2k.count);
err = gcry_sexp_build (&prot, NULL,
" (protection %s %s %b %d %s %b %s)\n",
ski->sha1chk? "sha1":"sum",
openpgp_cipher_algo_name (ski->algo),
ski->ivlen? (int)ski->ivlen:1,
ski->ivlen? ski->iv: (const unsigned char*)"X",
ski->s2k.mode,
openpgp_md_algo_name (ski->s2k.hash_algo),
(int)sizeof (ski->s2k.salt), ski->s2k.salt,
countbuf);
}
else
err = gcry_sexp_build (&prot, NULL, " (protection none)\n");
if (err)
goto leave;
err = gcry_sexp_build (result, NULL,
"(openpgp-private-key\n"
" (version %d)\n"
" (algo %s)\n"
" %S%S\n"
" (csum %d)\n"
" %S)\n",
pk->version,
openpgp_pk_algo_name (pk->pubkey_algo),
curve, skey,
(int)(unsigned long)ski->csum, prot);
leave:
gcry_sexp_release (prot);
gcry_sexp_release (skey);
gcry_sexp_release (curve);
return err;
}
/* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The
* function prints diagnostics and returns an error code. If BATCH is
* true the secret keys are stored by gpg-agent in the transfer format
* (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED
* is set, only those nodes with flag NODE_TRANSFER_SECKEY are
* processed. */
gpg_error_t
transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force,
int only_marked)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
kbnode_t ctx = NULL;
kbnode_t node;
PKT_public_key *main_pk, *pk;
struct seckey_info *ski;
gcry_sexp_t tmpsexp;
unsigned char *transferkey = NULL;
size_t transferkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
char *cache_nonce = NULL;
int stub_key_skipped = 0;
/* Get the current KEK. */
err = agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
/* Note: We need to use walk_kbnode so that we skip nodes which are
* marked as deleted. */
main_pk = NULL;
while ((node = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY))
continue;
pk = node->pkt->pkt.public_key;
if (!main_pk)
main_pk = pk;
/* Make sure the keyids are available. */
keyid_from_pk (pk, NULL);
if (node->pkt->pkttype == PKT_SECRET_KEY)
{
pk->main_keyid[0] = pk->keyid[0];
pk->main_keyid[1] = pk->keyid[1];
}
else
{
pk->main_keyid[0] = main_pk->keyid[0];
pk->main_keyid[1] = main_pk->keyid[1];
}
ski = pk->seckey_info;
if (!ski)
BUG ();
if (stats)
{
stats->count++;
stats->secret_read++;
}
/* We ignore stub keys. The way we handle them in other parts
of the code is by asking the agent whether any secret key is
available for a given keyblock and then concluding that we
have a secret key; all secret (sub)keys of the keyblock the
agent does not know of are then stub keys. This works also
for card stub keys. The learn command or the card-status
command may be used to check with the agent whether a card
has been inserted and a stub key is in turn generated by the
agent. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
stub_key_skipped = 1;
continue;
}
- err = build_classic_transfer_sexp (pk, &tmpsexp);
+ tmpsexp = NULL;
+ if (ski->s2k.mode == 1003)
+ {
+ const void *tmpbuf;
+ unsigned int tmpbuflen;
+ int npkey;
+
+ /* Fixme: Check that the public key parameters in pkey match
+ * those in the s-expression of the secret key. */
+ npkey = pubkey_get_npkey (pk->pubkey_algo);
+ if (npkey+1 > PUBKEY_MAX_NSKEY)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else if (!pk->pkey[npkey]
+ || !gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE))
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ {
+ tmpbuf = gcry_mpi_get_opaque (pk->pkey[npkey], &tmpbuflen);
+ tmpbuflen = (tmpbuflen +7)/8; /* Fixup bits to bytes */
+ err = gcry_sexp_new (&tmpsexp, tmpbuf, tmpbuflen, 0);
+ }
+ }
+ else
+ err = build_classic_transfer_sexp (pk, &tmpsexp);
+
xfree (transferkey);
transferkey = NULL;
if (!err)
err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen);
gcry_sexp_release (tmpsexp);
tmpsexp = NULL;
if (err)
{
log_error ("error building transfer key: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
wrappedkeylen = transferkeylen + 8;
xfree (wrappedkey);
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
err = gpg_error_from_syserror ();
else
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
transferkey, transferkeylen);
if (err)
goto leave;
xfree (transferkey);
transferkey = NULL;
/* Send the wrapped key to the agent. */
{
char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1);
- err = agent_import_key (ctrl, desc, &cache_nonce,
+ err = agent_import_key (ctrl, desc, ski->s2k.mode == 1003,
+ &cache_nonce,
wrappedkey, wrappedkeylen, batch, force,
pk->keyid, pk->main_keyid, pk->pubkey_algo,
pk->timestamp);
xfree (desc);
}
if (!err)
{
if (opt.verbose)
log_info (_("key %s: secret key imported\n"),
keystr_from_pk_with_sub (main_pk, pk));
if (stats)
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
if (opt.verbose)
log_info (_("key %s: secret key already exists\n"),
keystr_from_pk_with_sub (main_pk, pk));
err = 0;
if (stats)
stats->secret_dups++;
}
else
{
log_error (_("key %s: error sending to agent: %s\n"),
keystr_from_pk_with_sub (main_pk, pk),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break; /* Don't try the other subkeys. */
}
}
if (!err && stub_key_skipped)
/* We need to notify user how to migrate stub keys. */
err = gpg_error (GPG_ERR_NOT_PROCESSED);
leave:
xfree (cache_nonce);
xfree (wrappedkey);
xfree (transferkey);
gcry_cipher_close (cipherhd);
xfree (kek);
return err;
}
/* Walk a secret keyblock and produce a public keyblock out of it.
* Returns a new node or NULL on error. Modifies the tag field of the
* nodes. */
static kbnode_t
sec_to_pub_keyblock (kbnode_t sec_keyblock)
{
kbnode_t pub_keyblock = NULL;
kbnode_t ctx = NULL;
kbnode_t secnode, pubnode;
kbnode_t lastnode = NULL;
unsigned int tag = 0;
/* Set a tag to all nodes. */
for (secnode = sec_keyblock; secnode; secnode = secnode->next)
secnode->tag = ++tag;
/* Copy. */
while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (secnode->pkt->pkttype == PKT_SECRET_KEY
|| secnode->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* Make a public key. */
PACKET *pkt;
PKT_public_key *pk;
pkt = xtrycalloc (1, sizeof *pkt);
pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL;
if (!pk)
{
xfree (pkt);
release_kbnode (pub_keyblock);
return NULL;
}
if (secnode->pkt->pkttype == PKT_SECRET_KEY)
pkt->pkttype = PKT_PUBLIC_KEY;
else
pkt->pkttype = PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
pubnode = new_kbnode (pkt);
}
else
{
pubnode = clone_kbnode (secnode);
}
pubnode->tag = secnode->tag;
if (!pub_keyblock)
pub_keyblock = lastnode = pubnode;
else
{
lastnode->next = pubnode;
lastnode = pubnode;
}
}
return pub_keyblock;
}
/* Delete all notes in the keyblock at R_KEYBLOCK which are not in
* PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */
static gpg_error_t
resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock,
kbnode_t *r_removedsecs)
{
kbnode_t sec_keyblock = *r_keyblock;
kbnode_t node, prevnode;
unsigned int *taglist;
unsigned int ntaglist, n;
kbnode_t attic = NULL;
kbnode_t *attic_head = &attic;
/* Collect all tags in an array for faster searching. */
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
ntaglist++;
taglist = xtrycalloc (ntaglist, sizeof *taglist);
if (!taglist)
return gpg_error_from_syserror ();
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
taglist[ntaglist++] = node->tag;
/* Walks over the secret keyblock and delete all nodes which are not
* in the tag list. Those nodes have been deleted in the
* pub_keyblock. Sequential search is a bit lazy and could be
* optimized by sorting and bsearch; however secret keyrings are
* short and there are easier ways to DoS the import. */
again:
for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next)
{
for (n=0; n < ntaglist; n++)
if (taglist[n] == node->tag)
break;
if (n == ntaglist) /* Not in public keyblock. */
{
if (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (!prevnode)
sec_keyblock = node->next;
else
prevnode->next = node->next;
node->next = NULL;
*attic_head = node;
attic_head = &node->next;
goto again; /* That's lame; I know. */
}
else
delete_kbnode (node);
}
}
xfree (taglist);
/* Commit the as deleted marked nodes and return the possibly
* modified keyblock and a list of removed secret key nodes. */
commit_kbnode (&sec_keyblock);
*r_keyblock = sec_keyblock;
*r_removedsecs = attic;
return 0;
}
/* Helper for import_secret_one. */
static gpg_error_t
do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk,
struct import_stats_s *stats, int batch, int only_marked)
{
gpg_error_t err;
struct import_stats_s subkey_stats = {0};
int force = 0;
int already_exist = agent_probe_secret_key (ctrl, pk);
if (already_exist == 2 || already_exist == 4)
{
if (!opt.quiet)
log_info (_("key %s: card reference is overridden by key material\n"),
keystr_from_pk (pk));
force = 1;
}
err = transfer_secret_keys (ctrl, &subkey_stats, keyblock,
batch, force, only_marked);
if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED)
{
/* TRANSLATORS: For a smartcard, each private key on host has a
* reference (stub) to a smartcard and actual private key data
* is stored on the card. A single smartcard can have up to
* three private key data. Importing private key stub is always
* skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED.
* Instead, user should be suggested to run 'gpg --card-status',
* then, references to a card will be automatically created
* again. */
log_info (_("To migrate '%s', with each smartcard, "
"run: %s\n"), "secring.gpg", "gpg --card-status");
err = 0;
}
if (!err)
{
int status = 16;
if (!opt.quiet)
log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk));
if (subkey_stats.secret_imported)
{
status |= 1;
stats->secret_imported += 1;
}
if (subkey_stats.secret_dups)
stats->secret_dups += 1;
if (is_status_enabled ())
print_import_ok (pk, status);
}
return err;
}
/* If the secret keys (main or subkey) in SECKEYS have a corresponding
* public key in the public key described by (FPR,FPRLEN) import these
* parts.
*/
static gpg_error_t
import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch)
{
gpg_error_t err;
kbnode_t pub_keyblock = NULL;
kbnode_t node;
struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL;
size_t n, nfprlist;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
PKT_public_key *pk;
/* Get the entire public key block from our keystore and put all its
* fingerprints into an array. */
err = get_pubkey_byfpr (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen);
if (err)
goto leave;
log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
for (nfprlist = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
nfprlist++;
log_assert (nfprlist);
fprlist = xtrycalloc (nfprlist, sizeof *fprlist);
if (!fprlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (n = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
fingerprint_from_pk (node->pkt->pkt.public_key,
fprlist[n].fpr, &fprlist[n].fprlen);
n++;
}
log_assert (n == nfprlist);
/* for (n=0; n < nfprlist; n++) */
/* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */
/* Mark all secret keys which have a matching public key part in
* PUB_KEYBLOCK. */
for (node = seckeys; node; node = node->next)
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue; /* Should not happen. */
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
node->flag &= ~NODE_TRANSFER_SECKEY;
for (n=0; n < nfprlist; n++)
if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen))
{
node->flag |= NODE_TRANSFER_SECKEY;
/* log_debug ("found matching seckey\n"); */
break;
}
}
/* Transfer all marked keys. */
err = do_transfer (ctrl, seckeys, pk, stats, batch, 1);
leave:
xfree (fprlist);
release_kbnode (pub_keyblock);
return err;
}
/* Import function for a single secret keyblock. Handling is simpler
* than for public keys. We allow secret key importing only when
* allow is true, this is so that a secret key can not be imported
* accidentally and thereby tampering with the trust calculation.
*
* Ownership of KEYBLOCK is transferred to this function!
*
* If R_SECATTIC is not null the last special sec_keyblock is stored
* there.
*/
static gpg_error_t
import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic)
{
PKT_public_key *pk;
struct seckey_info *ski;
kbnode_t node, uidnode;
u32 keyid[2];
gpg_error_t err = 0;
int nr_prev;
kbnode_t pub_keyblock;
kbnode_t attic = NULL;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Get the key and print some info about it */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fprlen);
keyid_from_pk (pk, keyid);
uidnode = find_next_kbnode (keyblock, PKT_USER_ID);
if (screener && screener (keyblock, screener_arg))
{
log_error (_("secret key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
release_kbnode (keyblock);
return 0;
}
if (opt.verbose && !for_migration)
{
log_info ("sec %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk), datestr_from_pk (pk));
if (uidnode)
print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len);
log_printf ("\n");
}
stats->secret_read++;
if ((options & IMPORT_ONLY_PUBKEYS))
{
if (!for_migration)
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
if (!uidnode)
{
if (!for_migration)
log_error( _("key %s: no user ID\n"), keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
ski = pk->seckey_info;
if (!ski)
{
/* Actually an internal error. */
log_error ("key %s: secret key info missing\n", keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
/* A quick check to not import keys with an invalid protection
cipher algorithm (only checks the primary key, though). */
if (ski->algo > 110)
{
if (!for_migration)
log_error (_("key %s: secret key with invalid cipher %d"
" - skipped\n"), keystr_from_pk (pk), ski->algo);
release_kbnode (keyblock);
return 0;
}
#ifdef ENABLE_SELINUX_HACKS
if (1)
{
/* We don't allow importing secret keys because that may be used
to put a secret key into the keyring and the user might later
be tricked into signing stuff with that key. */
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
#endif
clear_kbnode_flags (keyblock);
nr_prev = stats->skipped_new_keys;
/* Make a public key out of the key. */
pub_keyblock = sec_to_pub_keyblock (keyblock);
if (!pub_keyblock)
{
err = gpg_error_from_syserror ();
log_error ("key %s: failed to create public key from secret key\n",
keystr_from_pk (pk));
}
else
{
int valid;
/* Note that this outputs an IMPORT_OK status message for the
public key block, and below we will output another one for
the secret keys. FIXME? */
import_one (ctrl, pub_keyblock, stats,
NULL, NULL, options, 1, for_migration,
screener, screener_arg, 0, NULL, &valid);
/* The secret keyblock may not have nodes which are deleted in
* the public keyblock. Otherwise we would import just the
* secret key without having the public key. That would be
* surprising and clutters our private-keys-v1.d. */
err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic);
if (err)
goto leave;
if (!valid)
{
/* If the block was not valid the primary key is left in the
* original keyblock because we require that for the first
* node. Move it to ATTIC. */
if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
node = keyblock;
keyblock = node->next;
node->next = NULL;
if (attic)
{
node->next = attic;
attic = node;
}
else
attic = node;
}
/* Try to import the secret key iff we have a public key. */
if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
else
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* log_debug ("attic is:\n"); */
/* dump_kbnode (attic); */
/* Proceed with the valid parts of PUBKEYBLOCK. */
/* At least we cancel the secret key import when the public key
import was skipped due to MERGE_ONLY option and a new
key. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN))
&& stats->skipped_new_keys <= nr_prev)
{
/* Read the keyblock again to get the effects of a merge for
* the public key. */
err = get_pubkey_byfpr (ctrl, NULL, &node, fpr, fprlen);
if (err || !node)
log_error ("key %s: failed to re-lookup public key: %s\n",
keystr_from_pk (pk), gpg_strerror (err));
else
{
err = do_transfer (ctrl, keyblock, pk, stats, batch, 0);
if (!err)
check_prefs (ctrl, node);
release_kbnode (node);
if (!err && attic)
{
/* Try to import invalid subkeys. This can be the
* case if the primary secret key was imported due
* to --allow-non-selfsigned-uid. */
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
}
}
}
}
leave:
release_kbnode (keyblock);
release_kbnode (pub_keyblock);
if (r_secattic)
*r_secattic = attic;
else
release_kbnode (attic);
return err;
}
/* Return a string for the revocation reason CODE. R_FREEM must be an
* possibly unintialized ptr which should be freed by the caller after
* the return value has been consumed. */
const char *
revocation_reason_code_to_str (int code, char **freeme)
{
/* Take care: get_revocation_reason has knowledge of the internal
* working of this fucntion. */
const char *result;
*freeme = NULL;
switch (code)
{
case 0x00: result = _("No reason specified"); break;
case 0x01: result = _("Key is superseded"); break;
case 0x02: result = _("Key has been compromised"); break;
case 0x03: result = _("Key is no longer used"); break;
case 0x20: result = _("User ID is no longer valid"); break;
default:
*freeme = xasprintf ("code=%02x", code);
result = *freeme;
break;
}
return result;
}
/* Return the recocation reason from signature SIG. If no revocation
* reason is available 0 is returned, in other cases the reason
* (0..255). If R_REASON is not NULL a malloced textual
* representation of the code is stored there. If R_COMMENT is not
* NULL the comment from the reason is stored there and its length at
* R_COMMENTLEN. Note that the value at R_COMMENT is not filtered but
* user supplied data in UTF8; thus it needs to be escaped for display
* purposes. Both return values are either NULL or a malloced
* string/buffer. */
int
get_revocation_reason (PKT_signature *sig, char **r_reason,
char **r_comment, size_t *r_commentlen)
{
int reason_seq = 0;
size_t reason_n;
const byte *reason_p;
int reason_code = 0;
const char *reason_string;
char *freeme;
if (r_reason)
*r_reason = NULL;
if (r_comment)
*r_comment = NULL;
/* Skip over empty reason packets. */
while ((reason_p = enum_sig_subpkt (sig, 1, SIGSUBPKT_REVOC_REASON,
&reason_n, &reason_seq, NULL))
&& !reason_n)
;
if (reason_p && reason_n)
{
reason_code = *reason_p;
reason_n--; reason_p++;
reason_string = revocation_reason_code_to_str (reason_code, &freeme);
if (r_reason && freeme)
*r_reason = freeme;
else if (r_reason && reason_string)
*r_reason = xstrdup (reason_string);
else
xfree (freeme);
if (r_comment && reason_n)
{
*r_comment = xmalloc (reason_n);
memcpy (*r_comment, reason_p, reason_n);
*r_commentlen = reason_n;
}
}
return reason_code;
}
/* List the recocation signature as a "rvs" record. SIGRC shows the
* character from the signature verification or 0 if no public key was
* found. */
static void
list_standalone_revocation (ctrl_t ctrl, PKT_signature *sig, int sigrc)
{
char *siguid = NULL;
size_t siguidlen = 0;
char *issuer_fpr = NULL;
int reason_code = 0;
char *reason_text = NULL;
char *reason_comment = NULL;
size_t reason_commentlen;
if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode)
{
int nouid;
siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid);
if (nouid)
sigrc = '?';
}
reason_code = get_revocation_reason (sig, &reason_text,
&reason_comment, &reason_commentlen);
if (opt.with_colons)
{
es_fputs ("rvs:", es_stdout);
if (sigrc)
es_putc (sigrc, es_stdout);
es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:::",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (siguid)
es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL);
es_fprintf (es_stdout, ":%02x%c", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (reason_text)
es_fprintf (es_stdout, ",%02x", reason_code);
es_fputs ("::", es_stdout);
if ((issuer_fpr = issuer_fpr_string (sig)))
es_fputs (issuer_fpr, es_stdout);
es_fprintf (es_stdout, ":::%d:", sig->digest_algo);
if (reason_comment)
{
es_fputs ("::::", es_stdout);
es_write_sanitized (es_stdout, reason_comment, reason_commentlen,
":", NULL);
es_putc (':', es_stdout);
}
es_putc ('\n', es_stdout);
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
else /* Human readable. */
{
es_fputs ("rvs", es_stdout);
es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s",
sigrc, (sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ', keystr (sig->keyid),
datestr_from_sig (sig));
if (siguid)
{
es_fprintf (es_stdout, " ");
print_utf8_buffer (es_stdout, siguid, siguidlen);
}
es_putc ('\n', es_stdout);
if (sig->flags.policy_url
&& (opt.list_options & LIST_SHOW_POLICY_URLS))
show_policy_url (sig, 3, 0);
if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS))
show_notation (sig, 3, 0,
((opt.list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0)
+
((opt.list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0)
+
((opt.list_options & LIST_SHOW_HIDDEN_NOTATIONS) ? 4:0));
if (sig->flags.pref_ks
&& (opt.list_options & LIST_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 3, 0);
if (reason_text)
{
es_fprintf (es_stdout, " %s%s\n",
_("reason for revocation: "), reason_text);
print_revocation_reason_comment (reason_comment, reason_commentlen);
}
}
es_fflush (es_stdout);
xfree (reason_text);
xfree (reason_comment);
xfree (siguid);
xfree (issuer_fpr);
}
/* Import a revocation certificate; only the first packet in the
* NODE-list is considered. */
static int
import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats)
{
PKT_public_key *pk = NULL;
kbnode_t onode;
kbnode_t keyblock = NULL;
KEYDB_HANDLE hd = NULL;
u32 keyid[2];
int rc = 0;
int sigrc = 0;
int silent;
/* No error output for --show-keys. */
silent = (options & (IMPORT_SHOW | IMPORT_DRY_RUN));
log_assert (node->pkt->pkttype == PKT_SIGNATURE );
log_assert (IS_KEY_REV (node->pkt->pkt.signature));
/* FIXME: We can do better here by using the issuer fingerprint if
* available. We should also make use of get_keyblock_byfprint_fast. */
keyid[0] = node->pkt->pkt.signature->keyid[0];
keyid[1] = node->pkt->pkt.signature->keyid[1];
pk = xmalloc_clear( sizeof *pk );
rc = get_pubkey (ctrl, pk, keyid );
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY )
{
if (!silent)
log_error (_("key %s: no public key -"
" can't apply revocation certificate\n"), keystr(keyid));
rc = 0;
goto leave;
}
else if (rc )
{
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* Read the original keyblock. */
hd = keydb_new (ctrl);
if (!hd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = keydb_lock (hd);
if (rc)
{
keydb_release (hd);
goto leave;
}
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
fingerprint_from_pk (pk, afp, &an);
rc = keydb_search_fpr (hd, afp, an);
}
if (rc)
{
log_error (_("key %s: can't locate original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (hd, &keyblock );
if (rc)
{
log_error (_("key %s: can't read original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* it is okay, that node is not in keyblock because
* check_key_signature works fine for sig_class 0x20 (KEY_REV) in
* this special case. SIGRC is only used for IMPORT_SHOW. */
rc = check_key_signature (ctrl, keyblock, node, NULL);
switch (gpg_err_code (rc))
{
case 0: sigrc = '!'; break;
case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break;
case GPG_ERR_NO_PUBKEY: sigrc = '?'; break;
case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break;
default: sigrc = '%'; break;
}
if (rc )
{
if (!silent)
log_error (_("key %s: invalid revocation certificate"
": %s - rejected\n"), keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* check whether we already have this */
for(onode=keyblock->next; onode; onode=onode->next ) {
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& !cmp_signatures(node->pkt->pkt.signature,
onode->pkt->pkt.signature))
{
rc = 0;
goto leave; /* yes, we already know about it */
}
}
/* insert it */
insert_kbnode( keyblock, clone_kbnode(node), 0 );
/* and write the keyblock back unless in dry run mode. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
rc = keydb_update_keyblock (ctrl, hd, keyblock );
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc) );
keydb_release (hd);
hd = NULL;
/* we are ready */
if (!opt.quiet )
{
char *p=get_user_id_native (ctrl, keyid);
log_info( _("key %s: \"%s\" revocation certificate imported\n"),
keystr(keyid),p);
xfree(p);
}
/* If the key we just revoked was ultimately trusted, remove its
* ultimate trust. This doesn't stop the user from putting the
* ultimate trust back, but is a reasonable solution for now. */
if (get_ownertrust (ctrl, pk) == TRUST_ULTIMATE)
clear_ownertrusts (ctrl, pk);
revalidation_mark (ctrl);
}
stats->n_revoc++;
leave:
if ((options & IMPORT_SHOW))
list_standalone_revocation (ctrl, node->pkt->pkt.signature, sigrc);
keydb_release (hd);
release_kbnode( keyblock );
free_public_key( pk );
return rc;
}
/* Loop over the KEYBLOCK and check all self signatures. KEYID is the
* keyid of the primary key for reporting purposes. On return the
* following bits in the node flags are set:
*
* - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature
* - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature
* - NODE_DELETION_MARK :: This node shall be deleted
*
* NON_SELF is set to true if there are any sigs other than self-sigs
* in this keyblock.
*
* Returns 0 on success or -1 (but not an error code) if the keyblock
* is invalid.
*/
static int
chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self)
{
kbnode_t knode = NULL; /* The node of the current subkey. */
PKT_public_key *subpk = NULL; /* and its packet. */
kbnode_t bsnode = NULL; /* Subkey binding signature node. */
u32 bsdate = 0; /* Timestamp of that node. */
kbnode_t rsnode = NULL; /* Subkey recocation signature node. */
u32 rsdate = 0; /* Timestamp of that node. */
PKT_signature *sig;
int rc;
kbnode_t n;
for (n=keyblock; (n = find_next_kbnode (n, 0)); )
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
knode = n;
subpk = knode->pkt->pkt.public_key;
bsdate = 0;
rsdate = 0;
bsnode = NULL;
rsnode = NULL;
continue;
}
if ( n->pkt->pkttype != PKT_SIGNATURE )
continue;
sig = n->pkt->pkt.signature;
if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] )
{
*non_self = 1;
continue;
}
/* This just caches the sigs for later use. That way we
import a fully-cached key which speeds things up. */
if (!opt.no_sig_cache)
check_key_signature (ctrl, keyblock, n, NULL);
if ( IS_UID_SIG(sig) || IS_UID_REV(sig) )
{
kbnode_t unode = find_prev_kbnode( keyblock, n, PKT_USER_ID );
if ( !unode )
{
log_error( _("key %s: no user ID for signature\n"),
keystr(keyid));
return -1; /* The complete keyblock is invalid. */
}
/* If it hasn't been marked valid yet, keep trying. */
if (!(unode->flag & NODE_GOOD_SELFSIG))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if ( opt.verbose )
{
char *p = utf8_to_native
(unode->pkt->pkt.user_id->name,
strlen (unode->pkt->pkt.user_id->name),0);
log_info (gpg_err_code(rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key "
"algorithm on user ID \"%s\"\n"):
_("key %s: invalid self-signature "
"on user ID \"%s\"\n"),
keystr (keyid),p);
xfree (p);
}
}
else
unode->flag |= NODE_GOOD_SELFSIG;
}
}
else if (IS_KEY_SIG (sig))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key algorithm\n"):
_("key %s: invalid direct key signature\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
}
else if ( IS_SUBKEY_SIG (sig) )
{
/* Note that this works based solely on the timestamps like
the rest of gpg. If the standard gets revocation
targets, this may need to be revised. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key binding\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key"
" algorithm\n"):
_("key %s: invalid subkey binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= bsdate)
{
knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */
if (bsnode)
{
/* Delete the last binding sig since this
one is newer */
bsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (_("key %s: removed multiple subkey"
" binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
}
bsnode = n;
bsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
else if ( IS_SUBKEY_REV (sig) )
{
/* We don't actually mark the subkey as revoked right now,
so just check that the revocation sig is the most recent
valid one. Note that we don't care if the binding sig is
newer than the revocation sig. See the comment in
getkey.c:merge_selfsigs_subkey for more. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if(opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public"
" key algorithm\n"):
_("key %s: invalid subkey revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= rsdate)
{
if (rsnode)
{
/* Delete the last revocation sig since
this one is newer. */
rsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
log_info (_("key %s: removed multiple subkey"
" revocation\n"),keystr(keyid));
}
rsnode = n;
rsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
}
return 0;
}
/* Delete all parts which are invalid and those signatures whose
* public key algorithm is not available in this implementation; but
* consider RSA as valid, because parse/build_packets knows about it.
* If R_OTHERREVSIGS is not NULL, it is used to return a list of
* revocation certificates which have been deleted from KEYBLOCK but
* should be handled later.
*
* Returns: True if at least one valid user-id is left over.
*/
static int
delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
unsigned int options, kbnode_t *r_otherrevsigs)
{
kbnode_t node;
int nvalid=0, uid_seen=0, subkey_seen=0;
PKT_public_key *pk;
for (node=keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid_seen = 1;
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
char *p=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
log_info( _("key %s: skipped user ID \"%s\"\n"),
keystr(keyid),p);
xfree(p);
}
delete_kbnode( node ); /* the user-id */
/* and all following packets up to the next user-id */
while (node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){
delete_kbnode( node->next );
node = node->next;
}
}
else
nvalid++;
}
else if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, NULL);
log_info (_("key %s: skipped subkey\n"),
keystr_with_sub (keyid, pk->keyid));
}
delete_kbnode( node ); /* the subkey */
/* and all following signature packets */
while (node->next
&& node->next->pkt->pkttype == PKT_SIGNATURE ) {
delete_kbnode( node->next );
node = node->next;
}
}
else
subkey_seen = 1;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& openpgp_pk_test_algo (node->pkt->pkt.signature->pubkey_algo)
&& node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA )
{
delete_kbnode( node ); /* build_packet() can't handle this */
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !node->pkt->pkt.signature->flags.exportable
&& !(options&IMPORT_LOCAL_SIGS)
&& !have_secret_key_with_kid (ctrl,
node->pkt->pkt.signature->keyid))
{
/* here we violate the rfc a bit by still allowing
* to import non-exportable signature when we have the
* the secret key used to create this signature - it
* seems that this makes sense */
if(opt.verbose)
log_info( _("key %s: non exportable signature"
" (class 0x%02X) - skipped\n"),
keystr(keyid), node->pkt->pkt.signature->sig_class );
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
if (uid_seen )
{
if(opt.verbose)
log_info( _("key %s: revocation certificate"
" at wrong place - skipped\n"),keystr(keyid));
if (r_otherrevsigs)
{
PACKET *pkt;
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = copy_signature
(NULL, node->pkt->pkt.signature);
*r_otherrevsigs = new_kbnode2 (*r_otherrevsigs, pkt);
}
delete_kbnode( node );
}
else
{
/* If the revocation cert is from a different key than
the one we're working on don't check it - it's
probably from a revocation key and won't be
verifiable with this key anyway. */
if(node->pkt->pkt.signature->keyid[0]==keyid[0]
&& node->pkt->pkt.signature->keyid[1]==keyid[1])
{
int rc = check_key_signature (ctrl, keyblock, node, NULL);
if (rc )
{
if(opt.verbose)
log_info( _("key %s: invalid revocation"
" certificate: %s - skipped\n"),
keystr(keyid), gpg_strerror (rc));
delete_kbnode( node );
}
}
else if (r_otherrevsigs)
{
PACKET *pkt;
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = copy_signature
(NULL, node->pkt->pkt.signature);
*r_otherrevsigs = new_kbnode2 (*r_otherrevsigs, pkt);
}
}
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (IS_SUBKEY_SIG (node->pkt->pkt.signature)
|| IS_SUBKEY_REV (node->pkt->pkt.signature))
&& !subkey_seen )
{
if(opt.verbose)
log_info( _("key %s: subkey signature"
" in wrong place - skipped\n"), keystr(keyid));
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !IS_CERT(node->pkt->pkt.signature))
{
if(opt.verbose)
log_info(_("key %s: unexpected signature class (0x%02X) -"
" skipped\n"),keystr(keyid),
node->pkt->pkt.signature->sig_class);
delete_kbnode(node);
}
else if ((node->flag & NODE_DELETION_MARK))
delete_kbnode( node );
}
/* note: because keyblock is the public key, it is never marked
* for deletion and so keyblock cannot change */
commit_kbnode( &keyblock );
return nvalid;
}
/* This function returns true if any UID is left in the keyring. */
static int
any_uid_left (kbnode_t keyblock)
{
kbnode_t node;
for (node=keyblock->next; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
return 1;
return 0;
}
/* Delete all non-self-sigs from KEYBLOCK.
* Returns: True if the keyblock has changed. */
static void
remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid)
{
kbnode_t node;
unsigned int dropped = 0;
for (node = *keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
if (node->pkt->pkt.signature->keyid[0] == keyid[0]
&& node->pkt->pkt.signature->keyid[1] == keyid[1])
continue;
delete_kbnode (node);
dropped++;
}
if (dropped)
commit_kbnode (keyblock);
if (dropped && opt.verbose)
log_info ("key %s: number of dropped non-self-signatures: %u\n",
keystr (keyid), dropped);
}
/*
* It may happen that the imported keyblock has duplicated user IDs.
* We check this here and collapse those user IDs together with their
* sigs into one.
* Returns: True if the keyblock has changed.
*/
int
collapse_uids (kbnode_t *keyblock)
{
kbnode_t uid1;
int any=0;
for(uid1=*keyblock;uid1;uid1=uid1->next)
{
kbnode_t uid2;
if(is_deleted_kbnode(uid1))
continue;
if(uid1->pkt->pkttype!=PKT_USER_ID)
continue;
for(uid2=uid1->next;uid2;uid2=uid2->next)
{
if(is_deleted_kbnode(uid2))
continue;
if(uid2->pkt->pkttype!=PKT_USER_ID)
continue;
if(cmp_user_ids(uid1->pkt->pkt.user_id,
uid2->pkt->pkt.user_id)==0)
{
/* We have a duplicated uid */
kbnode_t sig1,last;
any=1;
/* Now take uid2's signatures, and attach them to
uid1 */
for(last=uid2;last->next;last=last->next)
{
if(is_deleted_kbnode(last))
continue;
if(last->next->pkt->pkttype==PKT_USER_ID
|| last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| last->next->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
}
/* Snip out uid2 */
(find_prev_kbnode(*keyblock,uid2,0))->next=last->next;
/* Now put uid2 in place as part of uid1 */
last->next=uid1->next;
uid1->next=uid2;
delete_kbnode(uid2);
/* Now dedupe uid1 */
for(sig1=uid1->next;sig1;sig1=sig1->next)
{
kbnode_t sig2;
if(is_deleted_kbnode(sig1))
continue;
if(sig1->pkt->pkttype==PKT_USER_ID
|| sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig1->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig1->pkt->pkttype!=PKT_SIGNATURE)
continue;
for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next)
{
if(is_deleted_kbnode(sig2))
continue;
if(sig2->pkt->pkttype==PKT_USER_ID
|| sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig2->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig2->pkt->pkttype!=PKT_SIGNATURE)
continue;
if(cmp_signatures(sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature)==0)
{
/* We have a match, so delete the second
signature */
delete_kbnode(sig2);
sig2=last;
}
}
}
}
}
}
commit_kbnode(keyblock);
if(any && !opt.quiet)
{
const char *key="???";
if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
log_info (_("key %s: duplicated user ID detected - merged\n"), key);
}
return any;
}
/*
* It may happen that the imported keyblock has duplicated subkeys.
* We check this here and collapse those subkeys along with their
* binding self-signatures.
* Returns: True if the keyblock has changed.
*/
int
collapse_subkeys (kbnode_t *keyblock)
{
kbnode_t kb1, kb2, sig1, sig2, last;
int any = 0;
for (kb1 = *keyblock; kb1; kb1 = kb1->next)
{
if (is_deleted_kbnode (kb1))
continue;
if (kb1->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& kb1->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
/* We assume just a few duplicates and use a straightforward
* algorithm. */
for (kb2 = kb1->next; kb2; kb2 = kb2->next)
{
if (is_deleted_kbnode (kb2))
continue;
if (kb2->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& kb2->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
if (cmp_public_keys (kb1->pkt->pkt.public_key,
kb2->pkt->pkt.public_key))
continue;
/* We have a duplicated subkey. */
any = 1;
/* Take subkey-2's signatures, and attach them to subkey-1. */
for (last = kb2; last->next; last = last->next)
{
if (is_deleted_kbnode (last))
continue;
if (last->next->pkt->pkttype != PKT_SIGNATURE)
break;
}
/* Snip out subkye-2 */
find_prev_kbnode (*keyblock, kb2, 0)->next = last->next;
/* Put subkey-2 in place as part of subkey-1 */
last->next = kb1->next;
kb1->next = kb2;
delete_kbnode (kb2);
/* Now dedupe kb1 */
for (sig1 = kb1->next; sig1; sig1 = sig1->next)
{
if (is_deleted_kbnode (sig1))
continue;
if (sig1->pkt->pkttype != PKT_SIGNATURE)
break;
for (sig2 = sig1->next, last = sig1;
sig2;
last = sig2, sig2 = sig2->next)
{
if (is_deleted_kbnode (sig2))
continue;
if (sig2->pkt->pkttype != PKT_SIGNATURE)
break;
if (!cmp_signatures (sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature))
{
/* We have a match, so delete the second
signature */
delete_kbnode (sig2);
sig2 = last;
}
}
}
}
}
commit_kbnode (keyblock);
if (any && !opt.quiet)
{
const char *key="???";
if ((kb1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (kb1->pkt->pkt.public_key);
else if ((kb1 = find_kbnode (*keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (kb1->pkt->pkt.public_key);
log_info (_("key %s: duplicated subkeys detected - merged\n"), key);
}
return any;
}
/* Check for a 0x20 revocation from a revocation key that is not
present. This may be called without the benefit of merge_xxxx so
you can't rely on pk->revkey and friends. */
static void
revocation_present (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t onode, inode;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
for(onode=keyblock->next;onode;onode=onode->next)
{
/* If we reach user IDs, we're done. */
if(onode->pkt->pkttype==PKT_USER_ID)
break;
if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& onode->pkt->pkt.signature->revkey)
{
int idx;
PKT_signature *sig=onode->pkt->pkt.signature;
for(idx=0;idx<sig->numrevkeys;idx++)
{
u32 keyid[2];
keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr,
sig->revkey[idx].fprlen, keyid);
for(inode=keyblock->next;inode;inode=inode->next)
{
/* If we reach user IDs, we're done. */
if(inode->pkt->pkttype==PKT_USER_ID)
break;
if (inode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (inode->pkt->pkt.signature)
&& inode->pkt->pkt.signature->keyid[0]==keyid[0]
&& inode->pkt->pkt.signature->keyid[1]==keyid[1])
{
/* Okay, we have a revocation key, and a
* revocation issued by it. Do we have the key
* itself? */
gpg_error_t err;
err = get_pubkey_byfpr_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
{
char *tempkeystr = xstrdup (keystr_from_pk (pk));
/* No, so try and get it */
if ((opt.keyserver_options.options
& KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (ctrl))
{
log_info(_("WARNING: key %s may be revoked:"
" fetching revocation key %s\n"),
tempkeystr,keystr(keyid));
keyserver_import_fpr (ctrl,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen,
opt.keyserver, 0);
/* Do we have it now? */
err = get_pubkey_byfpr_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
}
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
log_info(_("WARNING: key %s may be revoked:"
" revocation key %s not present.\n"),
tempkeystr,keystr(keyid));
xfree(tempkeystr);
}
}
}
}
}
}
}
/*
* compare and merge the blocks
*
* o compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* o Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* Note: We indicate newly inserted packets with NODE_FLAG_A.
*/
static int
merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig, kbnode_t keyblock,
u32 *keyid, u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk )
{
kbnode_t onode, node;
int rc, found;
/* 1st: handle revocation certificates */
for (node=keyblock->next; node; node=node->next )
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found)
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
{
char *p = get_user_id_native (ctrl, keyid);
log_info(_("key %s: \"%s\" revocation"
" certificate added\n"), keystr(keyid),p);
xfree(p);
}
}
}
}
/* 2nd: merge in any direct key (0x1F) sigs */
for(node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID)
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found )
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
log_info( _("key %s: direct key signature added\n"),
keystr(keyid));
}
}
}
/* 3rd: try to merge new certificates in */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A) && onode->pkt->pkttype == PKT_USER_ID)
{
/* find the user id in the imported keyblock */
for (node=keyblock->next; node; node=node->next)
if (node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (node ) /* found: merge */
{
rc = merge_sigs (onode, node, n_sigs);
if (rc )
return rc;
}
}
}
/* 4th: add new user-ids */
for (node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
/* do we have this in the original keyblock */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (!onode ) /* this is a new user id: append */
{
rc = append_new_uid (options, keyblock_orig, node,
curtime, origin, url, n_sigs);
if (rc )
return rc;
++*n_uids;
}
}
}
/* 5th: add new subkeys */
for (node=keyblock->next; node; node=node->next)
{
onode = NULL;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
/* do we have this in the original keyblock? */
for(onode=keyblock_orig->next; onode; onode=onode->next)
if (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key))
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc)
return rc;
++*n_subk;
}
}
else if (node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* do we have this in the original keyblock? */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_SECRET_SUBKEY
&& !cmp_public_keys (onode->pkt->pkt.public_key,
node->pkt->pkt.public_key) )
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc )
return rc;
++*n_subk;
}
}
}
/* 6th: merge subkey certificates */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A)
&& (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| onode->pkt->pkttype == PKT_SECRET_SUBKEY))
{
/* find the subkey in the imported keyblock */
for(node=keyblock->next; node; node=node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key ) )
break;
}
if (node) /* Found: merge. */
{
rc = merge_keysigs( onode, node, n_sigs);
if (rc )
return rc;
}
}
}
return 0;
}
/* Helper function for merge_blocks.
*
* Append the new userid starting with NODE and all signatures to
* KEYBLOCK. ORIGIN and URL conveys the usual key origin info. The
* integer at N_SIGS is updated with the number of new signatures.
*/
static gpg_error_t
append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node, u32 curtime,
int origin, const char *url, int *n_sigs)
{
gpg_error_t err;
kbnode_t n;
kbnode_t n_where = NULL;
log_assert (node->pkt->pkttype == PKT_USER_ID);
/* Find the right position for the new user id and its signatures. */
for (n = keyblock; n; n_where = n, n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY )
break;
}
if (!n)
n_where = NULL;
/* and append/insert */
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first. */
n = clone_kbnode(node);
if (n->pkt->pkttype == PKT_USER_ID
&& !(options & IMPORT_RESTORE) )
{
err = insert_key_origin_uid (n->pkt->pkt.user_id,
curtime, origin, url);
if (err)
{
release_kbnode (n);
return err;
}
}
if (n_where)
{
insert_kbnode( n_where, n, 0 );
n_where = n;
}
else
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID.
* (how should we handle comment packets here?)
*/
static int
merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_USER_ID);
log_assert (src->pkt->pkttype == PKT_USER_ID);
for (n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next)
{
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
if (IS_SUBKEY_SIG (n->pkt->pkt.signature)
|| IS_SUBKEY_REV (n->pkt->pkt.signature) )
continue; /* skip signatures which are only valid on subkeys */
found = 0;
for (n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next)
if (!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature))
{
found++;
break;
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY.
*/
static int
merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| dst->pkt->pkttype == PKT_SECRET_SUBKEY);
for (n=src->next; n ; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
found = 0;
for (n2=dst->next; n2; n2 = n2->next)
{
if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n2->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n2->pkt->pkttype == PKT_SIGNATURE
&& (n->pkt->pkt.signature->keyid[0]
== n2->pkt->pkt.signature->keyid[0])
&& (n->pkt->pkt.signature->keyid[1]
== n2->pkt->pkt.signature->keyid[1])
&& (n->pkt->pkt.signature->timestamp
<= n2->pkt->pkt.signature->timestamp)
&& (n->pkt->pkt.signature->sig_class
== n2->pkt->pkt.signature->sig_class))
{
found++;
break;
}
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks.
* Append the subkey starting with NODE and all signatures to KEYBLOCK.
* Mark all new and copied packets by setting flag bit 0.
*/
static int
append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs)
{
kbnode_t n;
log_assert (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first */
n = clone_kbnode(node);
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 1, 10:27 PM (1 d, 22 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0b/85/2f9c130497038002cf19617b2ad7

Event Timeline