diff --git a/agent/agent.h b/agent/agent.h
index 958e3be6c..a1b37943c 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -1,555 +1,560 @@
/* 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 .
*/
#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
#define map_assuan_err(a) \
map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
#include
#include
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/session-env.h"
#include "../common/shareddefs.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 contrained 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
/* A large struct name "opt" to keep global flags */
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 */
const char *homedir; /* Configuration directory name */
/* 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. */
const char *pinentry_program;
/* Filename of the program to handle smartcard tasks. */
const char *scdaemon_program;
int disable_scdaemon; /* Never use the SCdaemon. */
int no_grab; /* Don't let the pinentry grab the keyboard */
/* The name of the file pinentry shall tocuh before exiting. If
this is not set the filoe name of the standard socket is used. */
const char *pinentry_touch_file;
/* 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 require minmum 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. */
const char *check_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_passhrase_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;
/* 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 options 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 options 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;
} opt;
/* Bit values for the --debug option. */
#define DBG_COMMAND_VALUE 1 /* debug commands i/o */
#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_COMMAND (opt.debug & DBG_COMMAND_VALUE)
#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 scd_local_s;
/* 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 SCdaemon (call-scd.c). */
struct scd_local_s *scd_local;
/* Environment settings for the connection. */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
/* 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 {
int algo;
unsigned char value[MAX_DIGEST_LEN];
int valuelen;
int raw_value: 1;
} digest;
unsigned char keygrip[20];
int have_keygrip;
/* 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;
};
/* 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. */
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. */
int (*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 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_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. */
#ifdef ENABLE_NLS
#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
/*-- gpg-agent.c --*/
void agent_exit (int rc)
GPGRT_ATTR_NORETURN; /* Also implemented in other tools */
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);
#ifdef HAVE_W32_SYSTEM
void *get_agent_scd_notify_event (void);
#endif
void agent_sighup_action (void);
int map_pk_openpgp_to_gcry (int openpgp_algo);
/*-- command.c --*/
gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid);
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);
#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 (ctrl_t, gnupg_fd_t);
/*-- findkey.c --*/
int agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length, int force);
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);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
int agent_is_dsa_key (gcry_sexp_t s_key);
int agent_is_eddsa_key (gcry_sexp_t s_key);
int agent_key_available (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);
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip, int force);
/*-- 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);
int 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);
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);
void agent_flush_cache (void);
int agent_put_cache (const char *key, cache_mode_t cache_mode,
const char *data, int ttl);
char *agent_get_cache (const char *key, cache_mode_t cache_mode);
void agent_store_cache_hit (const char *key);
/*-- pksign.c --*/
int 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);
int agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode);
/*-- pkdecrypt.c --*/
int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding);
/*-- genkey.c --*/
int check_passphrase_constraints (ctrl_t ctrl, const char *pw,
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, const char *cache_nonce,
const char *keyparam, size_t keyparmlen,
int no_protection, const char *override_passphrase,
int preset, membuf_t *outbuf);
gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr);
/*-- protect.c --*/
unsigned long get_standard_s2k_count (void);
unsigned char get_standard_s2k_count_rfc4880 (void);
int agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count);
int 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_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info);
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);
/*-- 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 (void *assuan_context);
gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
const char *fpr, int flag);
void agent_reload_trustlist (void);
/*-- divert-scd.c --*/
int divert_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_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_generic_cmd (ctrl_t ctrl,
const char *cmdline, void *assuan_context);
int divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *id, const char *keydata, size_t keydatalen);
/*-- call-scd.c --*/
void initialize_module_call_scd (void);
void agent_scd_dump_state (void);
int agent_scd_check_running (void);
void agent_scd_check_aliveness (void);
int agent_reset_scd (ctrl_t ctrl);
int agent_card_learn (ctrl_t ctrl,
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);
int agent_card_pksign (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *, char*, size_t),
void *getpin_cb_arg,
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 *, char*,size_t),
void *getpin_cb_arg,
const unsigned char *indata, size_t indatalen,
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);
int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *id, const char *keydata,
size_t keydatalen,
int (*getpin_cb)(void *, const char *, char*, size_t),
void *getpin_cb_arg);
gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result);
int agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *, char*, size_t),
void *getpin_cb_arg, void *assuan_context);
/*-- learncard.c --*/
int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force);
/*-- 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);
#endif /*AGENT_H*/
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index 2a3037cd4..3d29f9744 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -1,3779 +1,3779 @@
/* command-ssh.c - gpg-agent's ssh-agent emulation layer
* 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 .
*/
/* 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
The protocol for the agent is defined in OpenSSH's PROTOCL.agent
file.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "agent.h"
#include "i18n.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
/* Options. */
#define SSH_OPT_CONSTRAIN_LIFETIME 1
#define SSH_OPT_CONSTRAIN_CONFIRM 2
/* Response types. */
#define SSH_RESPONSE_SUCCESS 6
#define SSH_RESPONSE_FAILURE 5
#define SSH_RESPONSE_IDENTITIES_ANSWER 12
#define SSH_RESPONSE_SIGN_RESPONSE 14
/* Other constants. */
#define SSH_DSA_SIGNATURE_PADDING 20
#define SSH_DSA_SIGNATURE_ELEMS 2
#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.)*/
/* 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 boundling 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. */
const char *identifier;
/* 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. */
const char *curve_name;
/* The hash algorithm to be used with this key. 0 for using the
default. */
int hash_algo;
/* Misc flags. */
unsigned int flags;
};
/* Definition of an object to access the sshcontrol file. */
struct ssh_control_file_s
{
char *fname; /* Name of the file. */
FILE *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;
};
/* 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_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);
/* Global variables. */
/* Associating request types with the corresponding request
handlers. */
static 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)
#undef REQUEST_SPEC_DEFINE
};
/* Table holding key type specifications. */
static ssh_key_type_spec_t ssh_key_types[] =
{
{
"ssh-ed25519", "Ed25519", "ecc", "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", 0, SPEC_FLAG_IS_EdDSA
},
{
"ssh-rsa", "RSA", "rsa", "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, 0, SPEC_FLAG_USE_PKCS1V2
},
{
"ssh-dss", "DSA", "dsa", "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, 0, 0
},
{
"ecdsa-sha2-nistp256", "ECDSA", "ecdsa", "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp384", "ECDSA", "ecdsa", "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp521", "ECDSA", "ecdsa", "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
}
};
/*
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;
}
/* Create and return a new C-string from DATA/DATA_N (i.e.: add
NUL-termination); return NULL on OOM. */
static char *
make_cstring (const char *data, size_t data_n)
{
char *s;
s = xtrymalloc (data_n + 1);
if (s)
{
memcpy (s, data, data_n);
s[data_n] = 0;
}
return s;
}
/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns
NULL if not found. */
static const char *
ssh_identifier_from_curve_name (const char *curve_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))
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. */
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;
/* 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 = stream_read_data (stream, buffer, length);
if (err)
goto out;
/* Finalize string object. */
buffer[length] = 0;
*string = buffer;
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. 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? length:1);
else
buffer = xtrymalloc (length?length:1);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Read data. */
err = stream_read_data (stream, buffer, length);
if (err)
goto leave;
*r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*length);
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)
{
gpg_error_t err;
unsigned char *buffer;
err = stream_read_string (stream, 0, &buffer, NULL);
if (!err)
*string = (char *)buffer;
return err;
}
/* 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;
}
/* Read the content of the file specified by FILENAME into a newly
create buffer, which is to be stored in BUFFER; store length of
buffer in BUFFER_N. */
static gpg_error_t
file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n)
{
unsigned char *buffer_new;
struct stat statbuf;
estream_t stream;
gpg_error_t err;
int ret;
*buffer = NULL;
*buffer_n = 0;
buffer_new = NULL;
err = 0;
stream = es_fopen (filename, "rb");
if (! stream)
{
err = gpg_error_from_syserror ();
goto out;
}
ret = fstat (es_fileno (stream), &statbuf);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
buffer_new = xtrymalloc (statbuf.st_size);
if (! buffer_new)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_data (stream, buffer_new, statbuf.st_size);
if (err)
goto out;
*buffer = buffer_new;
*buffer_n = statbuf.st_size;
out:
if (stream)
es_fclose (stream);
if (err)
xfree (buffer_new);
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 (opt.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 = 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 = 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)
fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
else
*r_cf = cf;
return err;
}
static void
rewind_control_file (ssh_control_file_t cf)
{
fseek (cf->fp, 0, SEEK_SET);
cf->lnr = 0;
clearerr (cf->fp);
}
static void
close_control_file (ssh_control_file_t cf)
{
if (!cf)
return;
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;
clearerr (cf->fp);
do
{
if (!fgets (line, DIM(line)-1, cf->fp) )
{
if (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=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. */
static gpg_error_t
search_control_file (ssh_control_file_t cf, const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
assert (strlen (hexgrip) == 40 );
if (r_disabled)
*r_disabled = 0;
if (r_ttl)
*r_ttl = 0;
if (r_confirm)
*r_confirm = 0;
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;
}
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, const char *fmtfpr,
int ttl, int confirm)
{
gpg_error_t err;
ssh_control_file_t cf;
int disabled;
(void)ctrl;
err = open_control_file (&cf, 1);
if (err)
return err;
err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
if (err && gpg_err_code(err) == GPG_ERR_EOF)
{
struct tm *tp;
time_t atime = time (NULL);
/* 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);
fprintf (cf->fp,
("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
"# MD5 Fingerprint: %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,
fmtfpr, hexgrip, ttl, confirm? " confirm":"");
}
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)
|| 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)
|| 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 retruned 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);
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. 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 key_spec, gcry_mpi_t **mpi_list)
{
const char *elems_public;
unsigned int elems_n;
const char *elems;
int elem_is_secret;
gcry_mpi_t *mpis;
gpg_error_t err;
unsigned int i;
mpis = NULL;
err = 0;
if (secret)
elems = key_spec.elems_key_secret;
else
elems = key_spec.elems_key_public;
elems_n = strlen (elems);
elems_public = key_spec.elems_key_public;
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]);
err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
if (err)
break;
}
if (err)
goto out;
*mpi_list = mpis;
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];
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 boths 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;
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? 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? 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];
arg_list[arg_idx++] = &key_spec.identifier;
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);
xfree (formatbuf);
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;
char *curve_name = 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 extpression. */
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 the algorithm identifier. */
value_list = gcry_sexp_find_token (sexp, key_spec.identifier, 0);
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))
{
/* Parse the "curve" parameter. We currently expect the curve
name for ECC and not the parameters of the curve. This can
easily be changed but then we need to find the curve name
from the parameters using gcry_pk_get_curve. */
const char *mapped;
const char *sshname;
gcry_sexp_release (value_pair);
value_pair = gcry_sexp_find_token (value_list, "curve", 5);
if (!value_pair)
{
err = gpg_error (GPG_ERR_INV_CURVE);
goto out;
}
curve_name = gcry_sexp_nth_string (value_pair, 1);
if (!curve_name)
{
err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */
goto out;
}
/* Fixme: The mapping should be done by using gcry_pk_get_curve
et al to iterate over all name aliases. */
if (!strcmp (curve_name, "NIST P-256"))
mapped = "nistp256";
else if (!strcmp (curve_name, "NIST P-384"))
mapped = "nistp384";
else if (!strcmp (curve_name, "NIST P-521"))
mapped = "nistp521";
else
mapped = NULL;
if (mapped)
{
xfree (curve_name);
curve_name = xtrystrdup (mapped);
if (!curve_name)
{
err = gpg_error_from_syserror ();
goto out;
}
}
sshname = ssh_identifier_from_curve_name (curve_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, curve_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;
}
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);
xfree (curve_name);
es_fclose (stream);
es_free (blob);
return err;
}
/* Extract the car from SEXP, and create a newly created C-string
which is to be stored in IDENTIFIER. */
static gpg_error_t
sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
{
char *identifier_new;
gcry_sexp_t sublist;
const char *data;
size_t data_n;
gpg_error_t err;
identifier_new = NULL;
err = 0;
sublist = gcry_sexp_nth (sexp, 1);
if (! sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
data = gcry_sexp_nth_data (sublist, 0, &data_n);
if (! data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
identifier_new = make_cstring (data, data_n);
if (! identifier_new)
{
err = gpg_err_code_from_errno (errno);
goto out;
}
*identifier = identifier_new;
out:
gcry_sexp_release (sublist);
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 "name" is equal to NAME.
Store found entry in SPEC on success, return error otherwise. */
static gpg_error_t
ssh_key_type_lookup (const char *ssh_name, const char *name,
ssh_key_type_spec_t *spec)
{
gpg_error_t err;
unsigned int i;
/* FIXME: Although this sees to work, it not be correct if the
lookup is done via name which might be "ecc" but actually it need
to check the flags to see whether it is eddsa or ecdsa. Maybe
the entire parameter controlled logic is too complicated and we
would do better by just switching on the ssh_name. */
for (i = 0; i < DIM (ssh_key_types); i++)
if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
|| (name && (! strcmp (name, ssh_key_types[i].identifier))))
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;
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
gcry_mpi_t *mpi_list = NULL;
const char *elems;
char *curve_name = NULL;
err = stream_read_cstring (stream, &key_type);
if (err)
goto out;
err = ssh_key_type_lookup (key_type, NULL, &spec);
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 theres are 64 bytes; however
* we only want the real 32 byte private key - Libgcrypt expects
* this.
*/
mpi_list = xtrycalloc (3, sizeof *mpi_list);
if (!mpi_list)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_blob (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.
*/
unsigned char *buffer;
const char *mapped;
err = stream_read_string (stream, 0, &buffer, NULL);
if (err)
goto out;
curve_name = buffer;
/* Fixme: Check that curve_name matches the keytype. */
/* Because Libgcrypt < 1.6 has no support for the "nistpNNN"
curve names, we need to translate them here to Libgcrypt's
native names. */
if (!strcmp (curve_name, "nistp256"))
mapped = "NIST P-256";
else if (!strcmp (curve_name, "nistp384"))
mapped = "NIST P-384";
else if (!strcmp (curve_name, "nistp521"))
mapped = "NIST P-521";
else
mapped = NULL;
if (mapped)
{
xfree (curve_name);
curve_name = xtrystrdup (mapped);
if (!curve_name)
{
err = gpg_error_from_syserror ();
goto out;
}
}
err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list);
if (err)
goto out;
}
else
{
err = ssh_receive_mpint_list (stream, secret, spec, &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;
}
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
if (secret)
{
err = gcry_sexp_build (&key, NULL,
"(private-key(ecc(curve \"Ed25519\")"
"(flags eddsa)(q %m)(d %m))"
"(comment%s))",
mpi_list[0], mpi_list[1],
comment? comment:"");
}
else
{
err = gcry_sexp_build (&key, NULL,
"(public-key(ecc(curve \"Ed25519\")"
"(flags eddsa)(q %m))"
"(comment%s))",
mpi_list[0],
comment? comment:"");
}
}
else
{
err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
comment? comment:"");
if (err)
goto out;
}
if (key_spec)
*key_spec = spec;
*key_new = key;
out:
mpint_list_free (mpi_list);
xfree (curve_name);
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;
char *key_type = NULL;
char *comment = NULL;
void *blob = NULL;
size_t bloblen;
gpg_error_t err;
err = sexp_extract_identifier (key, &key_type);
if (err)
goto out;
err = ssh_key_type_lookup (NULL, key_type, &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 (key_type);
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 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, gcry_sexp_t *r_pk, char **cardsn)
{
gpg_error_t err;
char *authkeyid;
char *serialno = NULL;
unsigned char *pkbuf;
size_t pkbuflen;
gcry_sexp_t s_pk;
unsigned char grip[20];
*r_pk = NULL;
if (cardsn)
*cardsn = NULL;
/* First see whether a card is available and whether the application
is supported. */
err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED )
{
/* Ask for the serial number to reset the card. */
err = agent_card_serialno (ctrl, &serialno);
if (err)
{
if (opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
return err;
}
log_info (_("detected card with S/N: %s\n"), serialno);
err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
}
if (err)
{
log_error (_("no authentication key for ssh on card: %s\n"),
gpg_strerror (err));
xfree (serialno);
return err;
}
/* Get the S/N if we don't have it yet. Use the fast getattr method. */
if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) )
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
xfree (authkeyid);
return err;
}
/* Read the public key. */
err = agent_card_readkey (ctrl, authkeyid, &pkbuf);
if (err)
{
if (opt.verbose)
log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
xfree (serialno);
xfree (authkeyid);
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);
xfree (serialno);
xfree (authkeyid);
return err;
}
err = ssh_key_grip (s_pk, grip);
if (err)
{
log_debug ("error computing keygrip from received card key: %s\n",
gcry_strerror (err));
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
if ( agent_key_available (grip) )
{
/* (Shadow)-key is not available in our key storage. */
unsigned char *shadow_info;
unsigned char *tmp;
shadow_info = make_shadow_info (serialno, authkeyid);
if (!shadow_info)
{
err = gpg_error_from_syserror ();
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
err = agent_shadow_key (pkbuf, shadow_info, &tmp);
xfree (shadow_info);
if (err)
{
log_error (_("shadowing the key failed: %s\n"), gpg_strerror (err));
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
xfree (pkbuf);
pkbuf = tmp;
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
assert (pkbuflen);
err = agent_write_private_key (grip, pkbuf, pkbuflen, 0);
if (err)
{
log_error (_("error writing key: %s\n"), gpg_strerror (err));
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
}
if (cardsn)
{
char *dispsn;
/* 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))
{
*cardsn = xtryasprintf ("cardno:%s", dispsn);
xfree (dispsn);
}
else
*cardsn = xtryasprintf ("cardno:%s", serialno);
if (!*cardsn)
{
err = gpg_error_from_syserror ();
xfree (pkbuf);
gcry_sexp_release (s_pk);
xfree (serialno);
xfree (authkeyid);
return err;
}
}
xfree (pkbuf);
xfree (serialno);
xfree (authkeyid);
*r_pk = s_pk;
return 0;
}
/*
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)
{
ssh_key_type_spec_t spec;
char *key_fname = NULL;
char *fnameptr;
u32 key_counter;
estream_t key_blobs;
gcry_sexp_t key_secret;
gcry_sexp_t key_public;
gpg_error_t err;
int ret;
ssh_control_file_t cf = NULL;
char *cardsn;
gpg_error_t ret_err;
(void)request;
/* Prepare buffer stream. */
key_secret = NULL;
key_public = NULL;
key_counter = 0;
err = 0;
key_blobs = es_fopenmem (0, "r+b");
if (! key_blobs)
{
err = gpg_error_from_syserror ();
goto out;
}
/* First check whether a key is currently available in the card
reader - this should be allowed even without being listed in
sshcontrol. */
if (!opt.disable_scdaemon
&& !card_key_available (ctrl, &key_public, &cardsn))
{
err = ssh_send_key_public (key_blobs, key_public, cardsn);
gcry_sexp_release (key_public);
key_public = NULL;
xfree (cardsn);
if (err)
goto out;
key_counter++;
}
/* Prepare buffer for key name construction. */
{
char *dname;
dname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dname)
{
err = gpg_err_code_from_syserror ();
goto out;
}
key_fname = xtrymalloc (strlen (dname) + 1 + 40 + 4 + 1);
if (!key_fname)
{
err = gpg_err_code_from_syserror ();
xfree (dname);
goto out;
}
fnameptr = stpcpy (stpcpy (key_fname, dname), "/");
xfree (dname);
}
/* Then look at all the registered and non-disabled keys. */
err = open_control_file (&cf, 0);
if (err)
goto out;
while (!read_control_file_item (cf))
{
if (!cf->item.valid)
continue; /* Should not happen. */
if (cf->item.disabled)
continue;
assert (strlen (cf->item.hexgrip) == 40);
stpcpy (stpcpy (fnameptr, cf->item.hexgrip), ".key");
/* Read file content. */
{
unsigned char *buffer;
size_t buffer_n;
err = file_to_buffer (key_fname, &buffer, &buffer_n);
if (err)
{
log_error ("%s:%d: key '%s' skipped: %s\n",
cf->fname, cf->lnr, cf->item.hexgrip,
gpg_strerror (err));
continue;
}
err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n);
xfree (buffer);
if (err)
goto out;
}
{
char *key_type = NULL;
err = sexp_extract_identifier (key_secret, &key_type);
if (err)
goto out;
err = ssh_key_type_lookup (NULL, key_type, &spec);
xfree (key_type);
if (err)
goto out;
}
err = ssh_send_key_public (key_blobs, key_secret, NULL);
if (err)
goto out;
gcry_sexp_release (key_secret);
key_secret = NULL;
key_counter++;
}
err = 0;
ret = es_fseek (key_blobs, 0, SEEK_SET);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
out:
/* Send response. */
gcry_sexp_release (key_secret);
gcry_sexp_release (key_public);
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
{
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
}
es_fclose (key_blobs);
close_control_file (cf);
xfree (key_fname);
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 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 releaase 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);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, &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:
xfree (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;
u32 flags;
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;
/* FIXME? */
err = stream_read_uint32 (request, &flags);
if (err)
goto out;
hash_algo = spec.hash_algo;
if (!hash_algo)
hash_algo = GCRY_MD_SHA1; /* Use the default. */
ctrl->digest.algo = hash_algo;
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.
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;
err = 0;
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;
}
gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n);
/* FIXME: guarantee? */
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0);
out:
xfree (buffer_new);
return err;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static int
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 -1;
}
/* 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 new 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, *pi2;
err = ssh_key_grip (key, key_grip_raw);
if (err)
goto out;
/* Check whether the key is already in our key storage. Don't do
anything then. */
if ( !agent_key_available (key_grip_raw) )
goto out; /* Yes, key is available. */
err = ssh_get_fingerprint_string (key, &key_fpr);
if (err)
goto out;
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 (2, sizeof (*pi) + 100 + 1);
+ pi = gcry_calloc_secure (2, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
err = gpg_error_from_syserror ();
goto out;
}
- pi2 = pi + (sizeof *pi + 100 + 1);
- pi->max_length = 100;
+ pi2 = pi + (sizeof *pi + MAX_PASSPHRASE_LEN + 1);
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 1;
pi->with_repeat = 1;
- pi2->max_length = 100;
+ 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 (err == -1)
{ /* 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. */
err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0);
if (err)
goto out;
/* Cache this passphrase. */
bin2hex (key_grip_raw, 20, key_grip);
err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl);
if (err)
goto out;
/* And add an entry to the sshcontrol file. */
err = add_control_entry (ctrl, spec, key_grip, key_fpr, ttl, confirm);
out:
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 (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;
}
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 emulation? */
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;
}
/* Return the request specification for the request identified by TYPE
or NULL in case the requested request specification could not be
found. */
static ssh_request_spec_t *
request_spec_lookup (int type)
{
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)
{
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 occured 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;
}
/* Start serving client on SOCK_CLIENT. */
void
start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
{
estream_t stream_sock = NULL;
gpg_error_t err;
int ret;
err = agent_copy_startup_env (ctrl);
if (err)
goto out;
/* Create stream from socket. */
stream_sock = es_fdopen (FD2INT(sock_client), "r+");
if (!stream_sock)
{
err = gpg_error_from_syserror ();
log_error (_("failed to create stream from socket: %s\n"),
gpg_strerror (err));
goto out;
}
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
ret = es_setvbuf (stream_sock, NULL, _IONBF, 0);
if (ret)
{
err = gpg_error_from_syserror ();
log_error ("failed to disable buffering "
"on socket stream: %s\n", gpg_strerror (err));
goto out;
}
/* Main processing loop. */
while ( !ssh_request_process (ctrl, stream_sock) )
{
/* Check wether we have reached EOF before trying to read
another request. */
int c;
c = es_fgetc (stream_sock);
if (c == EOF)
break;
es_ungetc (c, stream_sock);
}
/* Reset the SCD in case it has been used. */
agent_reset_scd (ctrl);
out:
if (stream_sock)
es_fclose (stream_sock);
}
#ifdef HAVE_W32_SYSTEM
/* Serve one ssh-agent request. This is used for the Putty support.
REQUEST is the 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;
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 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 SCD in case it has been used. */
agent_reset_scd (ctrl);
return valid_response? 0 : -1;
}
#endif /*HAVE_W32_SYSTEM*/
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
index 8bf587342..6d2221015 100644
--- a/agent/cvt-openpgp.c
+++ b/agent/cvt-openpgp.c
@@ -1,1498 +1,1498 @@
/* 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 .
*/
#include
#include
#include
#include
#include
#include "agent.h"
#include "i18n.h"
#include "cvt-openpgp.h"
#include "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;
}
/* 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++)
{
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;
const unsigned char *buffer;
buffer = gcry_mpi_get_opaque (skey[i], &nbits);
nbytes = (nbits+7)/8;
actual_csum += checksum (buffer, nbytes);
}
else
{
unsigned char *buffer;
err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes,
skey[i]);
if (!err)
actual_csum += checksum (buffer, nbytes);
xfree (buffer);
}
if (err)
return err;
}
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;
if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE ))
{
gcry_cipher_close (cipher_hd);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
p = gcry_mpi_get_opaque (skey[npkey], &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 (gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, p, ndata, &nbytes))
{
/* Checksum was okay, but not correctly decrypted. */
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
break;
}
gcry_mpi_release (skey[i]);
skey[i] = tmpmpi;
ndata -= nbytes;
p += nbytes;
}
skey[i] = NULL;
skeylen = i;
assert (skeylen <= skeysize);
/* Note: at this point NDATA should be 2 for a simple
checksum or 20 for the sha1 digest. */
}
xfree(data);
}
else /* Packet version <= 3. */
{
unsigned char *buffer;
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_OPAQUE))
{
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 = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, buffer, ndata, &ndata);
xfree (buffer);
if (err)
{
/* Checksum was okay, but not correctly decrypted. */
desired_csum = 0;
actual_csum = 1; /* Mark checksum bad. */
break;
}
gcry_mpi_release (skey[i]);
skey[i] = tmpmpi;
}
}
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 int
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,
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'))
goto bad_seckey;
is_v4 = (value[0] == '4');
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 || curve)
{
/* Encrypted parameters and ECC parameters need or can be
stored as opaque. */
skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8);
if (!skey[skeyidx])
goto outofmem;
if (is_enc)
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=%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 ("XXX iv", iv, ivlen);
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 ("XXX s2k_salt", s2k_salt, sizeof 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 (!from_native && !agent_key_available (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) + 100);
+ pi = xtrycalloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
- pi->max_length = 100;
+ 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);
}
else if (cache_nonce)
{
char *cache_value;
cache_value = agent_get_cache (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,
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, 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, 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 (grip, protectedkey, protectedkeylen, 1);
xfree (protectedkey);
}
else
{
/* Empty passphrase: write key without protection. */
agent_write_private_key (grip,
*r_key,
gcry_sexp_canon_len (*r_key, 0, NULL,NULL),
1);
}
}
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;
assert (npkey < nskey);
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))
{
const unsigned char *s;
unsigned int n;
s = gcry_mpi_get_opaque (array[i], &n);
if (!s)
{
s = "";
n = 0;
}
/* Strip leading zero bits. */
for (; n >= 8 && !*s; s++, n -= 8)
;
if (n >= 8 && !(*s & 0x80))
if (--n >= 7 && !(*s & 0x40))
if (--n >= 6 && !(*s & 0x20))
if (--n >= 5 && !(*s & 0x10))
if (--n >= 4 && !(*s & 0x08))
if (--n >= 3 && !(*s & 0x04))
if (--n >= 2 && !(*s & 0x02))
if (--n >= 1 && !(*s & 0x01))
--n;
nbits[j] = n;
n = (n+7)/8;
narr[j] = n;
bufarr[j] = (gcry_is_secure (s)? xtrymalloc_secure (n?n:1)
/* */ : xtrymalloc (n?n:1));
if (!bufarr[j])
{
err = gpg_error_from_syserror ();
for (i = 0; i < j; i++)
xfree (bufarr[i]);
return err;
}
memcpy (bufarr[j], s, n);
}
else
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]);
if (err)
{
for (i = 0; i < j; i++)
xfree (bufarr[i]);
return err;
}
nbits[j] = gcry_mpi_get_nbits (array[i]);
}
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;
}
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);
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;
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 = "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 = "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 = "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"))
{
algoname = "ecc";
format = "/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);
if (flags)
{
gcry_sexp_t param = gcry_sexp_find_token (flags, "param", 0);
if (param)
{
gcry_sexp_release (param);
array[6] = array[0];
array[7] = array[1];
err = gcry_sexp_extract_param (list, NULL, "pabgnh?",
array+0, array+1, array+2, array+3,
array+4, array+5, NULL);
if (array[5] == NULL)
{
array[5] = GCRYMPI_CONST_ONE;
npkey += 6;
nskey += 6;
}
format = "pabgnhqd?";
}
}
}
else if (!strcmp (name, "ecdsa"))
{
algoname = "ecdsa";
format = "pabgnqd?";
npkey = 6;
nskey = 7;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
array+4, array+5, array+6, NULL);
}
else if (!strcmp (name, "ecdh"))
{
algoname = "ecdh";
format = "pabgnqd?";
npkey = 6;
nskey= 7;
err = gcry_sexp_extract_param (list, NULL, format,
array+0, array+1, array+2, array+3,
array+4, array+5, array+6, 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)
{
if (format[0] == '/') /* It is opaque data qualifier, skip it. */
*r_elems = format+1;
else
*r_elems = format;
}
*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/findkey.c b/agent/findkey.c
index e7cd79e6c..c49c37a55 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1,1331 +1,1331 @@
/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* (we use pth_sleep) */
#include "agent.h"
#include "i18n.h"
#include "../common/ssh-utils.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
/* 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. */
};
/* Write an S-expression formatted key to our key storage. With FORCE
passed as true an existing key with the given GRIP will get
overwritten. */
int
agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length, int force)
{
char *fname;
estream_t fp;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
/* FIXME: Write to a temp file first so that write failures during
key updates won't lead to a key loss. */
if (!force && !access (fname, F_OK))
{
log_error ("secret key file '%s' already exists\n", fname);
xfree (fname);
return gpg_error (GPG_ERR_EEXIST);
}
fp = es_fopen (fname, force? "wb,mode=-rw" : "wbx,mode=-rw");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
xfree (fname);
return tmperr;
}
if (es_fwrite (buffer, length, 1, fp) != 1)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr));
es_fclose (fp);
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
if (es_fclose (fp))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr));
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
bump_key_eventcounter ();
xfree (fname);
return 0;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static int
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;
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 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.
*/
static gpg_error_t
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;
comment_length = strlen (comment);
in_len = strlen (in);
/* First pass calculates the length, second pass does the actual
copying. */
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, &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;
assert (*result + out_len == out);
xfree (ssh_fpr);
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 int
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 (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;
}
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 (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);
rc = 0;
}
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 (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);
rc = 0;
}
}
/* 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. */
npth_sleep (1);
goto retry;
}
/* Timeout - better call pinentry now the plain way. */
}
}
- pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
- pi->max_length = 100;
+ 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)
{
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;
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 (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. On failure
returns an error code and stores NULL at RESULT. */
static gpg_error_t
read_key_file (const unsigned char *grip, gcry_sexp_t *result)
{
int rc;
char *fname;
estream_t fp;
struct stat st;
unsigned char *buf;
size_t buflen, erroff;
gcry_sexp_t s_skey;
char hexgrip[40+4+1];
*result = NULL;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
fp = es_fopen (fname, "rb");
if (!fp)
{
rc = gpg_error_from_syserror ();
if (gpg_err_code (rc) != GPG_ERR_ENOENT)
log_error ("can't open '%s': %s\n", fname, strerror (errno));
xfree (fname);
return rc;
}
if (fstat (es_fileno (fp), &st))
{
rc = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
if (!buf)
{
rc = gpg_error_from_syserror ();
log_error ("error allocating %zu bytes for '%s': %s\n",
buflen, fname, strerror (errno));
xfree (fname);
es_fclose (fp);
xfree (buf);
return rc;
}
if (es_fread (buf, buflen, 1, fp) != 1)
{
rc = gpg_error_from_syserror ();
log_error ("error reading %zu bytes from '%s': %s\n",
buflen, fname, strerror (errno));
xfree (fname);
es_fclose (fp);
xfree (buf);
return rc;
}
/* Convert the file into a gcrypt S-expression object. */
rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
xfree (fname);
es_fclose (fp);
xfree (buf);
if (rc)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
return rc;
}
*result = s_skey;
return 0;
}
/* 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;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
if (gnupg_remove (fname))
err = gpg_error_from_syserror ();
xfree (fname);
return err;
}
/* Return the secret key as an S-Exp in RESULT after locating it using
the GRIP. 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)
{
int rc;
unsigned char *buf;
size_t len, buflen, erroff;
gcry_sexp_t s_skey;
*result = NULL;
if (shadow_info)
*shadow_info = NULL;
if (r_passphrase)
*r_passphrase = NULL;
rc = read_key_file (grip, &s_skey);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_ENOENT)
rc = gpg_error (GPG_ERR_NO_SECKEY);
return rc;
}
/* For use with the protection functions we also need the key as an
canonical encoded S-expression in a buffer. Create this buffer
now. */
rc = make_canon_sexp (s_skey, &buf, &len);
if (rc)
return rc;
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;
rc = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
if (rc)
log_error ("failed to convert unprotected openpgp key: %s\n",
gpg_strerror (rc));
else
{
xfree (buf);
buf = buf_new;
}
}
break;
case PRIVATE_KEY_PROTECTED:
{
char *desc_text_final;
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. */
{
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);
}
desc_text_final = NULL;
if (desc_text)
rc = modify_description (desc_text, comment? comment:"", s_skey,
&desc_text_final);
gcry_free (comment);
if (!rc)
{
rc = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
cache_mode, lookup_ttl, r_passphrase);
if (rc)
log_error ("failed to unprotect the secret key: %s\n",
gpg_strerror (rc));
}
xfree (desc_text_final);
}
break;
case PRIVATE_KEY_SHADOWED:
if (shadow_info)
{
const unsigned char *s;
size_t n;
rc = agent_get_shadow_info (buf, &s);
if (!rc)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
rc = out_of_core ();
else
{
memcpy (*shadow_info, s, n);
rc = 0;
}
}
if (rc)
log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
}
else
rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
log_error ("invalid private key format\n");
rc = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
gcry_sexp_release (s_skey);
s_skey = NULL;
if (rc)
{
xfree (buf);
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
return rc;
}
buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
wipememory (buf, buflen);
xfree (buf);
if (rc)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
return rc;
}
*result = s_skey;
return 0;
}
/* Return the string name from the S-expression S_KEY as well as a
string describing the names of the parameters. ALGONAMESIZE and
ELEMSSIZE give the allocated size of the provided buffers. The
buffers may be NULL if not required. If R_LIST is not NULL the top
level list will be stored there; the caller needs to release it in
this case. */
static gpg_error_t
key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
char *r_algoname, size_t algonamesize,
char *r_elems, size_t elemssize)
{
gcry_sexp_t list, l2;
const char *name, *algoname, *elems;
size_t n;
if (r_list)
*r_list = NULL;
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 );
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_data (list, 0, &n);
if (n==3 && !memcmp (name, "rsa", 3))
{
algoname = "rsa";
elems = "ne";
}
else if (n==3 && !memcmp (name, "dsa", 3))
{
algoname = "dsa";
elems = "pqgy";
}
else if (n==3 && !memcmp (name, "ecc", 3))
{
algoname = "ecc";
elems = "pabgnq";
}
else if (n==5 && !memcmp (name, "ecdsa", 5))
{
algoname = "ecdsa";
elems = "pabgnq";
}
else if (n==4 && !memcmp (name, "ecdh", 4))
{
algoname = "ecdh";
elems = "pabgnq";
}
else if (n==3 && !memcmp (name, "elg", 3))
{
algoname = "elg";
elems = "pgy";
}
else
{
log_error ("unknown private key algorithm\n");
gcry_sexp_release (list);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
if (r_algoname)
{
if (strlen (algoname) >= algonamesize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
strcpy (r_algoname, algoname);
}
if (r_elems)
{
if (strlen (elems) >= elemssize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
strcpy (r_elems, elems);
}
if (r_list)
*r_list = list;
else
gcry_sexp_release (list);
return 0;
}
/* Return true if KEYPARMS holds an EdDSA key. */
static int
is_eddsa (gcry_sexp_t keyparms)
{
int result = 0;
gcry_sexp_t list;
const char *s;
size_t n;
int i;
list = gcry_sexp_find_token (keyparms, "flags", 0);
for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
{
s = gcry_sexp_nth_data (list, i, &n);
if (!s)
continue; /* Not a data element. */
if (n == 5 && !memcmp (s, "eddsa", 5))
{
result = 1;
break;
}
}
gcry_sexp_release (list);
return result;
}
/* Return the public key algorithm number if S_KEY is a DSA style key.
If it is not a DSA style key, return 0. */
int
agent_is_dsa_key (gcry_sexp_t s_key)
{
int result;
gcry_sexp_t list;
char algoname[6];
if (!s_key)
return 0;
if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an DSA key. */
if (!strcmp (algoname, "dsa"))
result = GCRY_PK_DSA;
else if (!strcmp (algoname, "ecc"))
{
if (is_eddsa (list))
result = 0;
else
result = GCRY_PK_ECDSA;
}
else if (!strcmp (algoname, "ecdsa"))
result = GCRY_PK_ECDSA;
else
result = 0;
gcry_sexp_release (list);
return result;
}
/* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */
int
agent_is_eddsa_key (gcry_sexp_t s_key)
{
int result;
gcry_sexp_t list;
char algoname[6];
if (!s_key)
return 0;
if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an EdDSA key. */
if (!strcmp (algoname, "ecc") && is_eddsa (list))
result = 1;
else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */
result = 1;
else
result = 0;
gcry_sexp_release (list);
return result;
}
/* 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)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
*result = NULL;
err = read_key_file (grip, &s_skey);
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. */
gpg_error_t
agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
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;
char *format, *p;
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;
err = read_key_file (grip, &s_skey);
if (err)
return err;
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;
/* FIXME: The following thing is pretty ugly code; we should
investigate how to make it cleaner. Probably code to handle
canonical S-expressions in a memory buffer is better suited for
such a task. After all that is what we do in protect.c. Neeed
to find common patterns and write a straightformward API to use
them. */
assert (sizeof (size_t) <= sizeof (void*));
format = xtrymalloc (15+4+7*npkey+10+15+1+1);
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;
}
argidx = 0;
p = stpcpy (stpcpy (format, "(public-key("), algoname);
p = stpcpy (p, "%S%S"); /* curve name and flags. */
args[argidx++] = &curve;
args[argidx++] = &flags;
for (idx=0, s=elems; idx < npkey; idx++)
{
*p++ = '(';
*p++ = *s++;
p = stpcpy (p, " %m)");
assert (argidx < DIM (args));
args[argidx++] = &array[idx];
}
*p++ = ')';
if (uri)
{
p = stpcpy (p, "(uri %b)");
assert (argidx+1 < DIM (args));
args[argidx++] = (void *)&uri_length;
args[argidx++] = (void *)&uri;
}
if (comment)
{
p = stpcpy (p, "(comment %b)");
assert (argidx+1 < DIM (args));
args[argidx++] = (void *)&comment_length;
args[argidx++] = (void*)&comment;
}
*p++ = ')';
*p = 0;
assert (argidx < DIM (args));
args[argidx] = NULL;
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;
}
/* Check whether the the secret key identified by GRIP is available.
Returns 0 is the key is available. */
int
agent_key_available (const unsigned char *grip)
{
int result;
char *fname;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
result = !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)
{
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 (grip, &sexp);
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 (buf, &s);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
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 fucntion won't
require a confirmation via Pinentry or warns if the key is also
used by ssh.
Common error codes are:
GPG_ERR_NO_SECKEY
GPG_ERR_KEY_ON_CARD
GPG_ERR_NOT_CONFIRMED
*/
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip, int force)
{
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;
err = read_key_file (grip, &s_skey);
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;
switch (agent_private_key_type (buf))
{
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 = modify_description (desc_text, comment? 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 = gpg_error (GPG_ERR_KEY_ON_CARD);
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;
}
diff --git a/agent/genkey.c b/agent/genkey.c
index af53c60ad..13858cae3 100644
--- a/agent/genkey.c
+++ b/agent/genkey.c
@@ -1,608 +1,608 @@
/* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include "agent.h"
#include "i18n.h"
#include "exechelp.h"
#include "sysutils.h"
static int
store_key (gcry_sexp_t private, const char *passphrase, int force,
unsigned long s2k_count)
{
int rc;
unsigned char *buf;
size_t len;
unsigned char grip[20];
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);
assert (len);
buf = gcry_malloc_secure (len);
if (!buf)
return out_of_core ();
len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
assert (len);
if (passphrase)
{
unsigned char *p;
rc = agent_protect (buf, passphrase, &p, &len, s2k_count);
if (rc)
{
xfree (buf);
return rc;
}
xfree (buf);
buf = p;
}
rc = agent_write_private_key (grip, buf, len, force);
xfree (buf);
return rc;
}
/* 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. */
static int
check_passphrase_pattern (ctrl_t ctrl, const char *pw)
{
gpg_error_t err = 0;
const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN);
FILE *infp;
const char *argv[10];
pid_t pid;
int result, i;
(void)ctrl;
infp = gnupg_tmpfile ();
if (!infp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating temporary file: %s\n"), gpg_strerror (err));
return 1; /* Error - assume password should not be used. */
}
if (fwrite (pw, strlen (pw), 1, infp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing to temporary file: %s\n"),
gpg_strerror (err));
fclose (infp);
return 1; /* Error - assume password should not be used. */
}
fseek (infp, 0, SEEK_SET);
clearerr (infp);
i = 0;
argv[i++] = "--null";
argv[i++] = "--",
argv[i++] = opt.check_passphrase_pattern,
argv[i] = NULL;
assert (i < sizeof argv);
if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid))
result = 1; /* Execute error - assume password should no be used. */
else if (gnupg_wait_process (pgmname, pid, 1, NULL))
result = 1; /* Helper returned an error - probably a match. */
else
result = 0; /* Success; i.e. no match. */
gnupg_release_process (pid);
/* Overwrite our temporary file. */
fseek (infp, 0, SEEK_SET);
clearerr (infp);
for (i=((strlen (pw)+99)/100)*100; i > 0; i--)
putc ('\xff', infp);
fflush (infp);
fclose (infp);
return result;
}
static int
take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn)
{
gpg_error_t err;
if (opt.enforce_passphrase_constraints)
{
err = agent_show_message (ctrl, desc, L_("Enter new passphrase"));
if (!err)
err = gpg_error (GPG_ERR_CANCELED);
}
else
err = agent_get_confirmation (ctrl, desc,
anyway_btn, L_("Enter new passphrase"), 0);
return err;
}
static int
take_this_one_anyway (ctrl_t ctrl, const char *desc)
{
return take_this_one_anyway2 (ctrl, desc, L_("Take this one anyway"));
}
/* 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 in
*FAILED_CONSTRAINT. */
int
check_passphrase_constraints (ctrl_t ctrl, const char *pw,
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;
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?
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)
*failed_constraint = xstrdup (desc);
else
err = take_this_one_anyway2 (ctrl, desc,
L_("Yes, protection is not needed"));
}
goto leave;
}
/* Now check the constraints and collect the error messages unless
in in silent mode which returns immediately. */
if (utf8_charcount (pw) < 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 &&
check_passphrase_pattern (ctrl, pw))
{
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);
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 int
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 -1;
}
/* 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;
size_t len = 100;
unsigned char *buffer;
err = pinentry_loopback(ctrl, "NEW_PASSPHRASE", &buffer, &size, len);
if (!err)
{
if (size)
{
buffer[size] = 0;
*r_passphrase = buffer;
}
else
*r_passphrase = NULL;
}
return err;
}
- pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
- pi2 = pi + (sizeof *pi + 100);
- pi->max_length = 100;
+ pi = gcry_calloc_secure (2, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
+ pi2 = pi + (sizeof *pi + MAX_PASSPHRASE_LEN + 1);
+ pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 3;
pi->with_qualitybar = 1;
pi->with_repeat = 1;
- pi2->max_length = 100;
+ 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, &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 (err == -1)
{ /* 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 (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. */
int
agent_genkey (ctrl_t ctrl, const char *cache_nonce,
const char *keyparam, size_t keyparamlen, int no_protection,
const char *override_passphrase, int preset, 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 (no_protection || !cache_nonce)
passphrase = NULL;
else
{
passphrase_buffer = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
passphrase = passphrase_buffer;
}
if (passphrase || no_protection)
;
else
{
rc = agent_ask_new_passphrase (ctrl,
L_("Please enter the passphrase to%0A"
"protect your new key"),
&passphrase_buffer);
if (rc)
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 (DBG_CRYPTO)
log_debug ("storing private key\n");
rc = store_key (s_private, passphrase, 0, ctrl->s2k_count);
if (!rc)
{
if (!cache_nonce)
{
char tmpbuf[12];
gcry_create_nonce (tmpbuf, 12);
cache_nonce = bin2hex (tmpbuf, 12, NULL);
}
if (cache_nonce
&& !no_protection
&& !agent_put_cache (cache_nonce, CACHE_MODE_NONCE,
passphrase, ctrl->cache_ttl_opt_preset))
agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL);
if (preset && !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 (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);
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);
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 (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1,
ctrl->s2k_count);
}
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 (s_skey, pass, 1, ctrl->s2k_count);
if (!err && passphrase_addr)
*passphrase_addr = pass;
else
xfree (pass);
}
return err;
}